本篇介绍Task代理(TaskDelegate.swift)html
我相信可能有80%的同窗使用AFNetworking
或者Alamofire
处理网络事件,而且这两个框架都提供了丰富的功能,我也相信不少人都作了二次封装,但事实上,这个二次封装却又异常简单或者是简陋。这篇文章的内容是Task代理,是一篇很独立的文章,你们能够经过这篇文章了解iOS中网络开发是怎么一回事。git
那么一条最普通的网络请求,到底是怎样的一个过程?首先咱们根据一个URL和若干个参数生成Request,而后根据Request生成一个会话Session,再根据这个Session生成Task,咱们开启Task就完成了这个请求。固然这一过程之中还会包含重定向,数据上传,挑战,证书等等一系列的配置信息。github
咱们再聊聊代理的问题,无论是在网络请求中,仍是再其余的地方,代理都相似于一个管理员的身份。这在业务架构中是一个很好的主意。假如我把代理想象成一我的,那么这我的的工做是什么呢?swift
提供处理业务的方法,这每每被用于事件的传递。这是一种狭义上的代理,是一个协议。在开发中,无论是view仍是Controller,均可以利用代理传递事件。但咱们这里说的不是协议,协议必需要求咱们实现它的属性和方法,这对上边提到的‘人’是不友好的。在真实的理想的场景中,我和代理的交互只有两种状况:api
若是对上边的内容不太明白,只须要明白,有的代理是一个协议,有的代理是一个'人',在某些让你头疼的复杂的业务中,用代理'人'去处理。我在想是否是不叫代理,叫Manager更合适。浏览器
URLSessionTask是对task最基本的封装。按照请求的目的和响应的结果能够分为:缓存
上边图中表示了一种继承关系,与之相对应的代理以下图:服务器
我会在下边详细讲解这些代理方法。网络
TaskDelegate位于继承链的最底层,所以它提供了一些最基础的东西,这些东西也是其余Delegate共享的,咱们先看看属性:session
// MARK: Properties /// The serial operation queue used to execute all operations after the task completes. open let queue: OperationQueue /// The data returned by the server. public var data: Data? { return nil } /// The error generated throughout the lifecyle of the task. public var error: Error? var task: URLSessionTask? { didSet { reset() } } var initialResponseTime: CFAbsoluteTime? var credential: URLCredential? var metrics: AnyObject? // URLSessionTaskMetrics
咱们来分析下这些属性:
queue: OperationQueue
很明显这是一个队列,队列中能够添加不少operation。队列是能够被暂停的,经过把isSuspended
设置为true就可让队列中的全部operation暂停,直到isSuspended
设置为false后,operation才会开始执行。在Alamofire中,放入该队列的operation有一下几种状况:
isSuspended
设置为falsedata: Data?
表示服务器返回的Data,这个可能为空error: Error?
表示该代理生命周期内有可能出现的错误,这一点很重要,咱们在写一个代理或者manager的时候,能够添加这么一个错误属性,专门抓取生命周期内的错误。task: URLSessionTask?
表示一个task,对于本代理而言,task是很重要的一个属性。initialResponseTime: CFAbsoluteTime?
当task是URLSessionDataTask时,表示接收到数据的时间;当task是URLSessionDownloadTask时,表示开始写数据的时间;当task是URLSessionUploadTask时,表示上传数据的时间;credential: URLCredential?
表示证书,若是给该代理设置了这个属性,在它里边的证书验证方法中会备用到metrics: AnyObject?
apple提供了一个统计task信息的类URLSessionTaskMetrics
,能够统计跟task相关的一些信息,包括和task相关的全部事务,task的开始和结束时间,task的重定向的次数。
上边的这些属性中,能够留意一下queue的使用方法,尽可能为设计的代理添加错误处理机制。
Alamofire使用相似Properties,Lifecycle等等关键字来分割文件的,看完了上边的属性,咱们在看看Lifecycle。
// MARK: Lifecycle init(task: URLSessionTask?) { self.task = task self.queue = { let operationQueue = OperationQueue() operationQueue.maxConcurrentOperationCount = 1 operationQueue.isSuspended = true operationQueue.qualityOfService = .utility return operationQueue }() } func reset() { error = nil initialResponseTime = nil }
reset函数把error和initialResponseTime都置为nil,这个没什么好说的,在init函数中队列的建立颇有意思。在swift中,若是咱们要建立一个对象,无论是view仍是别的,均可以采用这样的方式:建立一个函数,而后马上调用,这很像JavaScript的用法。
lazy var label: UILabel = { let view = UILabel() view.backgroundColor = .clear view.textAlignment = .center view.textColor = .white view.font = .boldSystemFont(ofSize: 18) self.contentView.addSubview(view) return view }()
operationQueue.isSuspended = true
能够保证队列中的operation都是暂停状态,正常状况下,operation在被加入到队列中后,会尽快执行。
在swift中函数是一等公民,能够当参数和返回值来使用。同oc的block同样,咱们能够把他们当成一个属性,目的是提早告诉代理当遇到指定的事件时应该怎么作?在YYModel中有一小段代码就是关于Block当返回值的妙用。咱们看看TaskDelegate下的四个相关函数:
// MARK: URLSessionTaskDelegate var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
咱们先看第一个函数,这个函数对应的代理方法以下:
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:) func urlSession( _ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { var redirectRequest: URLRequest? = request if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection { redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request) } completionHandler(redirectRequest) }
上边的函数处理的问题是请求重定向问题,咱们大概讲一下重定向是怎么一回事:Web服务器有时会返回重定向响应而不是成功的报文。Web服务器能够将浏览器重定向到其余地方来执行请求。重定向响应由返回码3XX说明。Location响应首部包含了内容的新地址或优选地址的URI重定向可用于下列状况:
上边的重定向函数要求返回一个redirectRequest,就是重定向的Request,Alamofire的处理方法就是,若是给代理的重定向处理函数赋值了,就返回代理函数的返回值,不然返回服务器返回的Request。
第二个函数用于处理验证相关的事务。咱们先讲讲disposition
,他的类型是URLSession.AuthChallengeDisposition
,其实就是一个枚举:
@available(iOS 7.0, *) public enum AuthChallengeDisposition : Int { case useCredential case performDefaultHandling case cancelAuthenticationChallenge case rejectProtectionSpace }
这个受权配置一共有四个选项:
useCredential
表示使用证书performDefaultHandling
表示采用默认的方式,这个方式跟服务器返回authenticationMethod有很大关系,我简单列一些经常使用的method
NSURLAuthenticationMethodHTTPBasic
基本认证NSURLAuthenticationMethodHTTPDigest
摘要认证NSURLAuthenticationMethodClientCertificate
客户端证书认证NSURLAuthenticationMethodServerTrust
表示返回的证书要使用serverTrust建立cancelAuthenticationChallenge
表示取消认证rejectProtectionSpace
表示拒绝认证
对于验证而言,有三种方式:
咱们先给出Alamofire中的函数,而后在分析:
@objc(URLSession:task:didReceiveChallenge:completionHandler:) func urlSession( _ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling var credential: URLCredential? if let taskDidReceiveChallenge = taskDidReceiveChallenge { (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { let host = challenge.protectionSpace.host if let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host), let serverTrust = challenge.protectionSpace.serverTrust { if serverTrustPolicy.evaluate(serverTrust, forHost: host) { disposition = .useCredential credential = URLCredential(trust: serverTrust) } else { disposition = .cancelAuthenticationChallenge } } } else { if challenge.previousFailureCount > 0 { disposition = .rejectProtectionSpace } else { credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) if credential != nil { disposition = .useCredential } } } completionHandler(disposition, credential) }
若是服务器须要验证客户端的,咱们只须要给TaskDelegate的 var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
赋值就好了。
这里有一个很重要的问题,HTTPS并不会触发上边的回调函数,缘由就是NSURLSession内部有一个根证书,内部会跟服务器的证书进行验证,若是服务器的证书是证书机构颁发的话,就能够顺利经过验证,不然会报错。
另外一个很重要的问题是,上边的方法在何种状况下触发,按照apple的说法URL Session Programming Guide,当服务器响应头中包含WWW-Authenticate
或者使用 proxy authenticatio
n TLS trust validation
时,上边的方法就会被触发,其余状况下都不会触发。
Alamofire是双向验证的
双向验证的过程:
challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
时,服务器提供了一个服务器信任的证书,但这个证书也仅仅是服务器本身信任的,攻击者彻底能够提供一个证书骗过客户端,基于这个问题,客户端须要验证服务端的证书ServerTrustPolicy.swift
这个类来验证证书,大概过程就是拿本地的证书跟服务端返回的证书进行对比,若是客户端存在相同的证书就表示经过,这个过程我会在ServerTrustPolicy.swift
那篇文章中给出详细的解答所以,客户端和服务端要创建SSL只须要两步就好了:
WWW-Authenticate
响应头,并返回本身信任证书第三个函数:
///This delegate method is called under two circumstances: ///To provide the initial request body stream if the task was created with uploadTask(withStreamedRequest:) ///To provide a replacement request body stream if the task needs to resend a request that has a body stream because of an authentication challenge or other recoverable server error. ///Note ///You do not need to implement this if your code provides the request body using a file URL or an NSData object. @objc(URLSession:task:needNewBodyStream:) func urlSession( _ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { var bodyStream: InputStream? if let taskNeedNewBodyStream = taskNeedNewBodyStream { bodyStream = taskNeedNewBodyStream(session, task) } completionHandler(bodyStream) }
当给task的Request提供一个body stream时才会调用,咱们不须要关心这个方法,即便咱们经过fileURL或者NSData上传数据时,该函数也不会被调用,使用场景不多。
第四个函数:
@objc(URLSession:task:didCompleteWithError:) func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let taskDidCompleteWithError = taskDidCompleteWithError { taskDidCompleteWithError(session, task, error) } else { if let error = error { if self.error == nil { self.error = error } if let downloadDelegate = self as? DownloadTaskDelegate, let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data { downloadDelegate.resumeData = resumeData } } queue.isSuspended = false } }
该函数在请求完成后被调用,值得注意的是error不为nil的状况,除了给自身的error属性赋值外,针对下载任务作了特殊处理,就是把当前已经下载的数据保存在downloadDelegate.resumeData中,有点像断点下载。
DataTaskDelegate
继承自TaskDelegate
,实现了URLSessionDataDelegate
协议。所以下边咱们也会讲解URLSessionDataDelegate
协议的方法。咱们仍是先看这里边的属性:
// MARK: Properties var dataTask: URLSessionDataTask { return task as! URLSessionDataTask } override var data: Data? { if dataStream != nil { return nil } else { return mutableData } } var progress: Progress var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? var dataStream: ((_ data: Data) -> Void)? private var totalBytesReceived: Int64 = 0 private var mutableData: Data private var expectedContentLength: Int64?
咱们对这些属性给出必定的解释:
dataTask: URLSessionDataTask
DataTaskDelegate管理URLSessionDataTaskdata: Data?
一样是返回Data,但这里有一点不一样,若是定义了dataStream
方法的话,这个data返回为nilprogress: Progress
进度progressHandler
这不是函数,是一个元组,何时调用,在下边的方法中给出说明dataStream
自定义的数据处理函数totalBytesReceived
已经接受的数据mutableData
保存数据的容器expectedContentLength
须要接受的数据的总大小DataTaskDelegate的生命周期:
// MARK: Lifecycle override init(task: URLSessionTask?) { mutableData = Data() progress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() progress = Progress(totalUnitCount: 0) totalBytesReceived = 0 mutableData = Data() expectedContentLength = nil }
这些没什么好说的,咱们在看看有哪些函数:
// MARK: URLSessionDataDelegate var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
URLSessionDataDelegate有四个函数,咱们先看第一个函数:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { var disposition: URLSession.ResponseDisposition = .allow expectedContentLength = response.expectedContentLength if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse { disposition = dataTaskDidReceiveResponse(session, dataTask, response) } completionHandler(disposition) }
当收到服务端的响应后,该方法被触发。在这个函数中,咱们可以获取到和数据相关的一些参数,你们能够想象成响应头。Alamofire中给出了一个函数dataTaskDidReceiveResponse,咱们能够利用这个函数控制是否是要继续获取数据,默认是.allow。
咱们看第二个函数:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) { dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask) }
在上边的disposition配置中,disposition的类型是URLSession.ResponseDisposition,咱们看看这个枚举:
public enum ResponseDisposition : Int { case cancel /* Cancel the load, this is the same as -[task cancel] */ case allow /* Allow the load to continue */ case becomeDownload /* Turn this request into a download */ @available(iOS 9.0, *) case becomeStream /* Turn this task into a stream task */ }
当选择了becomeDownload
后,就会触发上边的第二个函数,在函数中会提供一个新的downloadTask。这就给咱们一个把dataTask转换成downloadTask的机会
那么咱们把dataTask转换成downloadTask究竟有什么用呢?我想到了一个使用场景,假如给定一个URL,返回的数据是Data,其实我想把这些数据下载下来,那么就可使用上边的这种技术了。举个例子,https://baidu.com
打开这个url会直接显示网页,使用上边的技术,打开这个url会直接下载网页。我并无验证上边的想法。
咱们继续看第三个函数:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let dataTaskDidReceiveData = dataTaskDidReceiveData { dataTaskDidReceiveData(session, dataTask, data) } else { if let dataStream = dataStream { dataStream(data) } else { mutableData.append(data) } let bytesReceived = Int64(data.count) totalBytesReceived += bytesReceived let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown progress.totalUnitCount = totalBytesExpected progress.completedUnitCount = totalBytesReceived if let progressHandler = progressHandler { progressHandler.queue.async { progressHandler.closure(self.progress) } } } }
这个方法算是核心方法,我在MCDownloadManager中实现下载的核心方法就是这个方法,不一样之处是,Alamofire把数据放入对象中,而我把数据写入本地文件中。对这个函数内部就不作解释了,主要就是对自定义函数和进度的一些处理。
咱们看第四个函数:
func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { var cachedResponse: CachedURLResponse? = proposedResponse if let dataTaskWillCacheResponse = dataTaskWillCacheResponse { cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse) } completionHandler(cachedResponse) }
其实,每每这样的函数才是咱们应该注意的,最多见的接受响应,处理数据,请求完成都是咱们很熟悉的方法,所以更应该多多注意这些不太熟悉的方法。
该函数用于处理是否须要缓存响应,Alamofire默认是缓存这些response的,可是每次发请求,它不会再缓存中读取。
DownloadTaskDelegate
继承自TaskDelegate
,实现了URLSessionDownloadDelegate
协议。所以下边咱们也会讲解URLSessionDownloadDelegate
协议的方法。咱们仍是先看这里边的属性:
// MARK: Properties var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask } var progress: Progress var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)? var resumeData: Data? override var data: Data? { return resumeData } var destination: DownloadRequest.DownloadFileDestination? var temporaryURL: URL? var destinationURL: URL? var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
这些属性中有和上边介绍的属性重复的部分,咱们只对不重复的部分给出说明:
downloadTask
和URLSessionDownloadDelegate相对应的URLSessionDownloadTaskresumeData
在上边咱们提到过,当请求完成后,若是error不为nil,若是是DownloadTaskDelegate,就会给这个属性赋值data
返回resumeDatadestination
经过这个函数能够自定义文件保存目录和保存方式,这个保存方式分两种,为URl建立文件夹,删除已经下载且存在的文件,这个会在后续的文章中提到temporaryURL
临时的URLdestinationURL
数据存储URLfileURL
返回文件的路径,若是destination不为nil,就返回destinationURL,不然返回temporaryURL生命周期:
// MARK: Lifecycle override init(task: URLSessionTask?) { progress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() progress = Progress(totalUnitCount: 0) resumeData = nil }
和下载相关的代理函数有三个:
// MARK: URLSessionDownloadDelegate var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)? var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
咱们先看看第一个函数:
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { temporaryURL = location guard let destination = destination, let response = downloadTask.response as? HTTPURLResponse else { return } let result = destination(location, response) let destinationURL = result.destinationURL let options = result.options self.destinationURL = destinationURL /// 说明在编码过程当中,对于存在可能出现错误的地方,必定要作error处理 do { if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } if options.contains(.createIntermediateDirectories) { let directory = destinationURL.deletingLastPathComponent() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) } try FileManager.default.moveItem(at: location, to: destinationURL) } catch { self.error = error } }
对于这样的代理方法,咱们首先要作的就是弄明白在什么状况下它会被触发。当数据下载完成后,该函数被触发。系统会把数据下载到一个临时的locationURL的地方,咱们就是经过这个URL拿到数据的。上边函数内的代码主要是把数据复制到目标路径中。
可是我有一个疑问?按照apple文档的内容:If you choose to open the file for reading, you should do the actual reading in another thread to avoid blocking the delegate queue.
应该在另外一个线程来读取数据,这样才不会阻塞当前的代理线程,不知道有什么影响?
咱们来看第二个函数:
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let downloadTaskDidWriteData = downloadTaskDidWriteData { downloadTaskDidWriteData( session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite ) } else { progress.totalUnitCount = totalBytesExpectedToWrite progress.completedUnitCount = totalBytesWritten if let progressHandler = progressHandler { progressHandler.queue.async { progressHandler.closure(self.progress) } } } }
该代理方法在数据下载过程当中被触发,主要的做用就是提供下载进度。这个比较简单,咱们看看第三个函数:
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) { if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset { downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes) } else { progress.totalUnitCount = expectedTotalBytes progress.completedUnitCount = fileOffset } }
是这样的,若是一个下载的task是能够恢复的,那么当下载被取消或者失败后,系统会返回一个resumeData对象,这个对象包含了一些跟这个下载task相关的一些信息,有了它就能从新建立下载task,建立方法有两个:downloadTask(withResumeData:)
和downloadTask(withResumeData:completionHandler:)
,当task开始后,上边的代理方法就会被触发。
UploadTaskDelegate
继承自DataTaskDelegate
。对于上传数据来讲最麻烦的就是多表单数据的上传,这个咱们会在后续的MultipartFormData.swift
给出详细的解释。
咱们先看看它的属性有哪些?
// MARK: Properties var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask } var uploadProgress: Progress var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
这些和上边出现的内容有重叠,在这里就很少作解释了,咱们再看看生命周期:
// MARK: Lifecycle override init(task: URLSessionTask?) { uploadProgress = Progress(totalUnitCount: 0) super.init(task: task) } override func reset() { super.reset() uploadProgress = Progress(totalUnitCount: 0) }
也没什么好说的,再看看函数:
// MARK: URLSessionTaskDelegate var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? func URLSession( _ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() } if let taskDidSendBodyData = taskDidSendBodyData { taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) } else { uploadProgress.totalUnitCount = totalBytesExpectedToSend uploadProgress.completedUnitCount = totalBytesSent if let uploadProgressHandler = uploadProgressHandler { uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) } } } }
该函数主要目的是提供上传的进度,在Alamofire中,上传数据用的是stream,这个会在后续文章中给出详细的解释。
我我的解读源码的方式可能比较特别,我喜欢把全部的代码都写到文章之中。由于人的记忆都是有问题的,好多东西当时记住了,过段时间就忘了,为了方便往后查看这些笔记,我以为仍是把代码都弄上来比较好。
同一段代码,不一样的人看会有不一样的想法,这些解读也能够给别人一些参考。我如今愈来愈以为代码的设计很重要了。
因为知识水平有限,若有错误,还望指出
Alamofire源码解读系列(一)之概述和使用 简书-----博客园
Alamofire源码解读系列(二)之错误处理(AFError) 简书-----博客园
Alamofire源码解读系列(三)之通知处理(Notification) 简书-----博客园