搜索
您的当前位置:首页正文

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

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

本文如未解决您的问题请添加抖音号:51dongshi(抖音搜索懂视),直接咨询即可。

热门图文

Top