Alamofire(4.0.0是基于Swift3.0语法)是Swift中最常用的网络框架。
1. AFError
Swift中的枚举比较特殊,可以在枚举中定义方法,但是不能定义变量。
enum CompassPoint{
case North
case Sourth
case East
case West
//枚举中 可以定义方法
func show(){
print(self)
}
}
// 定义枚举变量
var p = CompassPoint.North
// 类型标注之后 可以使用点来获取枚举值
var p2 : CompassPoint = .Sourth
p.show()
p2.show()
在Alamofire中的枚举更加有个性,可以在枚举中定义枚举,并且枚举的case可以传递参数。异常类型可以直接通过throw抛出。
public enum AFError: Error {
public enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}
public enum MultipartEncodingFailureReason {
...
}
public enum ResponseValidationFailureReason {
...
}
public enum ResponseSerializationFailureReason {
...
}
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}
Swift中的枚举非常强大,可以对枚举进行扩展,在扩展中可以定义属性。如下代码所示,Alamofire中可以在AFError的扩展中定义方法来判断当前的错误属于哪一类错误。
extension AFError {
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
......
public var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
由于扩展可以定义多个,所以便可以将一类功能归类到同一个扩展中,如Alamofire中定义了一个便捷属性扩展。
// MARK: - Convenience Properties
extension AFError {
public var urlConvertible: URLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
......
public var failedStringEncoding: String.Encoding? {
switch self {
case .responseSerializationFailed(let reason):
return reason.failedStringEncoding
default:
return nil
}
}
}
Alamofire中枚举使用的最牛逼的就是为AFError内部枚举添加相应扩展。
extension AFError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
return error
default:
return nil
}
}
}
......
然后最上层直接调用underlyingError得到error
- Summary
1.枚举添加访问修饰符,并且可以实现协议。比如。public enum AFError: Error。这里的Error其实是一个协议public protocol Error。
2.枚举内部可以再定义枚举。相当于声明枚举,后面还是通过case的方式使用。并且可以传递参数。
3.通过扩展给枚举挺添加便捷属性。
4.按照不同功能给扩展分组。让代码更便于阅读。
2. Notifications
定义的通知是一件比较简单的事情。一般情况下,在OC中我们会直接定义一个字符串来表示某种通知。通常情况下也没怎么把通知管理起来。
在Alamofire中定义的通知就感觉很正式了。首先是成了一个扩展的形式,把相关的通知都写在里面。然后使用结构体来包装通知。代码如下:
extension Notification.Name {
public struct Task {
public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
}
}
- 注意定义的都是静态常量
相当于扩展了系统通知name。把通知名称都定义在里面。然后通过不同的结构体定义不同用途的通知。其实在OC中也可以这样做。只是平时很少这样写,这样写之后代码组织就更加优雅了。
Swift中Notification的API如下:
public struct Notification : ReferenceConvertible, Equatable, Hashable {
public typealias ReferenceType = NSNotification
public var name: Notification.Name
public var object: Any?
public var userInfo: [AnyHashable : Any]?
public init(name: Notification.Name, object: Any? = default, userInfo: [AnyHashable : Any]? = default)
public var hashValue: Int { get }
public var description: String { get }
public var debugDescription: String { get }
public typealias Name = NSNotification.Name
public static func ==(lhs: Notification, rhs: Notification) -> Bool
}
- Summary :
1.通过扩展Notification.Name来定义通知名称。让代码组织更加优雅。
2.使用结构体来区分不同功能的通知。在结构体下定义静态常量定义通知名称。
3. ParameterEncoding
详细代码见ParameterEncoding.swift类。
(1)枚举继承的类就是case所对应的类型。比如
public enum HTTPMethod: String {
case options = "OPTIONS"
case get = "GET"
case head = "HEAD"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
}
(2)typealias可以给一个类型取一个别名
public typealias Parameters = [String: Any]
Parameters就是一个字典,key为字符串,value可以是任意类型
4. Request
Request.swift这个类中有三个协议(RequestAdapter、RequestRetrier、TaskConvertible)、五个类、六个扩展。
四个类及之间的继承关系如下:
open class Request
open class DataRequest: Request
open class DownloadRequest: Request
open class UploadRequest: DataRequest
open class StreamRequest: Request
一开始先定义一套协议,和类型别名(类型别名)。如下:
public protocol RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}
// 类似于block
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
public protocol RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
protocol TaskConvertible {
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
}
defer关键字:表示在执行完方法最后的时候调用。比如文件打开后最后需要关闭。
internal(set):表示set方法只有在内部模块才能访问。get方法是都能访问的
open internal(set) var delegate: TaskDelegate {
get {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
return taskDelegate
}
set {
taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
taskDelegate = newValue
}
}
Swift 3.0 中方法的返回值必须有接收否则会报警告,当然其实主要目的是为了避免开发人员忘记接收返回值的情况,但是有些情况下确实不需要使用返回值可以使用"_"接收来忽略返回值。当然你也可以增加@discardableResult声明,告诉编译器此方法可以不用接收返回值。如Request.Swift类的第187行:
@discardableResult
open func authenticate(usingCredential credential: URLCredential) -> Self {
delegate.credential = credential
return self
}
@noescape: 用来标记一个闭包, 用法如下func hostFunc(@noescape closure: () -> ()) -> Void
@noescape字面意思是无法逃脱. closure 被@noescape修饰, 则声明 closure 的生命周期不能超过 hostFunc, 并且, closure不能被hostFunc中的其他闭包捕获(也就是强持有)
func hostFunc(@noescape closure: () -> ()) -> Void {
//以下编译出错, closure 被修饰后, 不能被其他异步线程捕获
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
closure()
}
}
5. Response
5.1 结构体
在OC中,我们在写代码的时候很少用到结构体。但是在Swift的中,结构体出现的频率相当高。在文件Response.swift中没有定义一个类,全是结构体。
先来回顾一下Swift中结构体和类的关系:
a.都可以有属性和方法;
b.都有构造器;
c.都支持附属脚本;
d.都支持扩展;
e.都支持协议。
然后我们来看看他们的不同之处:
a.类有继承;
b.结构体有一个自动生成的逐一初始化构造器;
c.在做赋值操作时,结构体总是被拷贝(Array有特殊处理);
d.结构体可以声明静态的属性和方法;
e.从设计模式的角度来分析,类的设计更侧重于对功能的封装,而结构体的设计更侧重于对数据的封装。
类的设计更侧重于对功能的封装,而结构体的设计更侧重于对数据的封装。为了便于代码组织,一般在结构体的扩展里面添加方法,比如在Response.swift中:
public struct DefaultDataResponse {
public let request: URLRequest?
public let response: HTTPURLResponse?
public let data: Data?
public let error: Error?
public let timeline: Timeline
var _metrics: AnyObject?
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?,
timeline: Timeline = Timeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}
}
结构体DefaultDataResponse
完全满足对数据的封装,当然这里用类来封装这些数据其实也可以,但是就感觉没有那么完美。
同样在文件中出现的DataResponse
也是结构体。然后通过扩展给结构体添加方法,或者应该算是属性。
5.2 Response协议
protocol Response {
var _metrics: AnyObject? { get set }
mutating func add(_ metrics: AnyObject?)
}
mutating:修饰方法是为了能在该方法中修改struct
或是 enum
的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。所以要多考虑使用mutating
来修饰方法。如果将Response中修饰方法的mutating去掉,编译器会报错说没有实现protocol。如果将struct中的mutating去掉,则会报错不能改变结构体的成员。
通过这样定义之后,就可以让结构体实现这个协议,然后修改结构体里面的变量了。
6. Result
这个类是一个泛型枚举,通过对结果的封装可以直接获取到更加详细的信息。来看代码:
public enum Result<Value> {
case success(Value)
case failure(Error)
// 对结果信息进一步处理,可以马上返回成功或者失败。
public var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
public var isFailure: Bool {
return !isSuccess
}
/// 对结果信息进一步处理,还可以直接返回成功的值。
public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
- CustomStringConvertible,CustomDebugStringConvertible接口:这两个接口都是自定义输出的。之前如果要达到同样的效果就重写toString。
7. SessionDelegate
SessionDelegate看名字以为它是一个代理,其实它是一个类,前面说过类一般是对功能的封装。这个类的作用是用闭包(也就是OC中的block)来替代系统中的代理回调。大致分三个部分:
(1)声明替代系统代理回调方法的闭包
注意这里定的闭包在下面的扩展里面将对系统的代理进行包装一次,然后外面通过定义的闭包使用。
(2)定义需要的属性及方法。比如lock,sessionManager.
var retrier: RequestRetrier?
weak var sessionManager: SessionManager?
private var requests: [Int: Request] = [:]
private let lock = NSLock()
/// Access the task delegate for the specified task in a thread-safe manner.
open subscript(task: URLSessionTask) -> Request? {
get {
lock.lock() ; defer { lock.unlock() }
return requests[task.taskIdentifier]
}
set {
lock.lock() ; defer { lock.unlock() }
requests[task.taskIdentifier] = newValue
}
}
(3)在类的扩展里面实现系统代理,实现自定义闭包代替系统回调代理。
这部分比较简单,基本思路是,实现代理,在代理方法中调用定义好的闭包,传递参数。通过对系统的代理方法包装一层,然后外部通过定义的闭包来调用。OC中的第三方BlockKit的底层原理和此类似。
8. SessionManager
这个类非常重要,包含以下8个方面:
(1)调用结果枚举定义。Helper
这个类似于在OC中定义成功回调和失败回调。只是现在把回调放到了枚举里面,这样更加合理。这种方式得益于case可以传递参数。
public enum MultipartFormDataEncodingResult {
case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
case failure(Error)
}
调用方式:
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
DispatchQueue.main.async { encodingCompletion?(encodingResult) }
(2)属性定义。Properties
属性分为:计算属性、存储属性、静态属性和实例属性
静态计算属性:
open static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
属性的get/set方法
open var retrier: RequestRetrier? {
get { return delegate.retrier }
set { delegate.retrier = newValue }
}
这种方式类似于OC中重写属性的get/set方法。最常见的将model改变和UI绑定在一起。
(3)生命周期。Lifecycle
Swift中属性在初始化之后必须有值。
关于初始方法,Swift3中有init 和 init?,前者代码一定会走的,后者代表可能会走的初始化方法。
(4)数据请求。Data Request
这部分在定义方法上可以学习一下,具体的内容就是先定义一个最为基础的方法,参数比较多但是一定要有默认值,然后后续的方法在参数上做减法,最终都是调用最为基本的方法
(5)下载请求。Download Request
(6)上传请求。Upload Request
(7)流式请求。Stream Request
(8)重试。Retry Request