Alamofire源码分析
源码分析
(test工程+源码注释工程)https://www.dropbox.com/sh/v7poaxekotxlaf1/AADQb7NbrrfHXJLrBt1Q51vVa?dl=0
整体的流程图
创建Session用来发送请求,可以自定义创建,也可以直接使用AF单例,默认的Session来发送请求
外部调用request()方法,传入URLConvertible,创建Request对象
- 内部开始先用URLConvertible来创建URLRequestConvertible对象
- 用URLRequestConvertible创建Request(这个Request就是返回给外部的对象),保存到Session中,然后开始对Request进行预处理
- 先创建初始URLRequest,用预处理器对其进行预处理,在预处理前后都有使用方法来告知Request,流程变更,用来通知事件监听器,所以看EventMonitor协议中,有一大堆生命周期的回调事件。
- 预处理完成,返回Request对象
外部调用response系列方法,在这些方法中实现对响应的处理
- 内部会先创建对原始响应数据的处理闭包
- 先解析响应(还记录了解析所花的时间)
- 解析失败的重试
- 解析成功执行外部传入的完成回调
- 把这个闭包追加保存到Request的responseSerializers数组中
- 检测下当前Request是不是已经完成了,完成的话重新标记成执行中
- 检测下当前Request是否已经完成了全部响应的解析,如果是的话,就手动执行processNextResponseSerializer()方法开始继续执行responseSerializers数组中的解析闭包
- 检测下当前Request是否需要发送请求,需要的话,就调用resume()方法发送请求
AF.request为例,调用情况
简单的request请求,并对request后的AFDataResponse<String>
不做处理
func requestAction() {/// 打印百度的htmlString/// 网址string是作为URLConvertible协议类型传入,实现一个asURL的方法转换为URLAF.request("https://www.baidu.com").responseString { resp inswitch resp.result {case let .success(str):debugPrint("request success:\(str)")case let .failure(err):debugPrint("request fail:\(err)")}}}
输入的是String
类型的"https://www.baidu.com"
,输入的网址string是作为URLConvertible协议类型传入的,该协议可以看作是URL类型的协议抽象,需要实现一个asURL
的方法,用来生成请求用的URL。
跳到第一个方法
使用传入的参数调用RequestEncodableConvertible()方法创建了一个convertible局部变量,类型只是个简单的结构体,实现了URLRequestConvertible协议类型
/// request 默认是get请求open func request(_ convertible: URLConvertible,method: HTTPMethod = .get,parameters: Parameters? = nil,encoding: ParameterEncoding = URLEncoding.default,headers: HTTPHeaders? = nil,interceptor: RequestInterceptor? = nil,requestModifier: RequestModifier? = nil) -> DataRequest {let convertible = RequestConvertible(url: convertible,method: method,parameters: parameters,encoding: encoding,headers: headers,requestModifier: requestModifier)return request(convertible, interceptor: interceptor)}
第二个方法
使用入参的convertible + 拦截器对象
Session所持有的rootQueue(回调执行队列),serializationQueue(响应解析队列),eventMonitor(事件监听器对象),以及把自己作为RequestDelegate对象,构建出了DataRequest对象
open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest {let request = DataRequest(convertible: convertible,underlyingQueue: rootQueue,serializationQueue: serializationQueue,eventMonitor: eventMonitor,interceptor: interceptor,delegate: self)perform(request)return request}
第三个方法
使用了rootQueue队列执行,先判断request要是取消了的话,直接return
/// 准备发送请求 主入口func perform(_ request: Request) {rootQueue.async {//现在rootQueue中判断是否请求被取消guard !request.isCancelled else { return }//塞入到正在请求的Request集合中self.activeRequests.insert(request)//在requestQueue队列发送请求self.requestQueue.async {// Leaf types must come first, otherwise they will cast as their superclass.switch request {//Request为uploadRequest//然后在rootQueue告知事件监听器didCreateUploadable, 然后调用performSetupOperations方法//创建失败的话先在rootQueue告知监视器didFailToCreateUploadable, 错误消息为createUploadableFailed//然后在request中决定是否重试case let r as UploadRequest: self.performUploadRequest(r) // UploadRequest must come before DataRequest due to subtype relationship.//直接调用performSetupOperationscase let r as DataRequest: self.performDataRequest(r)//如果是新建下载, 直接调用performSetupOperations方法//如果是断点续传, 在rootQueue调用didReceiveResumeData方法, 详见下方断点续传部分case let r as DownloadRequest: self.performDownloadRequest(r)///直接调用performSetupOperationscase let r as DataStreamRequest: self.performDataStreamRequest(r)default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))")}}}}
调用了performDataRequest
//直接调用performSetupOperationscase let r as DataRequest: self.performDataRequest(r)
第四个方法
检测是否在RequestQueue
执行,然后继续调用performSetupOperations()
方法继续处理
/// 处理成功后, 会调用didCreateURLRequest方法来更新状态func performDataRequest(_ request: DataRequest) {//当前在requestQueuedispatchPrecondition(condition: .onQueue(requestQueue))performSetupOperations(for: request, convertible: request.convertible)}
第五个方法
- 构建原始URLRequest对象T
- 对T进行有效行校验
- 告知Request,T已经初始化完成,把T传给Request进行内部处理
- 如果Request中有请求适配器,逐一调用适配器对T进行处理
- 处理完成之后,告知Request已经适配完成
- 最终调用didCreateURLRequest()方法完成
func performSetupOperations(for request: Request,convertible: URLRequestConvertible,shouldCreateTask: @escaping () -> Bool = { true }){//当前在RequestQueuedispatchPrecondition(condition: .onQueue(requestQueue))//URLRequestConvertible生成的URLRequestlet initialRequest: URLRequestdo {initialRequest = try convertible.asURLRequest()//检测请求是否有效(get请求不能带body参数)try initialRequest.validate()} catch {//在rootQueue队列上报错误rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) }return}//在rootQueue通知request,初始化URLRequest成功rootQueue.async { request.didCreateInitialURLRequest(initialRequest) }//检查是否有请求适配器guard !request.isCancelled else { return }guard let adapter = adapter(for: request) else {guard shouldCreateTask() else { return }//没有拦截器的话直接通知rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) }return}//使用拦截器中的适配器来预处理请求adapter.adapt(initialRequest, for: self) { result indo {let adaptedRequest = try result.get()try adaptedRequest.validate()//通知处理完成self.rootQueue.async { request.didAdaptInitialRequest(initialRequest, to: adaptedRequest) }guard shouldCreateTask() else { return }self.rootQueue.async { self.didCreateURLRequest(adaptedRequest, for: request) }} catch {//任何错误都抛出requestAdaptationFailed错误self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) }}}}
因为没有拦截器所以调用了didCreateURLRequest(initialRequest, for: request)
第六个方法
/// 当创建请求完成, 拦截适配器处理完成之后, 就会来到这里的逻辑func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {dispatchPrecondition(condition: .onQueue(rootQueue))///通知创建request成功request.didCreateURLRequest(urlRequest)guard !request.isCancelled else { return }// 创建URLSessionTask,基类Request为实现该方法,几个子类各自实现 ```let task = request.task(for: urlRequest, using: session)// 写入session的request—Task数据队列保存requestTaskMap[request] = task// 线程安全保存taskrequest.didCreateTask(task)updateStatesForTask(task, request: request)}
request.task(for: urlRequest, using: session)
创建了URLSessionTask
,这就是真正用来发送请求的task,然后把这个task存在了Session的requestTaskMap字典中,key是request,这样可以在任意时候根据request找到它所对应的task。开始发送请求。
AF对请求的处理完成
Response (接受响应)处理
Request() -> response()
因为写的是responseString
第一步
唯一需要必须传入的参数就是完成的回调闭包
completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self
/// Adds a handler using a `StringResponseSerializer` to be called once the request has finished./// 添加解析方法/// - Parameters:/// - queue: The queue on which the completion handler is dispatched. `.main` by default./// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the/// `completionHandler`. `PassthroughPreprocessor()` by default./// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined/// from the server response, falling back to the default HTTP character set, `ISO-8859-1`./// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default./// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default./// - completionHandler: A closure to be executed once the request has finished.////// - Returns: The request.@discardableResultpublic func responseString(queue: DispatchQueue = .main,dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,encoding: String.Encoding? = nil,emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self {response(queue: queue,responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor,encoding: encoding,emptyResponseCodes: emptyResponseCodes,emptyRequestMethods: emptyRequestMethods),completionHandler: completionHandler)}
执行的是response(
第二步
对数据进行一大堆的逻辑、解析处理
/// Adds a handler to be called once the request has finished./// 使用自定义解析器解析/// - Parameters:/// - queue: The queue on which the completion handler is dispatched. `.main` by default/// - responseSerializer: The response serializer responsible for serializing the request, response, and data./// - completionHandler: The code to be executed once the request has finished.////// - Returns: The request.@discardableResultpublic func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,responseSerializer: Serializer,completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void)-> Self {appendResponseSerializer {// Start work that should be on the serialization queue.// 开始解析响应, 必须在响应解析队列完成, 因为有解析操作, 所以开始计时let start = ProcessInfo.processInfo.systemUptime// 用入参解析器解析数据,catch错误并转换成AFErrorlet result: AFResult<Serializer.SerializedObject> = Result {try responseSerializer.serialize(request: self.request,response: self.response,data: self.data,error: self.error)}.mapError { error inerror.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error)))}// 用app启动的时间差值来计算出解析所花的时间let end = ProcessInfo.processInfo.systemUptime// End work that should be on the serialization queue.// 用request内部队列来继续处理self.underlyingQueue.async {// 组装DataResponse,Success类型为序列化协议中的泛型SerializedObject类型let response = DataResponse(request: self.request,response: self.response,data: self.data,metrics: self.metrics,serializationDuration: end - start,result: result)// 告知监听器self.eventMonitor?.request(self, didParseResponse: response)guard let serializerError = result.failure, let delegate = self.delegate else {self.responseSerializerDidComplete { queue.async { completionHandler(response) } }return}// 解析出错,准备重试delegate.retryResult(for: self, dueTo: serializerError) { retryResult in// 是否完成的回调,nil表示要重试var didComplete: (() -> Void)?defer {// 用defer处理(也就是return 或者执行最后一行再处理defer里的内容)if let didComplete = didComplete {self.responseSerializerDidComplete { queue.async { didComplete() } }}}/// 根据参数retryResult判断是否重试switch retryResult {case .doNotRetry: // 不重试直接完成didComplete = { completionHandler(response) }case let .doNotRetryWithError(retryError): // 不重试,把error替换掉// 用新的retryError初始化resultlet result: AFResult<Serializer.SerializedObject> = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError"))// 封装新的Responselet response = DataResponse(request: self.request,response: self.response,data: self.data,metrics: self.metrics,serializationDuration: end - start,result: result)didComplete = { completionHandler(response) }case .retry, .retryWithDelay: //重试delegate.retryRequest(self, withDelay: retryResult.delay)}}}}return self}
这一大堆的处理,其实就是appendResponseSerializer()
的方法接收一个很多数据处理的闭包参数
最后返回的是 return self
responseString
方法返回的类型是self
,可以连续链式的调用responseString
第三步
解析数据
根据Request当前状态来延迟调用
/// Appends the response serialization closure to the instance.////// - Note: This method will also `resume` the instance if `delegate.startImmediately` returns `true`./// 线程安全添加响应解析回调/// - Parameter closure: The closure containing the response serialization call.func appendResponseSerializer(_ closure: @escaping () -> Void) {$mutableState.write { mutableState in// 加入数组mutableState.responseSerializers.append(closure)// 如果请求完成了,标记为继续if mutableState.state == .finished {mutableState.state = .resumed}//如果响应解析完成了, 调一下解析方法if mutableState.responseSerializerProcessingFinished {underlyingQueue.async { self.processNextResponseSerializer() }}// 如果不是完成状态,且可以转换为继续状态if mutableState.state.canTransitionTo(.resumed) {// 问session是否需要立即开始,是的话立即调用resume()underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } }}}}
- 如果当前Request被标记finished,表示这个Request已经请求完成了,现在是正在对他再一次执行response,那就把request标记成**resumed,表示正在执 ** ,后续逻辑就不会发送请求,而是直接调用入解析回调。
- 检测下responseSerializerProcessingFinished字段,该字段是用来标记当前正在执行的某个解析回调是否完成了,如果完成了,就手动调用一下processNextResponseSerializer()方法来执行下一个回调闭包。如果尚未完成,就不需要手动调用。因为解析完成的逻辑会自己检测执行下一个解析。递归的来着。。。
- 最后,需要判断下,当前request能否变成resume,这个操作是因为:只有initialized,suspended,这两个状态,可以变成resume,代表需要对task调用resume方法来发送请求/继续请求。而且,必须request的startImmediately设置为true才会自动调用resume()方法,否则,需要手动调用resume()才能发送请求。
自定义AlamofireManager
实例化session
class AlamofireManager {private static var session: Session!class func shareInstance() -> Session {//不应该每次调用此方法都要初始化,应当检测是否初始化过,如果已经初始化就不需要进行初始化if session == nil {let configuration = URLSessionConfiguration.default/// 请求timeout截止时间为50秒configuration.timeoutIntervalForRequest = 50session = Session(configuration: configuration)}return session}
}
前提准备BaseBean
Swift的反射功能,的iOS Runtime不一样,Swift的反射用了另一套API,实现机制也完全不一样
反射是一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。
- 动态地创建对象和属性,
- 动态地获取一个类里面所有的属性,方法。
- 获取它的父类,或者是实现了什么样的接口(协议)
- 获取这些类和属性的访问限制(Public 或者 Private)
- 动态地获取运行中对象的属性值,同时也能给它赋值(KVC)
- 动态调用实例方法或者类方法
- 动态的给类添加方法或者属性,还可以交换方法(只限于Objective-C)
class BaseBean: NSObject,NSCoding {required override init() {}//当子类中定义了异于父类中的构造方法(参数名称不同、参数个数不同、参数类型不同),//那么父类中使用required修饰的构造方法在子类中必须实现,而且这个方法必须使用required关键字而不能使用override修饰required init?(coder: NSCoder) {super.init()// 反射机制(是指可以动态获取类型、成员信息)let mir = Mirror(reflecting: self)for item in mir.children {if let label = item.label, let cachedValue = coder.decodeObject(forKey: label) {if let stringValue = cachedValue as? String {self.setValue(stringValue, forKey: label)}else if let intValue = cachedValue as? Int {self.setValue(intValue, forKey: label)}else {}}}}func encode(with coder: NSCoder) {let mir = Mirror(reflecting: self)for item in mir.children {if let label = item.label, let labelValue = self.value(forKey: label) {coder.encode(labelValue, forKey: label)}}}
}
如果model使用NSObject类型,而不是Codable类型,就需要JSON转NSObject类型的model
为更方便,引入了使用swiftyJSON
https://github.com/SwiftyJSON/SwiftyJSON
import SwiftyJSON// JSON转NSObject类型的model
class DataArchiveUtils<D: NSObject>: NSObject {/// 列表类型class func getDatas(json: JSON, dataKey: String) -> [D]? {let dataData = json[dataKey].description.data(using: .utf8)let data = Reflect<D>.mapObjects(data: dataData)return data}class func getData(json: JSON, dataKey: String) -> D? {let dataData = json[dataKey].description.data(using: .utf8)let data = Reflect<D>.mapObject(data: dataData)return data}}
Any类型的话也可以使用 TTReflect
https://github.com/TifaTsubasa/TTReflect
把有用的抽出来
///转换json为NSObject的对象 data转为继承NSObject的对象或NSObject的数组对象
///Swift版JsonModel自动映射 https://github.com/TifaTsubasa/TTReflect
class Reflect<M: NSObject> {// MARK: - reflect with json/**map object with json- returns: special type object*/class func mapObject(json: Any?) -> M {guard let json = json else { return M() }guard json is NSDictionary || json is [String: AnyObject] else {return M()}let model = M()model.mapProperty(json)return model}class func mapObjects(json: Any?) -> [M] {guard let json = json else {return [M]()}guard json is NSArray || json is [AnyObject] else {return [M]()}guard let arrayJson = json as? [AnyObject] else {return [M]()}let models: [M] = arrayJson.map {return Reflect<M>.mapObject(json: $0)}return models}// MARK: - reflect with dataclass func mapObject(data: Data?) -> M {guard let data = data else { return M() }do {let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)return Reflect<M>.mapObject(json: json as AnyObject?)} catch {debugPrint("Serializat json error: \(error)")}return M()}class func mapObjects(data: Data?) -> [M] {guard let data = data else { return [M]() }do {let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)return Reflect<M>.mapObjects(json: json as AnyObject?)} catch {debugPrint("Serializat json error: \(error)")}return [M]()}// MARK: - reflect with plist nameclass func mapObject(_ plistName: String?) -> M {let plistPath = Bundle.main.path(forResource: plistName, ofType: "plist")guard let path = plistPath else {debugPrint("Reflect error: Error plist name")return M()}let json = NSDictionary(contentsOfFile: path)return Reflect<M>.mapObject(json: json)}class func mapObjects(_ plistName: String?) -> [M] {let plistPath = Bundle.main.path(forResource: plistName, ofType: "plist")guard let path = plistPath else {debugPrint("Reflect error: Error plist name")return [M]()}let json = NSArray(contentsOfFile: path)return Reflect<M>.mapObjects(json: json)}
}// MARK: - object map setting protocol
@objc
public protocol TTReflectProtocol {@objc optional func setupMappingReplaceProperty() -> [String: String]@objc optional func setupMappingObjectClass() -> [String: AnyClass]@objc optional func setupMappingElementClass() -> [String: AnyClass]@objc optional func setupMappingIgnorePropertyNames() -> [String]
}// MARK: - private function
extension NSObject: TTReflectProtocol {// main functionfileprivate func mapProperty(_ json: Any) {if json is NSNull { return }// mapping settinglet replacePropertyName = self.getMappingReplaceProperty()let ignorePropertyNames = self.getMappingIgnorePropertyNames()let mappingObjectClass = self.getMappingObjectClass()let mappingElementClass = self.getMappingElementClass()let keys = ergodicObjectKeys()for key in keys {let jsonKey = replacePropertyName[key] ?? keylet jsonValue = (json as AnyObject).value(forKey: jsonKey)guard !ignorePropertyNames.contains(key) else {continue} // ignore propertyguard let value = jsonValue else {continue}if value is NSNull { // ignore null porpertydebugPrint("Reflect error: The key \(jsonKey) value is \(value)")continue}setPropertyValue(value as AnyObject, forKey: key)// map sub objectif let trackObject = mapSubObject(key, jsonKey: jsonKey, mappingObjectClass: mappingObjectClass, value: value as AnyObject){setValue(trackObject, forKey: key)}// map sub arrayif let trackObjects = mapSubObjectArray(key, jsonKey: jsonKey, mappingElementClass: mappingElementClass, value: value as AnyObject) {setValue(trackObjects, forKey: key)}}}fileprivate func mapSubObject(_ key: String, jsonKey: String, mappingObjectClass: [String: AnyClass], value: AnyObject) -> AnyObject? {guard mappingObjectClass.keys.contains(jsonKey) else {return nil}guard let objClass = mappingObjectClass[jsonKey] as? NSObject.Type else {fatalError("Reflect error: Sub-model is not a subclass of NSObject")}let model = objClass.init()guard value is NSDictionary || value is [String: AnyObject] else {debugPrint("Reflect error: Error key: \(key) -- mapping sub-model without a dictionary json")return nil}model.mapProperty(value)return model}fileprivate func mapSubObjectArray(_ key: String, jsonKey: String, mappingElementClass: [String: AnyClass], value: AnyObject) -> AnyObject? {guard mappingElementClass.keys.contains(jsonKey) else {return nil}guard let objClass = mappingElementClass[jsonKey] as? NSObject.Type else {fatalError("Reflect error: Sub-model is not a subclass of NSObject")}guard let subArrayJson = value as? [AnyObject] else {debugPrint("Reflect error: Error key: \(key) -- mapping sub-model array without a array json")return nil}let submodelArray: [NSObject] = subArrayJson.map {let submodel = objClass.init()if $0 is NSDictionary || $0 is [String: AnyObject] {submodel.mapProperty($0)} else {debugPrint("Reflect error: Error key: \(key) -- mapping sub-model array element without a dictionary json")}return submodel}return submodelArray as AnyObject}fileprivate func setPropertyValue(_ value: AnyObject?, forKey key: String) {// convert typevar transFlag: Bool?var transValue: AnyObject?let valueTuple = (self.value(forKey: key), value)switch valueTuple {case let (objValue as NSNumber, jsonValue as NSString):transFlag = falseif objValue.isBool { // string -> boolif jsonValue == "true" {transValue = true as AnyObject?transFlag = true}} else { // string -> numberif let res = NumberFormatter().number(from: jsonValue as String) {transValue = restransFlag = true}}case let (_ as NSString, jsonValue as NSNumber):transValue = "\(jsonValue)" as AnyObject?transFlag = truedefault:setValue(value, forKey: key)}if let transFlag = transFlag {if transFlag {// Debug.DebugPRINT(obj: "Reflect warning: The key \(key) have different type value")setValue(transValue, forKey: key)} else {// Debug.DebugPRINT(obj: "Reflect error: The key \(key) map error type")}}}//func ergodicObjectKeys() -> [String] {var keys = [String]()let mirror = Mirror(reflecting: self)if let objectKeys = reflectObjectKeys(mirror) {keys = objectKeys}return keys}func reflectObjectKeys(_ mirror: Mirror?) -> [String]? { // iOS8+guard let mirror = mirror else { return nil }var keys = mirror.children.compactMap {$0.label}if mirror.superclassMirror?.subjectType != NSObject.self {if let subKeys = reflectObjectKeys(mirror.superclassMirror) {keys.append(contentsOf: subKeys)}}return keys}func getObjectKeys(_ cls: AnyClass) -> [String] { // iOS 7var keys = [String]()var propNum: UInt32 = 0let propList = class_copyPropertyList(cls, &propNum)for index in 0..<numericCast(propNum) {let prop: objc_property_t = propList![index]keys.append(String(validatingUTF8: property_getName(prop))!)}if class_getSuperclass(cls) != NSObject.self {if let k = class_getSuperclass(cls) {keys.append(contentsOf: getObjectKeys(k))}}return keys}fileprivate func getMappingReplaceProperty() -> [String: String] {var replacePropertyName = [String: String]()return getProtocolSetting(&replacePropertyName, aSelector: #selector(TTReflectProtocol.setupMappingReplaceProperty))}fileprivate func getMappingIgnorePropertyNames() -> [String] {var ignorePropertyNames = [String]()return getProtocolSetting(&ignorePropertyNames, aSelector: #selector(TTReflectProtocol.setupMappingIgnorePropertyNames))}fileprivate func getMappingObjectClass() -> [String: AnyClass] {var mappingObjectClass = [String: AnyClass]()return getProtocolSetting(&mappingObjectClass, aSelector: #selector(TTReflectProtocol.setupMappingObjectClass))}fileprivate func getMappingElementClass() -> [String: AnyClass] {var mappingElementClass = [String: AnyClass]()return getProtocolSetting(&mappingElementClass, aSelector: #selector(TTReflectProtocol.setupMappingElementClass))}fileprivate func getProtocolSetting<T>(_ emptySetting: inout T, aSelector: Selector) -> T {guard self.responds(to: aSelector) else {return emptySetting}let res = self.perform(aSelector)emptySetting = res?.takeUnretainedValue() as! Treturn emptySetting}
}
还有一个就是JSONDecoder,Data类型转换model,model是继承codable
let decoder = JSONDecoder()let model = try? decoder.decode(NetModel.self, from: jsonData)if model != nil {success(model!)}
自定义request RequestClient<T: BaseBean>: NSObject
T:继承BaseBean,主要用到是callback
第一个参数的请求结果 Reflect<T>.mapObject(data: response.data)
,callback的第二个为请求是否成功,nil为不成功判断,第三个为原始数据
class RequestClient<T: BaseBean>: NSObject {/// 请求接口////// - parameter method: 请求方式/// - parameter url: 请求url/// - parameter params: 请求参数/// - parameter callback: 回调,第一个参数为请求结果,第二个参数为错误信息,nil表示成功,第三个参数是请求返回原始数据class func request(method: HTTPMethod, url: String, params: [String: Any], callback:((T?, String?,String?) -> Void)?) {let newURL = "https://user.52jtg.com/" + urllet p = paramsdebugPrint(newURL)debugPrint(p)/// post放在body里,其余放在queryString里let encode = URLEncoding(destination: method == HTTPMethod.post ? .httpBody: .queryString)AlamofireManager.shareInstance().request(newURL, method: method, parameters: p, encoding: encode, headers: nil).responseString { response in/// 序列化响应的结果if response.result.isSuccess {do {let json = try JSON(data: response.data!)debugPrint(json)if json["successCode"] == "true" || json["successCode"] == true {/// data转换为NSObject对象callback?(Reflect<T>.mapObject(data: response.data),nil,response.value)}else {let message: String = json["message"].stringValuecallback?(nil, message, response.value)}} catch (let error) {let str = String(data: response.data!, encoding: String.Encoding.utf8)debugPrint(str ?? "")debugPrint(error.localizedDescription)callback?(nil,error.localizedDescription,response.value)}}else { //response.result.isFailureif let error = response.error?.localizedDescription {callback?(nil, "请求失败: \(error)",response.value)}else {callback?(nil, "请求失败:未知错误",response.value)}}}}
上传图片
class func upLoad(imgs: [UIImage],url: String, params: [String: Any],progressClosure: ((Double) -> Void)? = nil, callback: ((T?, String?, String?) -> Void)?) {
写了比较少用到的请求json文件,该JSON为swiftyJSON类
class RequestJsonData {/// 获取JSON/// - Parameters:/// - url: url转换/// - params: Parameters/// - method: 默认postclass func getJSON(url: String, params: [String : String], method: HTTPMethod = .post,completed:((JSON)-> Void)?,failed:((String?)->Void)?) {
运用到实例当中,我用家长端的一个请求数据为例子
https://eolinker.52jtg.com/#/home/project/inside/api/detail?groupID=-1&apiID=1866&projectName=今托管-家长端&projectID=14
class func requestModel(success: @escaping(NetModel)-> Void,failue: @escaping Failure) {var params: [String: Any] = [:]params = ["keyword":"","lat":22.99430230034722,"lon": 113.3336423068576,"page": 1,"pageSize":20,"sortName": "DISTANCE"]let urlTest = "search/preferred-shop/index-home-keyword-shop/?requestType=json"//不使用泛型直接转换,// RequestClient.request(method: .get, url: urlTest, params: params) { bean, error,result in/// 使用继承NSObject的泛型转换RequestClient<NetBean>.request(method: .get, url: urlTest, params: params) { bean, error,result inif error == nil || error == "" {if let jsonData = result?.data(using: .utf8) {// BaseBean解析//data转换为NSObject对象,model继承 BaseBean// 代码:callback?(Reflect<T>.mapObject(data: response.data),nil,response.value)let beanModel = beanprint(beanModel?.result)// 直接对原始数据使用JSONDecoder解析// model继承codableprint(jsonData)let decoder = JSONDecoder()let model = try? decoder.decode(NetModel.self, from: jsonData)if model != nil {success(model!)}}}else {print(error ?? "错误信息未知")}}}
可以选择两种形式返回,一个是success闭包返回继承codable的model数据返回,另一个也可以使用继承BaseBean(NSObject)的model数据返回。
func requestAction2() {NetTool.requestModel { model inself.model = modelprint(model)} failue: { error inprint(error.localizedDescription)}}
如果不是默认格式的数据,也可以自己写一个出来的alamofire请求
func shareInstance() {var params: [String: Any] = [:]params = ["keyword":"","lat":22.99430230034722,"lon":113.3336423068576,"page": 1,"pageSize":20,"sortName": "DISTANCE"]let urlTest = "https://user.52jtg.com/search/preferred-shop/index-home-keyword-shop/?requestType=json"AlamofireManager.shareInstance().request(urlTest, method: .get, parameters: params).responseString { response inif response.result.isSuccess {var json: JSON?if let dataStr = response.data {json = JSON(dataStr)}if json?["successCode"] == true || json?["successCode"] == "true" {if let result = json?["result"] {self.modelList = DataArchiveUtils<NetListBean>.getDatas(json: result, dataKey: "list") ?? []print(self.modelList)}}}}}
取消当前的请求(用URL来判断)
/// 取消当前sessionManager下某个请求class func cancelRequest(path: String) {if session != nil {let urlpath = "https://user.52jtg.com/" + pathAF.session.getAllTasks { sessionTask insessionTask.forEach { task inif task.currentRequest?.url?.path == urlpath {task.cancel()}}}}}
设置cookies
/// 设置所有的cookiesclass func setCookies(properties: [HTTPCookiePropertyKey : String]) {if let cookie = HTTPCookie(properties: properties) {// 单例AF.session.configuration.httpCookieStorage?.setCookie(cookie)}}///使用案例let cookieProps = [HTTPCookiePropertyKey.domain: "##put your domain here##",HTTPCookiePropertyKey.path: "/123",HTTPCookiePropertyKey.name: "fffff",HTTPCookiePropertyKey.value: "21312"]AlamofireManager.setCookies(properties: cookieProps)
删除cookies
// 删除所有的cookielet cstorage = HTTPCookieStorage.shared
// if let cookies = cstorage.cookies(for: url) { // 也可以单独删除cookiesif let cookies = cstorage.cookies {for cookie in cookies {cstorage.deleteCookie(cookie)}}
下载
/// 下载file/// - Parameters:/// - file: 真实的url下载链接/// - fileName: 下载后的名称func downLoadFile(file: String, fileName: String) {var fileStr = fileif fileStr.isIncludeChinese() {fileStr = file.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""}var url = URL(string: fileStr)let destination: DownloadRequest.Destination = { _, response inlet documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]let fileURL = documentsURL.appendingPathComponent("file1/\(fileName)")// 两个参数表示如果又同名文件则会覆盖,如果路径中文文件夹不存在则会自动创建url = fileURLreturn (fileURL,[.removePreviousFile,.createIntermediateDirectories])}AlamofireManager.shareInstance().download(fileStr,to: destination).downloadProgress { progress inprint(progress)}.responseData { response inif let url = url {// 验证文件self.docController = UIDocumentInteractionController(url: url)self.docController.presentOptionsMenu(from: self.view.bounds,in: self.view ,animated: true)}}}
多表单
核心语句
AlamofireManager.shareInstance().upload(multipartFormData: { multipartFormData in// 保存类型for imageData in imageDataArray {let timeStamp = Int(Date().timeIntervalSince1970)multipartFormData.append(imageData, withName: "files",fileName: "\(timeStamp)_\(imageData).jpeg",mimeType: "image/jpeg")}}, to: url).responseString
multipartFormData
回车换行字符串,内部做好数据的格式
// 封装回车换行字符串enum EncodingCharacters {static let crlf = "\r\n"}/// 封装多表单数据的分隔符,该分隔符需要存放在body头中enum BoundaryGenerator {enum BoundaryType {/// 起始:--分隔符\r\ncase initial/// 中间:\r\n --分隔符\r\ncase encapsulated/// 结束:\r\n --分隔符 -- \r\ncase final}
/// 生成分隔符Data,拼接数据用static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data {let boundaryText: Stringswitch boundaryType {case .initial:boundaryText = "--\(boundary)\(EncodingCharacters.crlf)"case .encapsulated:boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)"case .final:boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)"}return Data(boundaryText.utf8)}
只需要调用multipartFormData.append(imageData, withName: "files",fileName: "\(timeStamp)_\(imageData).jpeg",mimeType: "image/jpeg")
就能转换为标准格式的表单头(contents)
/// 最终编码的表单数据格式:/// - `前分隔符(若是第一个数据块, 就没有前分隔符)`/// - `表单头`/// - `表单数据`/// - `后分隔符(若是最后一个数据块, 后分隔符是终结分隔符)`public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {// 封装成bodyPart对象let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)// 存入数组bodyParts.append(bodyPart)}
执行uploadrequest的build 数据整合并判断可以upload
func build() throws -> UploadRequest.Uploadable {let uploadable: UploadRequest.Uploadableif multipartFormData.contentLength < encodingMemoryThreshold {// 把保存多表单的数据encode(编码)let data = try multipartFormData.encode()uploadable = .data(data)} else {let tempDirectoryURL = fileManager.temporaryDirectorylet directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")let fileName = UUID().uuidStringlet fileURL = directoryURL.appendingPathComponent(fileName)try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)do {try multipartFormData.writeEncodedData(to: fileURL)} catch {// Cleanup after attempted write if it fails.try? fileManager.removeItem(at: fileURL)throw error}uploadable = .file(fileURL, shouldRemove: true)}return uploadable}
数据就是通过,格式容器初始化
然后用户传递需要上传的数据,填充进去
包装成一个个 bodyPart,通过一个结合容器收集bodyParts
全部包装完毕,遍历 bodyParts 进行详细编码
首先拼接分隔符,拼接固定格式头信息,然后通过 stream 读取具体
!
值,通过data 传进,调用 URLSession 响应的方法,
通过 SessionDelegate 接受上传代理 - 最后下发给UploadTaskDelegate 最终返回上传情况
后面跟Request原理差不多 也是执行perform(request)
.responseString { response in ///响应结果
HTTPS
1、认证服务器:第一阶段服务器会提供经CA机构认证颁发的服务器证书,如果认证该服务器证书的CA机构,存在于浏览器的受信任CA机构列表中,并且服务器证书中的信息与当前正在访问的网站(域名等)一致,那么浏览器就认为服务端是可信的,并从服务器证书中取得服务器公钥,用于后续流程
2、协商会话密钥。客户端在认证完服务器,获得服务器的公钥之后,利用该公钥与服务器进行加密通信,协商出两个会话密钥,分别是用于加密客户端往服务端发送数据的客户端会话密钥,用于加密服务端往客户端发送数据的服务端会话密钥。
3、加密通讯
默认情况下,Alamofire会收到与URLSession相同的自动TLS证书和证书链,同默认情况下,Alamofire会自动帮我们处理身份验证
证书编写: 可以多个证书
public func trustSession() -> Session {///PinnedCertificatesTrustEvaluator 证书默认 Bundle.main.af.certificateslet certificates = AlamofireManager.certitificates()let keys = AlamofireManager.publicKeys()let evaluators: [String: ServerTrustEvaluating] = ["cert.xxxxx.com": PinnedCertificatesTrustEvaluator(certificates: certificates),"keys.xxxxx.com": PublicKeysTrustEvaluator(keys:keys),"any": ByIpTrustEvaluator()]let manager = ServerTrustManager(evaluators: evaluators)return Session(serverTrustManager: manager)}
/// 遍历整个工程bundle获取证书public static func certitificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {var certificates: [SecCertificate] = []let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension inbundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)}.joined())for path in paths {if let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,let certificate = SecCertificateCreateWithData(nil, certificateData) {certificates.append(certificate)}}return certificates}/// 获取公钥keypublic static func publicKeys(in bundle: Bundle = Bundle.main) -> [SecKey] {var publicKeys: [SecKey] = []// 把证书拿到for certificate in certitificates(in: bundle) {// ios 12以上 iOS10.3 SecCertificateCopyPublicKeyif let publicKey = SecCertificateCopyKey(certificate) {publicKeys.append(publicKey)}}return publicKeys}
验证方法
try evaluator.evaluate(trust, forHost: host)
interceptor 适配器、拦截器、重试器
继承RequestInterceptor
修改request头
func adapt(_ urlRequest: URLRequest,for session: Session,completion: @escaping (Result<URLRequest, Error>) -> Void) {var urlRequest = urlRequestif let token = TokenManager.shared.fetchAccessToken() {urlRequest.setValue("token \(token)", forHTTPHeaderField: "Authorization")}completion(.success(urlRequest))}
修改重试次数
let retryLimit = 5
let retryDelay: TimeInterval = 10func retry(_ request: Request,for session: Session,dueTo error: Error,completion: @escaping (RetryResult) -> Void) {let response = request.task?.response as? HTTPURLResponse//Retry for 5xx status codesiflet statusCode = response?.statusCode,(500...599).contains(statusCode),request.retryCount < retryLimit {completion(.retryWithDelay(retryDelay))} else {return completion(.doNotRetry)}}
eventMonitors 通知事件监测器
继承 EventMonitor
请求完成
func requestDidFinish(_ request: Request) {print(request.description)}
DataRequest成功创建序列化的DataResponse时回调
func request<Value>(_ request: DataRequest,didParseResponse response: DataResponse<Value, AFError>) {guard let data = response.data else {return}if let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {print(json)}}
cachedResponseHandler
缓存数据
/// 带缓存带重试器,事件监听器的Sessionclass func GitInstance() -> Session {let interceptor = GitRequestInterceptor()let configuration = URLSessionConfiguration.defaultlet networkLogger = GitNetworkLogger()/// 请求timeout截止时间为50秒configuration.timeoutIntervalForRequest = 50/// 先修改数据,再缓存新的数据,参数为修改数据的闭包,改闭包返回可选的新缓存数据let responseCacher = ResponseCacher(behavior: .modify { _, response inlet userInfo = ["date": Date()]return CachedURLResponse(/// 与实例关联的URL响应对象。response: response.response,/// 缓存响应的数据data: response.data,/// 缓存响应的用户信息字典userInfo: userInfo,/// 硬盘和内存没有限制storagePolicy: .allowed)})return Session(configuration: configuration,interceptor: interceptor,cachedResponseHandler: responseCacher,eventMonitors: [networkLogger])}
// 用request的缓存处理器处理缓存handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler)
如果是缓存数据.cache
直接返回 completion(response)
Alamofire源码分析相关推荐
- Alamofire源码分析 - POST请求
本文基于Alamofire v5.6.1,主要通过一个POST请求看看Alamofire源码是如何实现该过程的.请求代码如下: let url = "xxxxxxxxxxxxxxxxxxxx ...
- Alamofire源码解读系列(十一)之多表单(MultipartFormData)
本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...
- 开源项目源码分析(Kickstarter-iOS )(一)
开源项目源码分析(Kickstarter-iOS )(一) 1.Kickstarter开源项目简介 2. Kickstarter项目结构 2.1 Makefile 文件 2.2 Git submodu ...
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...
- SpringBoot-web开发(二): 页面和图标定制(源码分析)
[SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...
- SpringBoot-web开发(一): 静态资源的导入(源码分析)
目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...
- Yolov3Yolov4网络结构与源码分析
Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...
- ViewGroup的Touch事件分发(源码分析)
Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...
最新文章
- JAVA String之contains方法使用
- 组合计数 ---- 2020 icpc 上海 The Journey of Geor Autumn(思维划分问题计数+预处理优化)
- python27-资源|全机器学习和Python的27个速查表(完整版)
- TCP 和 UDP 绑定同一端口通信的解释
- 外部定义了一个数组,在函数内用指针对其操作
- php 访问 sharepoint列表,SharePoint—用REST方式访问列表
- (99)FPGA最大延迟与最小延迟基础
- 何使用ultraiso软碟通制作u盘启动盘(转载)
- vue-scroller的使用 开发自己的 scroll 插件
- source insight 使用技巧
- php 判断设备是手机还是平板还是pc
- 为程序界面添加滑动条
- 魅族手机Root之后如何恢复
- 低端电流检测电路实测
- Linux lvm(pv、vg、lv)操作命令收集
- 如何处理多重共线性问题
- 青龙面板之滴滴果园撸水果(2022.6.21更新库)
- ubuntu设置pac代理
- 报错:HTTP 401 Unauthorized
- 六、添加艺术字到图片