SDWebImage4.0源码探究(二)具体代码拓展

代码一

//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];
    // ...........
}];

知识点:block
参考:iOS中block的详解weakSelf、strongSelf <转自唐巧>
Block中weak/strong self的用法
深入分析 Objective-C block、weakself、strongself 实现原理

请问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 的持有。整个内存引用关系被解除。

还有哪些场景不需要weakself呢?循环引用的场景 & 处理 --- 2.3、关于Block内部要不要使用weakSelf的几种情况

请问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;

    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }

};

如果没有 strongSelf 的那行代码,那么后面的每一行代码执行时,self 都可能被释放掉了,这样很可能造成逻辑异常。

特别是当我们正在执行 strongSelf.networkReachabilityStatusBlock(status); 这个 block 闭包时,如果这个 block 执行到一半时 self 释放,那么多半情况下会 Crash。

文章详细解释了这个问题:i-finally-figured-out-weakself-and-strongself

总结:

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;
}

知识点:MD5加密过程 && 缓存图片的名称如何避免重名
参考 :MD5加密
cmd5在线加密

分析

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(^{

});

知识点:线程安全 之 切换到主线程
参考:SDWebImage4.0源码探究 -- 六、SDWebImage 如何保证UI操作放在主线程中执行?

提问:为什么不用 if ([NSThread isMainThread]) 来进行判断当前是主线程?

可参考: SDWebImage4.0源码探究 -- 六、SDWebImage 如何保证UI操作放在主线程中执行?

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");
    }
}

知识点:线程安全 之 检查当前队列
参考 SDWebImage4.0源码探究 -- 六、SDWebImage 如何保证UI操作放在主线程中执行?



代码五

//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);
}

知识点:Runtime之关联对象
参考:iOS Runtime之四:关联对象

提问1:如何给NSArray添加一个属性(不能使用继承)?

不能用继承,难道用Category?

Category不允许为已有的类添加新的成员变量,实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。但实际上可以使用runtime去实现Category为已有的类添加新的属性并生成getter和setter方法

具体细节参考:iOS Runtime之四:关联对象

提问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:runtime-Category添加属性为什么不生成setter和getter

提问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

知识点:忽略警告
参考:clang diagnostic的使用

格式:

#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];
        }

知识点:线程安全 之 多线程同步锁
参考:
iOS中保证线程安全的几种方式与性能对比
Effective Objective-C Notes:GCD 实现同步锁
不再安全的 OSSpinLock
起底多线程同步锁(iOS)
iOS中有哪些技术可以保证线程安全?



代码九

// SDWebImageDownloader.m   Line 231

dispatch_barrier_sync(self.barrierQueue, ^{
       // ..........
});

知识点:线程同步
参考:【iOS沉思录】GCD实现线程同步的方法

GCD实现线程同步的方法:

  • 组队列(dispatch_group)
  • 阻塞任务(dispatch_barrier)
  • 信号量机制(dispatch_semaphore)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,165评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,476评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,446评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,157评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,164评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,615评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,969评论 3 430
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,959评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,495评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,529评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,641评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,233评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,218评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,715评论 2 366

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,439评论 8 265
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,295评论 0 11
  • iOS面试题目100道 1.线程和进程的区别。 进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,...
    有度YouDo阅读 29,941评论 8 137
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类...
    司马DE晴空阅读 1,303评论 0 7
  • 前人有名言,不打无准备之仗,不做无准备之事。我们有多大的能耐,就会做多大的事情,但这之前,我们要提前规划好目标,有...
    水清亦有鱼阅读 526评论 8 19