美文网首页
读AFNetworking代码(三)

读AFNetworking代码(三)

作者: likefly | 来源:发表于2018-11-14 11:39 被阅读10次

    我们直接回去看datatask的生成,回到刚开始的那段代码

    - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                           URLString:(NSString *)URLString
                                          parameters:(id)parameters
                                             headers:(NSDictionary <NSString *, NSString *> *)headers
                                      uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                    downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                             success:(void (^)(NSURLSessionDataTask *, id))success
                                             failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
    {
        NSError *serializationError = nil;
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        for (NSString *headerField in headers.keyEnumerator) {
            [request addValue:headers[headerField] forHTTPHeaderField:headerField];
        }
        if (serializationError) {
            if (failure) {
                dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                    failure(nil, serializationError);
                });
            }
    
            return nil;
        }
    
        __block NSURLSessionDataTask *dataTask = nil;
        dataTask = [self dataTaskWithRequest:request
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            if (error) {
                if (failure) {
                    failure(dataTask, error);
                }
            } else {
                if (success) {
                    success(dataTask, responseObject);
                }
            }
        }];
    
        return dataTask;
    }
    

    进到代码里面看NSURLSessionDataTask的生成

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                 downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    
        __block NSURLSessionDataTask *dataTask = nil;
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
    
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    我们看到这句话,

    url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
    

    session根据传入的参数request生成了datatask,这个是iOS的API了,但是为什么加了一个safely,好像是做了一个保护,我们看一下这段保护的代码

    static void url_session_manager_create_task_safely(dispatch_block_t _Nonnull block) {
        if (block != NULL) {
            if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
                // Fix of bug
                // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
                // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
                // 同步等待这个block执行完,继续往下执行,保证task生成完一个再生成下一个
               //串行队列防止iOS8 以下 dataTaskWithRequest并发创建task,导致id的不唯一性
                dispatch_sync(url_session_manager_creation_queue(), block);
            } else {
                block();
            }
        }
    }
    
    static dispatch_queue_t url_session_manager_creation_queue() {
        static dispatch_queue_t af_url_session_manager_creation_queue;
        static dispatch_once_t onceToken;
        //dispatch_once保证即便在多线程环境下,也只会创建一条串行队列
        dispatch_once(&onceToken, ^{
            af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
        });
    
        return af_url_session_manager_creation_queue;
    }
    

    从代码看过去跟系统版本有关系,做了多线程的保护。看来系统的兼容性和多线程是一个永久的话题。大家可以根据作者的注释去github上找一下原因。
    在iOS8之前系统又一个bug,在并发线程里面, NSURLSessionTask's taskIdentifier isn't always unique 而AFN就是根据这个taskIdentifier来区分每一个请求进而给出正确的回调的,所以AFN希望这个taskIdentifier是唯一的,为了解决这个问题所以在iOS8一下的系统,AFN串行调用了这个系统API保证taskIdentifier的唯一性,进而保证每个请求对应每个回调的唯一性。
    作者用了一个同步串行队列来做的这件事情,至于原因我写在注释里面了,可以自行查阅。
    不知道大家对同步、异步、串行队列、并发队列理解的怎么样,我也是在读了很多底层网络库代码之后才对他们有所理解,希望后面有时间再把这部分知识总结一下吧。

    写到这里task就已经生成,之后调用它的resume方法,就会直接启动这个发送亲请求出去。后面便是就接收请求的过程了。

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
    - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                    uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                  downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                 completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
    
        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
        [self setDelegate:delegate forTask:dataTask];
    
        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
    

    NSURLSession的回调都是在它提供的代理方法回传数据的, AFN封装了自己的AFURLSessionManagerTaskDelegate,来将数据整合给上层。
    我们看到这个方法里面,创建了AFURLSessionManagerTaskDelegate的实例对象,然后给他的属性赋值,很重要是这句话[self setDelegate:delegate forTask:dataTask];,实现了datatask和delegate的绑定,我们来看一下这个方法

    - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
                forTask:(NSURLSessionTask *)task
    {
        NSParameterAssert(task);
        NSParameterAssert(delegate);
    
        [self.lock lock];
        self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
        [self addNotificationObserverForTask:task];
        [self.lock unlock];
    }
    

    这个容器self.mutableTaskDelegatesKeyedByTaskIdentifier就是存储了task的ID和对应的delegate。由于这些操作都是在复杂的多线程环境调用的,所以加了一把锁来保证线程安全。
    然后为NSURLSessionTask的启动和停止增加观察者,监听启动和停止事件。

    最好将uploadProgressBlock与downloadProgressBlock赋值给了AFN代理的uploadProgressBlock和downloadProgressBlock。

    现在我再重新返回去,回到生成NSURLSessionDataTask的函数中去

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                 downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    
        __block NSURLSessionDataTask *dataTask = nil;
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
    
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    看看我们现在有什么了,初始化的时候我们有了NSURLSession,然后我们生成request,现在我们又根据request生成了相应的dataTask,并且绑定了AF的代理到相应的dataTask。在dataTask启动之后,根据这个绑定关系,AF就可以将NSURLSession相应的代理回调转发,封装好数据传给上层了。

    那现在我就开始看NSURLSession的代理回调,以及AF做了哪些转发,如何转发的。
    首先看一下NSURLSession的代理


    NSURLSession协议组.jpg

    NSURLSessionDelegate 是 NSURLSession 众多协议的父协议,继承自根协议 NSObjectProtocol

    NSURLSessionTaskDelegate 就是 NSURLSessionDelegate 的子协议
    同样的,对应于 task,NSURLSessionTaskDelegate 有多个子协议NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionStreamDelegate (没有 upload 对应的协议)。
    其实AFN并没有全部实现这些协议,例如NSURLSessionStreamDelegate这个跟TCP/IP直连的协议没有实现,其他代理的协议的方法也并没有全部实现。我们顺着AFN的代码一路看过去就好。

    • NSURLSessionDelegate
    代理1
    //当前这个session已经失效时,该代理方法被调用。
    - (void)URLSession:(NSURLSession *)session
    didBecomeInvalidWithError:(NSError *)error
    {
        if (self.sessionDidBecomeInvalid) {
            self.sessionDidBecomeInvalid(session, error);
        }
    
        [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
    }
    

    session无效时触发该回调,如果用户定义了相应的block则执行,此外还会法相应的通知出去。AFN其实没有处理这个回调,该使用权交给了用户,去处理一些意外情况。

    代理2
    // https 认证
    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;
    
        if (self.sessionDidReceiveAuthenticationChallenge) {
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        } else {
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    if (credential) {
                        disposition = NSURLSessionAuthChallengeUseCredential;
                    } else {
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                    }
                } else {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
    
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }
    

    这部分代码是跟https认证相关的,我们后面单独用一章的篇幅来讲他。先暂时放一下。

    代理3
    //当session中所有已经入队的消息被发送出去后,会调用该代理方法
    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
        if (self.didFinishEventsForBackgroundURLSession) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.didFinishEventsForBackgroundURLSession(session);
            });
        }
    }
    
    NSURLSessionTaskDelegate
    代理1
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    willPerformHTTPRedirection:(NSHTTPURLResponse *)response
            newRequest:(NSURLRequest *)request
     completionHandler:(void (^)(NSURLRequest *))completionHandler
    {
        NSURLRequest *redirectRequest = request;
        // 如果用户自定了重定向的block,则用这个block利用四个参数重新生成一个请求
        if (self.taskWillPerformHTTPRedirection) {
            redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
        }
        // 将request转发出去
        if (completionHandler) {
            completionHandler(redirectRequest);
        }
    }
    

    当服务器重定向的时候,会回调这个代理。

    代理2
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;
    
        if (self.taskDidReceiveAuthenticationChallenge) {
            disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
        } else {
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                } else {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
    
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }
    

    这个也是一个跟https认证相关的回调。这个代理回调执行的内容和之前的内容一直,区别是这个回调是task级别的,之前的是session级别的。回调的参数相对之前多了task参数,这样方便我们在task级别自定义自己的https认证方式。

    //当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法。
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
     needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
    {
        NSInputStream *inputStream = nil;
    
        if (self.taskNeedNewBodyStream) {
            inputStream = self.taskNeedNewBodyStream(session, task);
        } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
            inputStream = [task.originalRequest.HTTPBodyStream copy];
        }
    
        if (completionHandler) {
            completionHandler(inputStream);
        }
    }
    

    1、当用户收到https Challenge或者其他服务器可以恢复的服务器错误,而导致需要客户端重新发送一个含有body stream的request,这时候会调用该代理。
    2、如果task是由uploadTaskWithStreamedRequest:创建的,那么提供初始的request body stream时候会调用该代理方法。

    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
       didSendBodyData:(int64_t)bytesSent
        totalBytesSent:(int64_t)totalBytesSent
    totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
    {
    
        int64_t totalUnitCount = totalBytesExpectedToSend;
        if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
            NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
            if(contentLength) {
                totalUnitCount = (int64_t) [contentLength longLongValue];
            }
        }
        
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
        
        if (delegate) {
            [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
        }
    
        if (self.taskDidSendBodyData) {
            self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
        }
    }
    

    周期性的回调通知代理,已经上传了多少字节的数据到服务器,总共需要上传多少数据。

    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
        // delegate may be nil when completing a task in the background
        if (delegate) {
            // 根据task找到对应的afn的代理,然后转发
            [delegate URLSession:session task:task didCompleteWithError:error];
            // 转发完成后,一处task
            [self removeDelegateForTask:task];
        }
        // 如果有自定义的回调,那么回调处理这个task
        if (self.taskDidComplete) {
            self.taskDidComplete(session, task, error);
        }
    }
    

    task完成时候的回调,无论是成功或者失败都会调用到这里。这里转发了这个代理到afn的代理,转发完成后一处这个task。至此宣布这个task声明周期的结束。

    • NSURLSessionDataDelegate
    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
    {
        NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    
        if (self.dataTaskDidReceiveResponse) {
            disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
        }
    
        if (completionHandler) {
            completionHandler(disposition);
        }
    }
    

    收到服务器响应后会调用此代理。函数的作用:
    告诉代理,该task获取到了服务器回传的最初始回复(response)。注意回调的completinHandler是由传入的参数来决定下一步做什么或者怎么做。

    • NSURLSessionResponseAllow:该task正常执行
    • NSURLSessionResponseCancel:该task取消
    • NSURLSessionResponseBecomeDownload:会调用另一个方法URLSession:dataTask:didBecomeDownloadTask:来新建一个download task代替当前的task
    • NSURLSessionResponseBecomeStream:转成一个StreamTask
    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
        if (delegate) {
            [self removeDelegateForTask:dataTask];
            [self setDelegate:delegate forTask:downloadTask];
        }
    
        if (self.dataTaskDidBecomeDownloadTask) {
            self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
        }
    }
    

    这个代理方法,是被上面的的代理方法的disposition设置为NSURLSessionResponseBecomeDownload的时候执行的,作用是创建一个新的download task,替换调之前的task。所以这里afn的代理重新的绑定了datatask。

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
        didReceiveData:(NSData *)data
    {
    
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
        [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    
        if (self.dataTaskDidReceiveData) {
            self.dataTaskDidReceiveData(session, dataTask, data);
        }
    }
    

    接收到数据的回调,这个回调会在收到数据的时候反复的调用直到接收完毕。而这些数据是在afn的代理中被拼接,然后回调给上层的。因为task是afn代理的,afn直到每个想用的response应该如何拼接到对于的task中。

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
     willCacheResponse:(NSCachedURLResponse *)proposedResponse
     completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
    {
        NSCachedURLResponse *cachedResponse = proposedResponse;
    
        if (self.dataTaskWillCacheResponse) {
            cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
        }
    
        if (completionHandler) {
            completionHandler(cachedResponse);
        }
    }
    

    询问data task代理或upload task是否应该将响应存储在缓存中。

    • NSURLSessionDownloadDelegate
    代理1
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
    didFinishDownloadingToURL:(NSURL *)location
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
        if (self.downloadTaskDidFinishDownloading) {
            NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
            if (fileURL) {
                delegate.downloadFileURL = fileURL;
                NSError *error = nil;
                
                if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
                }
    
                return;
            }
        }
    
        if (delegate) {
            [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
        }
    }
    

    当下载任务完成的时候会调用这个代理。这里面如果自己定义了处理的block会做本地文件的一些处理,代码比较清晰移动。同样也会抓发到afn的代理中。

    代理2
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
          didWriteData:(int64_t)bytesWritten
     totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    {
        
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
        
        if (delegate) {
            [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
        }
    
        if (self.downloadTaskDidWriteData) {
            self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
        }
    }
    

    周期性的通知下载进度。

    代理3
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didResumeAtOffset:(int64_t)fileOffset
    expectedTotalBytes:(int64_t)expectedTotalBytes
    {
        
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
        
        if (delegate) {
            [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
        }
    
        if (self.downloadTaskDidResume) {
            self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
        }
    }
    

    当下载被取消或者失败后,重新恢复下载时调用。

    • 其实这个就是用于断点续传的代理方法。可以在下载失败的时候,拿到我们失败的拼接部分的resumedata,然后去调用downloadTaskWithResumeData:就会到这个代理来。其中的fileOffset这个参数,如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么该值为0。否则,该值表示当前已经下载data的偏移量。
    总结:

    至此afn中实现的NSUrlSesssion的代理就写完了。其实NSUrlSesssion的代理afn并不是完全转发,下一篇我们重点看一些afn转发的代理,也是使用者比较关心的部分了。

    相关文章

      网友评论

          本文标题:读AFNetworking代码(三)

          本文链接:https://www.haomeiwen.com/subject/sabtxqtx.html