Swift 中闭包与属性的一点探索

811 阅读2分钟

闭包和属性是 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),这样我们将每一个属性的名称和类型都声明在这个元组中。

在赋值给这个属性的闭包中,初始化了 preferredLanguagesshowPhoto 属性,并且将他们声称一个元组类型返回给 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(nil), transform, false) {
                    return mutableUserAgent as String
                }
            }

            return "Alamofire"
        }()

        return [
            "Accept-Encoding": acceptEncoding,
            "Accept-Language": acceptLanguage,
            "User-Agent": userAgent
        ]
    }()
    
}

它的 defaultHTTPHeaders 属性以及 sharedInstance 单例属性都是通过闭包字面量调用的方式定义出来。