本章开始将介绍SDWebImage库中的核心类SDWebImageDownloader,该类是SDWebImage库进行网络加载的底层操作核心所在,理解了该类的实现思想,就能知道SDWebImage库的网络加载策略是如何的。首先解释一下涉及到的一些基本概念和方法:
•OC中的反射机制
反射是计算机程序在运行时检查、内省、修改自身结构和行为的一种能力。这样的能力可以让一个给定的程序动态地适应不同的情况。OC语言中的OC对象,都继承自NSObject类。这个类为我们提供了一些基础的方法和协议,我们可以直接调用从这个类继承过来的方法。本篇文章中讲到的反射方法,就在NSObject和Foundation框架中。Foundation框架为我们提供了一些反射的API方法,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。
// SEL和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);
// 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。
Class class = NSClassFromString(@"ViewController");
ViewController *vc = [[class alloc] init];
SEL selector = NSSelectorFromString(@"getDataList");
[vc performSelector:selector];
通过这些方法,我们可以在运行时选择创建实例,并动态选择调用哪个方法。这些操作甚至可以由服务器传回来的参数来控制,我们可以将服务器传回来的类名和方法名,实例为我们的对象。
使用反射机制的优点:
1.可以弱化连接,他并不会把没有的Framework也link到程序中
2.不需要使用import,因为类是动态加载的,只要存在就可以加载
使用反射机制的缺点:
1.不利于维护。使用反射模糊了程序内部实际发生的事情,隐藏了程序的逻辑。这种绕过源码的方式比直接代码更为复杂,增加了维护成本。
2.性能较差。使用反射匹配字符串间接命中内存比直接命中内存的方式要慢。当然,这个程度取决于使用场景,如果只是作为程序中很少涉及的部分,这个性能上的影响可以忽略不计。但是,如果在性能很关键的应用核心逻辑中使用反射,性能问题就尤其重要了。
•dispatch_barrier_async 和dispatch_barrier_sync
dispatch_barrier_(a)sync,可以翻译成栅栏(barrier),它可以往队列里面发送任务(块,也就是block),这个任务有栅栏(barrier)的作用。在队列中,barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义,并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。
假设我们原先有6个任务要执行,我们现在要插入一个任务0,这个任务0要在1、2、3都并发执行完了之后才能执行,而4、5、6号任务要在这个任务0结束后才允许并发。dispatch_barrier_sync和dispatch_barrier_async的共同点:
1、都会等待在它前面插入队列的任务(1、2、3)先执行完
2、都会等待他们自己的任务(0)执行完再执行后面的任务(4、5、6)
dispatch_barrier_sync和dispatch_barrier_async的不共同点:
在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务(0)结束之后才会继续程序,然后插入被写在它后面的任务(4、5、6),然后执行后面的任务,而dispatch_barrier_async将自己的任务(0)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue,所以,dispatch_barrier_async的不等待(异步)特性体现在将任务插入队列的过程,它的等待特性体现在任务真正执行的过程。
使用dispatch_barrier_sync结果如下:
使用dispatch_barrier_async结果如下:
下面开始对SDWebImageDownloader类的实现思路进行解读,先看看下面使用了反射机制的代码段:
该方法使用反射机制在程序运行期间去判断工程当中是否有引入了另一个第三方库SDNetworkActivityIndicator.h,而不是一开始就将该库引进工程中,使用#import "SDNetworkActivityIndicator.h"的方式进行引用,弱化了对一些其他库的依赖关系。在该类中添加了两个观察者通知,通过loading加载视图的出现与消失来表示SDWebImageDownloaderOperation网络请求的状态。下面开始主要讲解SDWebImageDownloader类中关键的几个方法:
该方法判断当前请求操作的合法性,若要进行网络加载,则必须保证progressBlock,completedBlock有存在对应的初始化操作,同时请求的URL并没有缓存在self.URLCallbacks数组中,这样请求操作createCallback()才会执行,同时网络请求会在所有的并发请求都执行完之后,才会执行dispatch_barrier_sync(self.barrierQueue, ^{});代码块中的内容,以保证网络请求操作是有序执行,而不是无序的并发执行。
该方法是SDWebImageDownloader类的关键方法,在经过网络请求合法性性的判断之后,便开始创建网络请求对象NSMutableURLRequest *request,调用SDWebImageDownloaderOperation已经封装好的网络请求任务执行图片的数据获取跟下载操作,在图片数据下载过程中,progress(),都会被持续执行去刷新当前的图片数据大小,知道图片完全被下载完成。而在图片下载操作完成或者取消操作下都需要对当前请求的图片URL进行移除,不进行移除的情况下,在下次调用- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback方法进行合法性校验是就会存在旧值,导致网络请求无法实时的进行下载更新。