[iOS]SDWebImage 源码阅读(三)下载

下载部分从这里开始


            SDWebImageDownloaderOptions downloaderOptions = 0;
            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
            if (image && options & SDWebImageRefreshCached) {
                // force progressive off if image already cached but forced refreshing
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }


上面是下载的一些选项。
这里我之前提到了用位运算判断选项的方法,并且这里用或运算来赋值,可以在一个变量上判断多个值。


            id  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished);

然后来到了这里,让我们进去看看,先不管返回的是什么东东。


    dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;

        if (first) {
            createCallback();
        }
    });

这里用到了dispatch_barrier_sync函数,这是一个同步栅栏函数,执行这个代码块之前,必须把前面的operation都执行完毕,且当这个代码块执行完毕后,后面的才能开始执行,保证了线程安全性。

这部分代码的作用是根据url,把progressBlock和completeBlock赋给它,等到需要用的时候取出来,由于这个字典是公用的,所以要保证线程安全性,所以用到了上面的方式。


        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }


这部分构造request,赋予相应的请求头,cookie等,而且缓存策略默认是不使用NSURLCache的。

在operation初始化的地方有一个知识点,他在block里面对weakSelf又做了一次赋值 

SDWebImageDownloader *sself = wself;

这样是为了保留对象,以防block在执行过程中self被销毁了,导致程序崩溃,这样做只有当block执行完self才能被销毁,而且不会造成循环引用。


之后把operation放到队列里面并发执行。


在operation放到队列里之后还为operation添加了依赖来达到LIFO的执行顺序,如果用GCD就很难实现。



整个的下载由一个自定义的operation完成,该operation是下载的代理。


我们来看看这个自定义的operation。

通常我们在网上看到的自定义operation代码都是写在main函数里面执行,而该库写在了start函数里面。

他们的区别是start需要自己去管理整个operation的状态,包括 isCanceled,isFinished 等等。且如果代码写在main函数里面,如果代码执行完毕,该operation会被移出队列,然后operation会被销毁,这个时候作为代理,就不能正确的接收代理消息,会发生错误。


        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

start函数里面首先就是这句,而且不止一处。因为正在运行的operation你用cancel方法是不能取消的,只能改变他的cancel状态,你在代码里加上这些语句就可以及时终止了。



        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

                if (sself) {
                    [sself cancel];

                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }

如果允许在程序进入后台还执行下载的话,就执行上面的代码,可以比正常多运行几分钟,一般来说足够了,这里也用到了 block内__strong self的方式。block内的代码是如果在给定时间内还没有完成操作的话会执行取消操作。


        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
        self.thread = [NSThread currentThread];
    }
    
    [self.dataTask resume];

    if (self.dataTask) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

这里用的是NSURLSession而不是NSURLConnection,因为NSURLConnection已经不建议使用了。

然后就是发请求的操作了。

关于NSURLSession的代理方法就不说了,全都在自定义的operation里面。


    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }

这一段对应前面那部分代码,表示后台代码的结束。


你可能感兴趣的:(iOS开发)