二、详解Alamofire(4.0.0)

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)声明替代系统代理回调方法的闭包

注意这里定的闭包在下面的扩展里面将对系统的代理进行包装一次,然后外面通过定义的闭包使用。

2017-06-15 下午3.22.57.png

(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的底层原理和此类似。

2017-06-15 下午3.41.01.png

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

9. TaskDelegate

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容