跟着Alamofire(4.0.0)学Swift3(一)

最近一段时间搞得东西太多了。服务端Spring,Android入门。但是自己的老本行一直没有好好的整理过。加上现在Swift3已经出来了一段时间了。还是不能忘了老本行,为了顺应潮流前前后后看了不少关于Swift的。还是觉得要在成熟的项目中才能学到更多的东西。所以选择了Alamofire这个库作为学习材料。

文中难免有错,不喜勿喷!

跟着Alamofire(4.0.0)学Swift3(二)

枚举定义(AFError异常类型)

枚举感觉更像一个类。感觉失去当初熟悉的枚举的影子。四不像,可以定义方法,但是不能定义变量

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)
    }

   ...
   
   // 这才是真正的case
    case invalidURL(url: URLConvertible)
    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
    case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
    case responseValidationFailed(reason: ResponseValidationFailureReason)
    case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}

枚举可以扩展。并且可以在扩展里面给枚举添加属性值,通过判断当前枚举,返回相应的内容

extension AFError {
    /// Returns whether the AFError is an invalid URL error.
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }

...
    /// `underlyingError` properties will contain the associated values.
    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false
    }
}

这里就是判断当前的错误是属于哪一种类型的错误。除此之外,由于扩展可以定义多个,那么就可以对某一类功能归类到统一扩展中。比如其中就定义了一个便捷属性的扩展。

extension AFError {
    /// The `URLConvertible` associated with the error.
    public var urlConvertible: URLConvertible? {
        switch self {
        case .invalidURL(let url):
            return url
        default:
            return nil
        }
    }

   ...

    /// The `String.Encoding` associated with a failed `.stringResponse()` call.
    public var failedStringEncoding: String.Encoding? {
        switch self {
        case .responseSerializationFailed(let reason):
            return reason.failedStringEncoding
        default:
            return nil
        }
    }
}

当然除了给AFError添加扩展之外,还为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的方式使用。并且可以传递参数

    public enum ParameterEncodingFailureReason {
        case missingURL
        case jsonEncodingFailed(error: Error)
        case propertyListEncodingFailed(error: Error)
    }
    ...
    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
    
  • 3.通过扩展给枚举挺添加便捷属性。

    extension AFError {
    /// Returns whether the AFError is an invalid URL error.
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }
    ...
    
  • 4.按照不同功能给扩展分组。让代码更便于阅读。比如:
    MARK: - Convenience Properties

    // MARK: - Convenience Properties
    

extension AFError {
/// The URLConvertible associated with the error.
public var urlConvertible: URLConvertible? {
switch self {
case .invalidURL(let url):
return url
default:
return nil
}
}
```
MARK: - Error Descriptions

```
// MARK: - Error Descriptions
extension AFError: LocalizedError {
public var errorDescription: String? {


    switch self {
    case .invalidURL(let url):
        return "URL is not valid: \(url)"
    case .parameterEncodingFailed(let reason):
        return reason.localizedDescription
    case .multipartEncodingFailed(let reason):
        return reason.localizedDescription
    case .responseValidationFailed(let reason):
        return reason.localizedDescription
    case .responseSerializationFailed(let reason):
        return reason.localizedDescription
    }
    

}
```

通知定义(Notifications)

定义的通知是一件比较简单的事情。一般情况下,在OC中我们会直接定义一个字符串来表示某种通知。通常情况下也没怎么把通知管理起来。比如:NSString *NTESNotificationLogout = @"NTESNotificationLogout";

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中也可以这样做。只是平时很少这样写,这样写之后代码组织就更加优雅了。

OC中也有这样的属性。

@interface NSNotification : NSObject <NSCopying, NSCoding>

@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;

Swift中是

open class NSNotification : NSObject, NSCopying, NSCoding {
    open var name: NSNotification.Name { get }
    open var object: Any? { get }
    open var userInfo: [AnyHashable : Any]? { get }

使用的地方写法:

 NotificationCenter.default.post(
            name: Notification.Name.Task.DidResume,
            object: self,
            userInfo: [Notification.Key.Task: task]
        )

Summary

  • 1.通过扩展Notification.Name来定义通知名称。让代码组织更加优雅。
  • 2.使用结构体来区分不同功能的通知。在结构体下定义静态常量定义通知名称。

参数编码(ParameterEncoding)

再看枚举定义

枚举继承的类就是case所对应的类型呢。比如:

public enum HTTPMethod: String {
    case options = "OPTIONS"
    ...
    case connect = "CONNECT"
}

HTTPMethod继承自String,表示case所定义的就是字符串类型。如果改为int。就会出现:

这里的String。其实就是rawType

typealias

通过typealias就是指给一个类型取一个别名。比如public typealias Parameters = [String: Any]Parameters就是一个字典,key为字符串,value可以是任意类型

throws

Swift 2中所有的同步 Cocoa APINSError 都已经被 throw 关键字取代。这就很尴尬了,关于这个的用法可以参考这里Swift 2 throws 全解析 - 从原理到实践当然最好的还是直接看苹果的文档Page
Error Handling

结构体实现协议

  • 方法默认值:

    public init(destination: Destination = .methodDependent) {
        self.destination = destination
    }
    

    这里默认参数为枚举methodDependent

  • 快速创建当前结构体。这个有点类似于通过静态方法创建类的概念。只不过把创建的过程直接放在了声明属性的后面。比如:

    /// Returns a default `URLEncoding` instance.
    public static var `default`: URLEncoding { return URLEncoding() }
    ...
    /// Returns a `URLEncoding` instance with an `.httpBody` destination.
    public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }
    

    这样的方式在项目中用得还不较多。在声明变量的时候就赋值好。

  • 方法参数

    通过_来省略外部参数。内部参数和外部参数一样,则只用声明种就行了。如public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?)

异常

看一段函数

 public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        var urlRequest = try urlRequest.asURLRequest()

        guard let parameters = parameters else { return urlRequest }

        if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
            guard let url = urlRequest.url else {
                throw AFError.parameterEncodingFailed(reason: .missingURL)
            }

            if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
                let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
            }
        } else {
            if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
                urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
            }

            urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
        }

        return urlRequest
    }

try的使用。如果方法有throws。那就就可以通过在调用的时候加上try来捕获异常。有一种场景比如不处理异常,我非常确定某个方法或者函数虽然声明会抛出异常,但是我自己知道我在使用时候是绝对不会抛出任何异常的。这种情况下 我们可以使用 try!。try! functionThrowErrorNil()

??问号,表示如果前面表达式为空,则就用后面的作为返回值。比如HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET")

if let和 guard else的使用。抛出异常直接就用throw抛出异常。千万要记住,if 后面不一定只是let。还有跟var。其本质就是变量和常量而已。比如上面代码中的if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET")if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty

其他

  • Any和AnyObject:详细介绍可以参考ANY 和 ANYOBJECT

    • AnyObject 可以代表任何 class 类型的实例
    • Any 可以表示任意类型,甚至包括方法 (func) 类型
  • 数组定义及初始化:var components: [(String, String)] = []

  • do catch的使用。和其他语言不同,没有用try.

    do {
    try functionWillThrowError()
    } catch {
    // deal with error
    }
    

    catch 和 switch 一样具有 Pattern Matching 的能力。所以,使用 catch 你可以对异常的解析进行更为高级的处理。比如:

    do {
    try functionWillThrowError()
     } catch MyError.NotExist {
    // deal with not exist
     } catch MyError.OutOfRange {
    // deal with not exist
    }
    
  • try。try?会将错误转换为可选值,当调用try?+函数或方法语句时候,如果函数或方法抛出错误,程序不会发崩溃,而返回一个nil,如果没有抛出错误则返回可选值。使用try!可以打破错误传播链条。错误抛出后传播给它的调用者,这样就形成了一个传播链条,但有的时候确实不想让错误传播下去,可以使用try!语句

    先来看看代码。

    do {
    let content = try NSString(contentsOfFile: "/file/path/str.txt", encoding: NSUTF8StringEncoding)
    

} catch {
print("read content fail")
}
```
里的 try 关键字是写在具体调用代码行上面的。也就是说,那个语句会有可能抛出异常,我们才在哪个语句前面加上 try 关键字。这种方式有一个好处。就是我们可以一目了然的看到那些代码会抛出异常。而不是将所有代码都混在 try-catch 语句块中。

结果(Result)

这个类是一个泛型枚举。一开始我还在想结果不就成或者失败没。为什么还要高这么多。通过对结果的封装(Swift的枚举相当强大)可以直接获取到更加详细的信息。来看代码:

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
        }
    }
}

Swift的枚举比较高级的用法如上。

  • CustomStringConvertible,CustomDebugStringConvertible接口:这两个接口都是自定义输出的。之前如果要达到同样的效果就重写toString。现在还多了一种。注意这两个知识协议。还有一点就是要善于使用扩展extension由于组织代码。比如下面代码就用扩展来实现了接口。之后这个类就有这个接口的功能。
extension Result: CustomStringConvertible {
    /// The textual representation used when written to an output stream, which includes whether the result was a
    /// success or failure.
    public var description: String {
        switch self {
        case .success:
            return "SUCCESS"
        case .failure:
            return "FAILURE"
        }
    }
}

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
}

名字取得好就是好。见名知意

  • 闭包声明:public typealias ProgressHandler = (Progress) -> Void

  • defer关键字:表示在执行完方法最后的时候调用。比如文件打开后最后需要关闭。

  • internal(set),这种写法还比较少见。具体如下:

    open internal(set) var delegate: TaskDelegate {
        get {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            return taskDelegate
        }
        set {
            taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
            taskDelegate = newValue
        }
    }
    

    表示set方法只有在内部模块才能访问。get方法是都能访问的。

  • @discardableResult:Swift 3.0 中方法的返回值必须有接收否则会报警告,当然其实主要目的是为了避免开发人员忘记接收返回值的情况,但是有些情况下确实不需要使用返回值可以使用"_"接收来忽略返回值。当然你也可以增加@discardableResult声明,告诉编译器此方法可以不用接收返回值。比如:

@discardableResult
open func authenticate(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
-> Self
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
```

  • @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()
}
}


### 参考

[Swift 2.0初探:值得注意的新特性](http://www.cocoachina.com/swift/20150623/12231.html)

[关于 Swift 2.0 - 语言新特性与革新](http://www.cnblogs.com/theswiftworld/p/swift2.html?utm_source=tuicool&utm_medium=referral)

[Swift 2.0 异常处理](//www.greatytc.com/p/96a7db3fde00)

[Swift 3那些不同以往的特性](//www.greatytc.com/p/5d911fae5b2f)

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

推荐阅读更多精彩内容