本文讨论 Alamofire 中使用到的一些 Swift 惯用编程技巧。
单例
单例模式是一种被广泛运用的设计模式。一个设计正确的单例类必须保证应用的生命周期之内在内存中有且只能有一个该类的实例,这也要求,单例类的实例化方法必须是线程安全的。使用 Swift 有若干种方式可以实现单例类,下面是几种常见的实现方式。
使用带有明显 Objective-C 风格的 dispatch_once
方式:
class Singleton {
public class var sharedInstance: Singleton {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: Singleton? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = Singleton()
}
return Static.instance!
}
private init() {}
}
在类常量中嵌套一个 struct 也可以完成任务(这里的 struct 类似于 C 语言中函数的 static
局部变量):
class Singleton {
public class var sharedInstance: Singleton {
struct Static {
static let instance: Singleton = Singleton()
}
return Static.instance
}
private init() {}
}
实现单例类的最简洁的方式是使用类常量:
class Singleton {
public static let sharedInstance = Singleton();
private init() {}
}
至于类常量的线程安全性,Swift 官方博客上面的一篇文章告诉我们,类常量的初始化工作实际上是在 dispatch_once
线程中完成的:
The lazy initializer for a global variable (also for static members of structs and enums) is run the first time that global is accessed, and is launched as dispatch_once to make sure that the initialization is atomic. This enables a cool way to use dispatch_once in your code: just declare a global variable with an initializer and mark it private.
Alamofire 正是使用了这种方式实现单例类。稍有不同的是,下面的例子使用了一个匿名闭包来初始化类常量,这让我们在实例化 Manager 对象之前可以做一些额外的准备工作:
// Alamofire: Manager.swift
public class Manager {
public static let sharedInstance: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
return Manager(configuration: configuration)
}()
}
枚举
Swift 提供了优雅而且功能强大的枚举类型。在 Swift 的枚举类型中,每一个枚举项均可以携带一个或者一组关联值(Associated Values):
// Alamofire: NetworkReachabilityManager.swift
public enum NetworkReachabilityStatus {
case Unknown
case NotReachable
case Reachable(ConnectionType)
}
另外,Swift 也支持具有原始值(Raw Value)的枚举类型,这种枚举类型继承自其他类:
// Alamofire: Error.swift
public enum Code: Int {
case InputStreamReadFailed = -6000
case OutputStreamWriteFailed = -6001
case ContentTypeValidationFailed = -6002
case StatusCodeValidationFailed = -6003
case DataSerializationFailed = -6004
case StringSerializationFailed = -6005
case JSONSerializationFailed = -6006
case PropertyListSerializationFailed = -6007
}
保镖模式(Bouncer Pattern)
保镖模式的原则是尽早发现错误,然后将其拒之门外。在 guard
关键字的帮助下,我们可以写出线性的、逻辑非常直观的函数。
示例代码:
// Alamofire: MultipartFormData.swift
public func appendBodyPart(fileURL fileURL: NSURL, name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
//============================================================
// Check 1 - is file URL?
//============================================================
guard fileURL.fileURL else {
let failureReason = "The file URL does not point to a file URL: \(fileURL)"
setBodyPartError(code: NSURLErrorBadURL, failureReason: failureReason)
return
}
//============================================================
// Check 2 - is file URL reachable?
//============================================================
var isReachable = true
if #available(OSX 10.10, *) {
isReachable = fileURL.checkPromisedItemIsReachableAndReturnError(nil)
}
guard isReachable else {
setBodyPartError(code: NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)")
return
}
// …
}
guard
关键字是一个极具争议性的关键字。有人认为 guard
关键字是多余的,因为 guard
能做到的事情,if
也可以做到。本文并不打算介入这一争论。
尾式闭包
尾式闭包(trailing closure)特性帮助写出简短易读的代码。当函数的最后一个参数是一个闭包时,我们可以使用下面的语法调用该函数:
func networkRequest(completion: (response: String) -> Void) {
// function body goes here
}
// here's how you call this function with a trailing closure:
networkRequest() { response in
// trailing closure’s body goes here
}
class 与 struct
在 Swift 中,class
和 struct
两种类型之间最大的区别是,class
是引用类型,而 struct
是值类型。下面的两个例子对此做出了说明。
class
的例子:
class PersonClass {
var name: String
init(name: String) {
self.name = name
}
}
let personA = PersonClass(name: "Tom")
let personB = personA
personB.name = "Bob"
print(personA.name) // "Bob"
print(personB.name) // "Bob"
struct
的例子:
struct PersonStruct {
var name: String
init(name: String) {
self.name = name
}
}
let personA = PersonStruct(name: "Tom")
let personB = personA
personB.name = "Bob"
print(personA.name) // "Tom"
print(personB.name) // "Bob"
一般而言,需要处理复杂的数据结构时,使用 class
;需要封装少量简单的数值而且期望在复制操作中进行值拷贝时,使用 struct
。
GCD 与 NSOperation
GCD (Grand Central Dispatch)和 NSOperation 是 Cocoa 框架提供的两种并发编程技术。GCD 在 C 语言层面实现了并发编程的基础设施,NSOperation 则是对 GCD 的上层封装。想要讲清楚这两个概念需要不小的篇幅,本文不打算展开讨论。具体的细节请参考相关技术文档:
(未完)