浅谈 Swift 中的属性(Property)

2017-01-14 15:49:16来源:http://www.jianshu.com/p/fe60f5bafab3作者:萌面大道人点击

Stored Properties & Computed Properties & Property Observers


Info:
macOS 10.12.1
Xcode 8.1 Beta 3
Swift 3.0

前言

Swift 中的属性分为存储属性与计算属性,存储属性即为我们平时常用的属性,可以直接赋值使用,而计算属性不直接存储值,而是根据其他(存储)属性计算得来的值。


在其他面向对象的编程语言中,例如 Java 和 Objective-C 中,get 和 set 方法提供了统一、规范的接口,可以使得外部访问或设置对象的私有属性,而不破坏封装性,也可以很好的控制权限(选择性实现 get 或 set 方法)。而 Swift 中似乎并没有见到类似的 get 和 set 方法,而 Swift 使用了一种名为属性观察器的概念来解决该问题。


本文简单介绍下 Swift 中的这两种属性,以及属性观察器。


延迟存储属性

存储属性使用广泛,即是类或结构体中的变量或常量,可以直接赋初始值,也可以修改其初始值(仅指变量)。延迟存储属性是指第一次使用到该变量再进行运算(这里的运算不能依赖其他属性)。延迟存储属性必须声明为 var 变量,因为其属性值在对象实例化前可能无法得到,而常量必须在初始化完成前拥有初始值。


在 Swift 中,可以将消耗性能才能得到的值的初始化放在延迟存储属性中,即懒加载。


Demo

这里假定在 ViewController.swift 有一个属性,需要从 plist 文件读取内容,将其中的字典转为模型。如果 plist 文件内容很多,那么就十分消耗性能。如果用户不触发相应事件,也没有必要加载这些数据。那么这里就很适合使用懒加载,即延迟存储属性。


ViewController.swift


class ViewController: UIViewController {
lazy var goods: NSArray? = {
var goodsArray: NSMutableArray = []
if let path = Bundle.main.path(forResource: "Goods", ofType: "plist") {
if let array = NSArray(contentsOfFile: path) {
for goodsDict in array {
goodsArray.add(Goods(goodsDict as! NSDictionary))
}
return goodsArray
}
}
return nil
}()
// 这样也是允许的
lazy var testLazy = Person()
}
class Person {}

可以在延迟存储属性运算的代码中加入 print(),即可验证其何时初始化。


计算属性

举个例子,一个矩形结构体(类同理),拥有宽度和高度两个存储属性,以及一个只读面积的计算属性,因为通过设置矩形的宽度和高度即可计算出矩形的面积,而无需直接设置其值。当宽度或高度改变,面积也应当可以跟随其变化(反之不能推算,因此为只读)。为说明 setter 以及便捷 setter 说明,另外添加了原点(矩形左下角)存储属性,以及中心计算属性。


Demo
struct Point {
var x = 0.0
var y = 0.0
}
struct Rectangle {
var width = 0.0
var height = 0.0
var origin = Point()
// 只读计算属性
var size: Double {
get {
return width * height
}
}
// 只读计算属性简写为
// var size: Double {
// return width * height
// }
var center: Point {
get {
return Point(x: origin.x + width / 2,
y: origin.y + height / 2)
}
set(newCenter) {
origin.x = newCenter.x - width / 2
origin.y = newCenter.y - height / 2
}
// 便捷 setter 声明
// set {
// origin.x = newValue.x - width / 2
// origin.y = newValue.y - height / 2
// }
}
}
var rect = Rectangle()
rect.width = 100
rect.height = 50
print(rect.size)
rect.origin = Point(x: 0, y: 0)
print(rect.center)
rect.center = Point(x: 100, y: 100)
print(rect.origin)
// 5000.0
// Point(x: 50.0, y: 25.0)
// Point(x: 50.0, y: 75.0)

综上,getter 可以根据存储属性推算计算属性的值,setter 可以在被赋值时根据新值倒推存储属性,但它们与我们在其他语言中的 get/set 方法却不一样。


属性观察器

属性观察器算是 Swift 中的一个 feature,变量在设值前会先进入 willSet,这时默认 newValue 等于即将要赋值的值,而变量本身尚未改变。变量在设值后会先进入 didSet,这时默认 oldValue 等于赋值前变量的值,而变量变为新值。


这样,开发者即可在 willSetdidSet 中进行相应的操作,如果只是取值和设值而不进行额外操作,那么直接使用点语法即可。但是有时候一个变量只需要被访问,而不能在外界赋值,那么可以使用访问控制修饰符加上 (set) 即可私有化 set 方法。例如 fileprivate(set)private(set),以及 internal(set)。值得注意的是,这里的访问控制修饰符修饰的是 set 方法,访问权限(即 get)是另外设置的。例如 public fileprivate(set) var prop = 0,该变量全局可以访问,但只有同文件内可以使用 set 方法。


Demo
struct Animal {
// internal 为默认权限,可不加
internal private(set) var privateSetProp = 0
var hungryValue = 0 {
// 设置前调用
willSet {
print("willSet /(hungryValue) newValue: /(newValue)")
}
// 设置后调用
didSet {
print("didSet /(hungryValue) oldValue: /(oldValue)")
}
// 也可以自己命名默认的 newValue/oldValue
// willSet(new) {}
// didSet(old) {}
}
}
var cat = Animal()
// private(set) 即只读
// cat.privateSetProp = 10
print(cat.privateSetProp)
cat.hungryValue += 10
print(cat.hungryValue)
// 0
// willSet 0 newValue: 10
// didSet 10 oldValue: 0
// 10

总结

Swift 的这几个 feature 我未曾在其他语言中见过,对于初学者确实容易凌乱。特别是 getter/setter 以及属性观察器中均没有代码提示,容易造成手误,代码似乎也变得臃肿。但是熟悉之后,这些也都能完成之前的功能,甚至更加细分。保持每一部分可控,便使得整个程序更加严谨,更加安全。


参考资料

浅谈 Swift 3 中的访问控制
Access Control
Properties




最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台