闭包和属性是 Swift 中的两个特性。闭包在 Swift 的应用非常灵活,使用得当的话,可以大大提升开发的效率以及代码的健壮性。
假如我们有这样一个结构:
struct Profile {
func getPreferredLanguages() -> String {
return NSLocale.preferredLanguages().reduce(""){ result, item in
return result + item
}
}
}
var profile = Profile()
print(profile.getPreferredLanguages())
Profile 代表用户信息, getPreferredLanguages 方法会返回当前用户设备的语言偏好。我们在这个方法中通过 reduce 方法,将 NSLocale.preferredLanguages() 返回的数组中所有的语言编码连接成一个字符串后返回。
如果想让这个表达的更清晰些,其实我们不用定义方法,用一个属性来表示会更加合适:
struct Profile {
let preferredLanguages : String = {
return NSLocale.preferredLanguages().reduce(""){ result, item in
return result + item
}
}()
}
这次我们将之前定义的 getPreferredLanguages 方法改成了 preferredLanguages 属性。这样我们调用起来表达就会更加清晰:
print(profile.preferredLanguages)
这里我们的 preferredLanguages 是一个 String 类型的属性,我们用一个闭包对它进行了初始化,将闭包的返回值作为它的初始值。这个机制在我们构建比较复杂的属性时会更加体现出它的好处。
假如我们又定义了一个 showPhoto 属性,这个属性值从本地存储中读取出来,代表是否显示用户头像,那么我们就不需要再为这个属性单独定义一个方法,我们可以使用闭包的这种机制:
struct Profile {
let settings: (preferredLanguage: String, showPhoto: Bool) = {
let preferredLanguages : String = {
return NSLocale.preferredLanguages().reduce(""){ result, item in
return result + item
}
}()
let showPhoto = NSUserDefaults.standardUserDefaults().boolForKey("showPhoto")
return (preferredLanguages, showPhoto)
}()
}
这次我们声明了一个 settings 属性,它的类型是一个元组(Tuple),这样我们将每一个属性的名称和类型都声明在这个元组中。
在赋值给这个属性的闭包中,初始化了 preferredLanguages
和 showPhoto
属性,并且将他们声称一个元组类型返回给 settings 属性。
这样,我们不需要对每一个属性都声明一个 getter 方法,并且我们还可以用更加结构化的方式引用这些属性:
var profile = Profile()
print(profile.settings.preferredLanguage)
print(profile.settings.showPhoto)
即便我们的这些属性都不是常量值,而是需要进行某些运算获取出来的。但我们在引用这些属性的时候完全将这些细节隐藏了起来。对于调用方,这需要将他们当做属性引用即可。
这个机制在知名的网络库 Alamofire 也有过应用,下面是 Alamofire 中的一段代码:
public class Manager {
public static let sharedInstance: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
return Manager(configuration: configuration)
}()
public static let defaultHTTPHeaders: [String: String] = {
// Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
let acceptEncoding: String = "gzip;q=1.0,compress;q=0.5"
// Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
let acceptLanguage: String = {
var components: [String] = []
for (index, languageCode) in (NSLocale.preferredLanguages() as [String]).enumerate() {
let q = 1.0 - (Double(index) * 0.1)
components.append("/(languageCode);q=/(q)")
if q <= 0.5 {
break
}
}
return components.joinWithSeparator(",")
}()
// User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
let userAgent: String = {
if let info = NSBundle.mainBundle().infoDictionary {
let executable: AnyObject = info[kCFBundleExecutableKey as String] ?? "Unknown"
let bundle: AnyObject = info[kCFBundleIdentifierKey as String] ?? "Unknown"
let version: AnyObject = info[kCFBundleVersionKey as String] ?? "Unknown"
let os: AnyObject = NSProcessInfo.processInfo().operatingSystemVersionString ?? "Unknown"
var mutableUserAgent = NSMutableString(string: "/(executable)//(bundle) (/(version); OS /(os))") as CFMutableString
let transform = NSString(string: "Any-Latin; Latin-ASCII; [:^ASCII:] Remove") as CFString
if CFStringTransform(mutableUserAgent, UnsafeMutablePointer<CFRange>(nil), transform, false) {
return mutableUserAgent as String
}
}
return "Alamofire"
}()
return [
"Accept-Encoding": acceptEncoding,
"Accept-Language": acceptLanguage,
"User-Agent": userAgent
]
}()
}
它的 defaultHTTPHeaders 属性以及 sharedInstance 单例属性都是通过闭包字面量调用的方式定义出来。