AFNetworking3.0源码解读之下载请求


标签: objective-c AFNetworking download


前言

上一篇简单介绍了一下AFURLSessionManager类的结构以及发起一个网络请求的函数调用过程。AFURLSessionManager类中有很多属性,还定义了很多的局部全局变量,在数据请求那大部分用不上。比如,AFURLSessionManager类定义了很多的block:

typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);

typedef NSURLRequest * (^AFURLSessionTaskWillPerformHTTPRedirectionBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionTaskDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);

typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task);
typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error);

typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response);
typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse);

typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes);

typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);

这里列出了该类所有的block,有没有很吓人,刚开始看的时候,我也看的头疼,后来调试的时候发现在本文接下来要讲的内容中,只用到了两个分别是AFURLSessionTaskCompletionHandler和AFURLSessionDownloadTaskDidFinishDownloadingBlock,这两个block都是delegate在完成代理后调用的。说明AFURLSessionManager中的block属性打酱油了,那我就很好奇了,既然是打酱油的那还要它干吗?其实不是,我们先来仔细观察一下它们的名字——objective-c的命名都是见名知意的,这一点我感觉很不错——以本文的下载为例,AFURLSessionDownloadTaskDidFinishDownloadingBlock意:下载任务完成时的block。那我们怎么去使用这个呢?呆会就讲。

正文

和上一篇一样还是以官方git上的例子看起,源码如下:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

NSURL *URL = [NSURL URLWithString:@"http://img04.tooopen.com/images/20130701/tooopen_10055061.jpg"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];

NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];

    return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    NSLog(@"File downloaded to: %@", filePath);
    self.imgView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:filePath]];

}];
//以下三行是我添加的,下面三行设置了下载完成后的回调
typedef NSURL * (^DownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);

id block = ^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location){
    //doing something
    NSLog(@"%@",location);
/*
 * 将上面的打印语句注释掉,改成下面的,再运行一次看看会有什么结果?
    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; 
        
    return [documentsDirectoryURL URLByAppendingPathComponent:[downloadTask.response suggestedFilename]];
    */
};
[manager setDownloadTaskDidFinishDownloadingBlock:block];


[downloadTask resume];

这里我在网上随便找了一张图片,然后还设置了manager的一个AFURLSessionDownloadTaskDidFinishDownloadingBlock,就是刚才说那些打酱油中的block中的一个。在这里只是简单的打印了一下location,并没有返回一个路径,注意哦,这个block要返回一个路径的哦,这个路径是用来存放我们下载的图片的。

这里通过manager实例方法来创建downloadTask:

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                             progress:(NSProgress * __autoreleasing *)progress
                                          destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                    completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    __block NSURLSessionDownloadTask *downloadTask = nil;
    dispatch_sync(url_session_manager_creation_queue(), ^{
        downloadTask = [self.session downloadTaskWithRequest:request];
    });
    //这个是用来设置下载代理的
    [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];

    return downloadTask;
}

然后又进入到[NSURLSessionManager addDelegateForDownloadTask:progress:destination:completionHandler]这个函数设置了具体的代理对象,以及下载完成后设置了图片存储路径的block,上一篇的数据请求也会进行这一步,但下载比数据请求多了一些东西,让我们来看看多了什么:

- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(NSProgress * __autoreleasing *)progress
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
//downloadTaskDidFinishDownloading -> NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
    if (destination) {
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            return destination(location, task.response);
        };
    }

    if (progress) {
        *progress = delegate.progress;
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];
}

和数据请求相比,这里主要多了两个地方一个是设置了下载完成后的回调,一个是设置了下载的进度。这里设置的是delegate相应的回调。函数的最后一句就是保存delegate,这个和数据请求部分一样,不解释了。最后还会调用这个函数:

- (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block {
    self.downloadTaskDidFinishDownloading = block;
}

因为我们之前设置了manager下载完成后的回调就是这句:

[manager setDownloadTaskDidFinishDownloadingBlock:block];

现在可以启动downloadTask了。

网络正常的情况以及URL没错的情况下,收到下载的数据后就会调用manager实现的代理方法:

//AFURLSessionManager->NSURLSessionDownloadDelegate->下载时调用,可能调用多次
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
//downloadTaskDidWriteData - > void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

这个函数没什么可以讲主要就是用来计算下载的进度。当下载完成的时候就会进入到这个代理方法中:

//AFURLSessionManager->NSURLSessionDownloadDelegate->下载完成是调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
//downloadTaskDidFinishDownloading -> NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
    //下载的时候这个block没有用
    if (self.downloadTaskDidFinishDownloading) {
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            if (error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }

    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

这里有一个重点,单步执行的时候会发现,程序会进入到第一个if里面。还记得在开头的例子中我加了三行代码:

typedef NSURL * (^DownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);

id block = ^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location){
    NSLog(@"%@",location);
};
[manager setDownloadTaskDidFinishDownloadingBlock:block];

这三行设置了manager的downloadTaskDidFinishDownloading属性,它是下载完成后的一个回调,它要求返回一个URL用来存放下载的图片,如果在这个block里返回了一个URL那么它会进入到第一个if里去,并且执行完就返回了。图片也就保存到了返回的这个URL了,后面的代码就不会执行了,如果没有返回一个URL的话,程序就是把下载的图片保存到这里:

//这是例子开头我们设置的用来保存图片的URL,如果我们设置了managerr的downloadTaskDidFinishDownloading属性并且同时在这个block里返回了一个URL那么下面这两行代码就不会执行了,因为已经有保存图片的URL了。只有block里没有返回URL下面的代码才会执行
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];

return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];

上面的这两行代码是在这个代理方法里调用的:

//AFURLSessionManagerTaskDelegate->NSURLSessionTaskDelegate->下载完成时调用
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;
//downloadTaskDidFinishDownloading -> NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
    if (self.downloadTaskDidFinishDownloading) {
        //下载完成时调用block
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

这里需要注意的是这个downloadTaskDidFinishDownloading是delegate里的和manager里的不一样,不要被迷惑了。这个block返回URL,往下执行就是把图片保存在这个URL上了。这个block是不是不知道我们什么时候设置的了。写了这么我,我也差一点忘记了。它是在这个[AFURLSessionMananger addDelegateForDownloadTask:progress:destination:completionHandler:]函数里设置的,注意里面的这一行代码:

if (destination) {
    delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
        return destination(location, task.response);
    };
}

block的用法有没有很神奇它竟然捕获从最外面传进来的destination,并且保存到现在,而且现在要执行它来返回图片要保存的URL。

到这里基本也就快结束了,执行完,就会进入这个[AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]函数里了,这一步和数据请求没有什么区别。经过这么多调用,图片也就下载完了。前面设置manager的downloadTaskDidFinishDownloading属性,这个个人认为可以用来返回指定的URL,也可以用来做一些,下载完成后的后续处理工作。

—EOF—

由于disqus被墙,评论需要VPN哦。