代码一
//UIView+WebCache.m Line 53
__weak __typeof(self)wself = self;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator];
// ...........
}];
请问1:什么时候在 block 里面用 self,不需要使用 weak self ?
当 block 本身不被 self 持有,而被别的对象持有,同时不产生循环引用的时候,就不需要使用 weak self 了。最常见的代码就是 UIView 的动画代码,我们在使用 UIView 的 animateWithDuration:animations 方法 做动画的时候,并不需要使用 weak self,因为引用持有关系是:
- UIView 的某个负责动画的对象持有了 block
- block 持有了 self
- 因为 self 并不持有 block,所以就没有循环引用产生,因为就不需要使用 weak self 了。
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
当动画结束时,UIView 会结束持有这个 block,如果没有别的对象持有 block 的话,block 对象就会释放掉,从而 block 会释放掉对于 self 的持有。整个内存引用关系被解除。
请问2:为什么 block 里面还需要写一个 strong self,如果不写会怎么样?
在 block 中先写一个 strong self,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。
不仅仅是在SDWebImage中,在AFNetworking中也存在这样的例子:
// AFNetworkReachabilityManager.m 的一段代码
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
= status;
if {
}
};
如果没有 strongSelf 的那行代码,那么后面的每一行代码执行时,self 都可能被释放掉了,这样很可能造成逻辑异常。
总结:
weakSelf是为了解决循环引用
strongSelf是为了保证任何情况下self在超出作用域后仍能够使用。(防止self变成nil,延迟self的生命)。
代码二
// SDImageCache.m Line 167
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];
return filename;
}
分析
1、先转为UTF_8编码的字符串
2、设置一个接受字符数组,md5加密后是128bit, 16 字节 * 8位/字节 = 128 位 = 128bit / 4 = 32个十六进制数
// #import <CommonCrypto/CommonDigest.h>
#define CC_MD5_DIGEST_LENGTH 16 /* digest length in bytes */
3、CC_MD5函数加密
// #import <CommonCrypto/CommonDigest.h> Line 133
extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)
// 调用
CC_MD5(str, (CC_LONG)strlen(str), r);
// 把str字符串转换成了32位的16进制数列(这个过程不可逆转) 存储到了result这个空间中
4、将16字节的16进制转成32字节的16进制字符串
x表示十六进制,%02X 意思是不足两位将用0补齐,如果多余两位则不影响
NSLog("%02X", 0x888); //888
NSLog("%02X", 0x4); //04
5、获取后缀名,并拼接
[key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]
// 从路径中获得完整的文件名(带后缀)
exestr = [filePath lastPathComponent];
// 获得文件名(不带后缀)
exestr = [exestr stringByDeletingPathExtension];
// 获得文件的后缀名(不带'.')
exestr = [filePath pathExtension];
代码三
// SDWebImageCompat.h Line 102
#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif
// 调用
dispatch_main_async_safe(^{
});
提问:为什么不用 if ([NSThread isMainThread])
来进行判断当前是主线程?
SDWebImage 就是从判断是否在主线程执行改为判断是否由主队列上调度。而由于主队列是一个串行队列,无论任务是异步同步都不会开辟新线程,所以当前队列是主队列等价于当前在主线程上执行。可以这样说,在主队列调度的任务肯定在主线程执行,而在主线程执行的任务不一定是由主队列调度的。
代码四
// SDImageCache.m Line 138
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}
代码五
//UIView+WebCache.m Line 130
- (UIActivityIndicatorView *)activityIndicator {
return (UIActivityIndicatorView *)objc_getAssociatedObject(self, &TAG_ACTIVITY_INDICATOR);
}
- (void)setActivityIndicator:(UIActivityIndicatorView *)activityIndicator {
objc_setAssociatedObject(self, &TAG_ACTIVITY_INDICATOR, activityIndicator, OBJC_ASSOCIATION_RETAIN);
}
提问1:如何给NSArray添加一个属性(不能使用继承)?
不能用继承,难道用Category?
Category不允许为已有的类添加新的成员变量,实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。但实际上可以使用runtime去实现Category为已有的类添加新的属性并生成getter和setter方法
提问2:那Category能不能添加属性呢?
Category不能添加成员变量(instance variables),那到底能不能添加属性(property)呢?
这个我们要从Category的结构体开始分析:
typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;
从Category的定义也可以看出 可以添加实例方法,类方法,甚至可以实现协议,添加属性,但无法添加实例变量。
提问3:
提问4:Category为什么可以添加方法,而不可以添加成员变量?
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
代码六
// SDWebImageDownloader.m Line 38
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
格式:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相关命令"
//需要操作的代码
#pragma clang diagnostic pop
相关命令
Wdeprecated-declarations // 忽略弃用的警告
Wincompatible-pointer-types // 忽略不兼容指针类型
Warc-retain-cycles // 循环引用警告
Wunused-variable // 未使用变量警告
Wcovered-switch-default // 未使用default警告
代码七
// SDWebImageCompat.h Line 84
#if OS_OBJECT_USE_OBJC
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q)
#define SDDispatchQueueSetterSementics strong
#else
#undef SDDispatchQueueRelease
#undef SDDispatchQueueSetterSementics
#define SDDispatchQueueRelease(q) (dispatch_release(q))
#define SDDispatchQueueSetterSementics assign
#endif
// 使用 SDImageCache.m Line 133
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
SDDispatchQueueRelease(_ioQueue);
}
通过宏控制ARC和非ARC下queue的释放和属性标识
代码八
// SDWebImageManager.m Line 128
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
代码九
// SDWebImageDownloader.m Line 231
dispatch_barrier_sync(self.barrierQueue, ^{
// ..........
});
GCD实现线程同步的方法:
- 组队列(dispatch_group)
- 阻塞任务(dispatch_barrier)
- 信号量机制(dispatch_semaphore)