优秀源码阅读:Swift网络库Alamofire

jopen 8年前

原文:swift网络库Alamofire源码分析


阅读优秀源码一直是提高自身技术的一个非常好的学习方式,用swift也有一段时间了,由于要用到网络库, OBJ-C下的AFNetworking对应swift下的Alamofire, 至于为什么是这个名字, 我也不知道, 不过在wiki上看到对于 Alamo Fire 的解释, Texas州的州花

本篇文章对于swift下的网络库alamofire的实现源码进行分析下(基于版本3.1.1)

目录结构

优秀源码阅读:Swift网络库Alamofire

首先看下源码结构,Source目录下包含Alamofire.swift文件,主要是暴露一些常用的调用接口给我们,Core文件夹下是一些核心的功能实现文件,Feature是在Core的基础上包装成我们更常用,更方便使用的功能

如果你只想知道如何使用这个库的话,基本上你只需要了解Alamofire.swift以及Core/ResponseSerialization.swift这两个文件就可以了

通用工具函数或类

在进行整体分析前,先来说明下库当中的一些通用的东西

  1. Error.swift用于生成NSError错误对象, 包含一些常用网络错误 如数据错误 服务器状态错误,解析序列错误等

  2. ParameterEncoding.swift 网络请求时的HTTP的content-type 如application/x-www-form-urlencoded; charset=utf-8, application/json等

  3. Result.swift 网络返回结果, 一个枚举, 包括Success和Failure, 其成功时会有数据, 失败时会有错误信息, 用于后面的response

  4. Alamofire.swift里定义的两个protocol, public protocol URLStringConvertible:方便NSURLRequest与String的转换, 以及public protocol URLRequestConvertible

整体流程图

优秀源码阅读:Swift网络库Alamofire

上图为alalmofire库的网络请求流程图, 后面会根据此图对一个网络请求从开始到结束的整体流程进行分析.由于NSURLSession包含data, upload, download等几种不同task, 本篇主要跟踪data的流程, 其余两个流程及原理都是相同的, 只是类型不同

根据使用文档, 如果要发起一个GET请求, 服务器返回的为JSON类型数据, 只需要使用如下方法

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])  .responseJSON { response in   print(response.request)  // original URL request   print(response.response) // URL response   print(response.data)  // server data   print(response.result)   // result of response serialization   if let JSON = response.result.value {    print("JSON: \(JSON)")   }  }

来看看Alamofire.request方法做了什么, 它调用了Manager类当中的request方法

public func request(   method: Method,   _ URLString: URLStringConvertible,   parameters: [String: AnyObject]? = nil,   encoding: ParameterEncoding = .URL,   headers: [String: String]? = nil)   -> Request  {   return Manager.sharedInstance.request(    method,    URLString,    parameters: parameters,    encoding: encoding,    headers: headers   )  }

Manager.swift

  • public class Manager:  Manager类有个单例方法, 该方法的生成的NSURLSession的configuration为系统默认的NSURLSessionConfiguration.defaultSessionConfiguration(), 将session的delegate置为自己的成员变量delegate: SessionDelegate. 这个delegate既有session回调代理的作用,也有task回调代理调度分发的作用,它会根据不同的task类别分配给不同类别的delegate对象处理. 上一步中request的对应方法为

public func request(   method: Method,   _ URLString: URLStringConvertible,   parameters: [String: AnyObject]? = nil,   encoding: ParameterEncoding = .URL,   headers: [String: String]? = nil)   -> Request  {   let mutableURLRequest = URLRequest(method, URLString, headers: headers)   let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0   return request(encodedURLRequest)  }  public func request(URLRequest: URLRequestConvertible) -> Request {   var dataTask: NSURLSessionDataTask!   dispatch_sync(queue) {    dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest)   }   let request = Request(session: session, task: dataTask)   delegate[request.delegate.task] = request.delegate   if startRequestsImmediately {    request.resume()   }   return request  }

这里会依据URLRequest生成一个dataTask(防止多线程问题, 生成都在一个线程中进行), 同时由session与dataTask生成一个Request对象(这里的request并不是我们常用的NSURLRequest,而是Alamofire中封装的Request对象)返回, 此方法中的delegate[request.delegate.task] = request.delegate, 指定了这个dataTask的对应的delegate回调处理类, 下面会讲到.

  • public final class SessionDelegate: 此类被声明为final对象, 不准被继承

public final class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {  private var subdelegates: [Int: Request.TaskDelegate] = [:]  private let subdelegateQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT)  subscript(task: NSURLSessionTask) -> Request.TaskDelegate? {   get {    var subdelegate: Request.TaskDelegate?    dispatch_sync(subdelegateQueue) {     subdelegate = self.subdelegates[task.taskIdentifier]    }    return subdelegate   }   set {    dispatch_barrier_async(subdelegateQueue) {     self.subdelegates[task.taskIdentifier] = newValue    }   }  }

可以看到它实现了session以及3种不同task的代理. 此类中实现了subscript的下标功能, 使之能更方便对不同的task根据不同task id调用不同的delegate处理方法.

实际上是通过subdelegate数组实现,里面存储的每个元素是一个字典, 这个字典中拥有key为taskIdentifier, value为对应的真正要处理delegate的类. 也是为了防止多线程问题使用了dispatch_barrier_async来确保线程安全。

为了使使用更加灵活,这个类中实现了类似拦截的机制, 使之可以自定义session的回调,进行自定义, 做为一个类库, 肯定不能因为自己的一些特殊用处就在自己项目中对此类库源码随意修改(当然你准备自己维护一套另当别论), 所以它自对每个delegate方法都给出自定义的实现, 我们如果有这方面的需求, 可以参考这种实现

public override func respondsToSelector(selector: Selector) -> Bool {   switch selector {   case "URLSession:didBecomeInvalidWithError:":    return sessionDidBecomeInvalidWithError != nil   case "URLSession:didReceiveChallenge:completionHandler:":    return sessionDidReceiveChallenge != nil   case "URLSessionDidFinishEventsForBackgroundURLSession:":    return sessionDidFinishEventsForBackgroundURLSession != nil   case "URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:":    return taskWillPerformHTTPRedirection != nil   case "URLSession:dataTask:didReceiveResponse:completionHandler:":    return dataTaskDidReceiveResponse != nil   default:    return self.dynamicType.instancesRespondToSelector(selector)   }  }

比如我想对sessionDidReceiveChallenge自定义, 我只需要实现

Manager.sharedInstance.delegate.sessionDidReceiveChallenge = {(session, challenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential) in      let disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling      let credential = NSURLCredential(user: "user", password: "password", persistence: .None)         return (disposition, credential)  }

就可以完成对challenge的自定义

Request.swift

  • public class Request: 在初始化时根据不同task生成不同的delegate处理类, 每个类当中都有对应的回调函数

init(session: NSURLSession, task: NSURLSessionTask) {   self.session = session   switch task {   case is NSURLSessionUploadTask:    self.delegate = UploadTaskDelegate(task: task)   case is NSURLSessionDataTask:    self.delegate = DataTaskDelegate(task: task)   case is NSURLSessionDownloadTask:    self.delegate = DownloadTaskDelegate(task: task)   default:    self.delegate = TaskDelegate(task: task)   }  }
  • public class TaskDelegate 及其对应子类 DataTaskDelegate, UploadTaskDelegate, DownloadTaskDelegate:

这些类才是真正之前讲的要处理task对应回调任务, 存储data, error, progress等信息, 负责task的suspend, resume, cancel等基本操作

里面的成员变量 public let queue: NSOperationQueue是很重要的一个元素, 后面的response的处理都是在它上面 被初始化后suspended被置为true,只有当task完成之后 (func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)), 才被置为false,此时加在它内部的operation即response的解析化才会真正执行,解析后执行回调completionHandler

response.swift

  • public struct Response: 一个泛型的struct, 实际上它的类型是和一开始讲到的result对应的类型一致的,都包含了真正的请求结果数据value或者error,  例如string, data, json就是result当中value的值.

public struct Response{   /// The URL request sent to the server.   public let request: NSURLRequest?   /// The server's response to the URL request.   public let response: NSHTTPURLResponse?   /// The data returned by the server.   public let data: NSData?   /// The result of response serialization.   public let result: Result   /**    Initializes the `Response` instance with the specified URL request, URL response, server data and response    serialization result.    - parameter request:  The URL request sent to the server.    - parameter response: The server's response to the URL request.    - parameter data:  The data returned by the server.    - parameter result:   The result of response serialization.    - returns: the new `Response` instance.   */   public init(request: NSURLRequest?, response: NSHTTPURLResponse?, data: NSData?, result: Result) {    self.request = request    self.response = response    self.data = data    self.result = result   }  }

ResponseSerialization.swift

  • public protocol ResponseSerializerType:序列化的协议, 定义了对response序列化的协议函数, 这里有一个值得学习的是对typealias的应用,可参考typealias 和泛型接口

  • public struct ResponseSerializer: ResponseSerializerType: 实现了ResponseSerializerType协议, 不仅要实现协议里的函数, 还要实现里面的typealias定义

  • extension Request: 对Request的序列化的扩展实现, 比如我们一开始的responseJson函数, 通过调用JSONResponseSerializer返回一个序列化对象,此函数中用到了swift的do-catch 机制来获取和处理异常, 最后统一调用了response函数, 这里就是前面所讲的在request.delegate的operationQueue中添加了一个执行序列化的operation, 完成后将封装好的response对象通过block回调传出。

public static func JSONResponseSerializer(      options options: NSJSONReadingOptions = .AllowFragments)      -> ResponseSerializer     {      return ResponseSerializer { _, response, data, error in       guard error == nil else { return .Failure(error!) }       if let response = response where response.statusCode == 204 { return .Success(NSNull()) }       guard let validData = data where validData.length > 0 else {        let failureReason = "JSON could not be serialized. Input data was nil or zero length."        let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)        return .Failure(error)       }       do {        let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options)        return .Success(JSON)       } catch {        return .Failure(error as NSError)       }      }     }  public func responseJSON(      options options: NSJSONReadingOptions = .AllowFragments,      completionHandler: Response-> Void)      -> Self     {      return response(       responseSerializer: Request.JSONResponseSerializer(options: options),       completionHandler: completionHandler      )     }  public func response(      queue queue: dispatch_queue_t? = nil,      responseSerializer: T,      completionHandler: Response-> Void)      -> Self     {      delegate.queue.addOperationWithBlock {       let result = responseSerializer.serializeResponse(        self.request,        self.response,        self.delegate.data,        self.delegate.error       )       dispatch_async(queue ?? dispatch_get_main_queue()) {        let response = Response(         request: self.request,         response: self.response,         data: self.delegate.data,         result: result        )        completionHandler(response)       }      }      return self     }

到这里就完成了一次请求流程,对于upload及download流程都是一样的

总结:

Alamofire是很好用的一个网络库, 使用起来很方便,不过如果项目有一定规模及复杂度,建议还是在项目中按照自己的需求进行一定的封装

因为是基于NSURLSession实现的网络请求, 如果你有多个并发请求, 比如下载多个文件,直接发出10个请求, 但你只想支持最大两个并发, NSURLSession的timeoutIntervalForRequest以及timeoutIntervalForResource并不能满足你的需求, 你必须自己使用operationqueue 实现。

参考资料:

Swift Generics Tutorial :这篇文章对泛型讲解的很好,不过示例项目是不是swift2.0的, 只要稍微改下便可运行

typealias 和泛型接口