淺談 Swift 中的屬性(Property
Properties in Swift
Date | Notes | Swift | Xcode |
---|---|---|---|
2017-04-27 | 擴充 #延遲儲存屬性# 部分並新增 #devxoul/Then# | 3.1 | 8.3.2 |
2016-10-26 | 首次提交 | 3.0 | 8.1 Beta 3 |
前言
Swift 中的屬性分為儲存屬性與計算屬性,儲存屬性即為我們平時常用的屬性,可以直接賦值使用,而計算屬性不直接儲存值,而是根據其他(儲存)屬性計算得來的值。
在其他物件導向的程式語言中,例如 Java 和 Objective-C 中,get 和 set 方法提供了統一、規範的介面,可以使得外部訪問或設定物件的私有屬性,而不破壞封裝性,也可以很好的控制許可權(選擇性實現 get 或 set 方法)。而 Swift 中似乎並沒有見到類似的 get 和 set 方法,而 Swift 使用了一種名為屬性觀察器的概念來解決該問題。
本文簡單介紹下 Swift 中的這兩種屬性,以及屬性觀察器。
延遲儲存屬性
儲存屬性使用廣泛,即是類或結構體中的變數或常量,可以直接賦初始值,也可以修改其初始值(僅指變數)。
延遲儲存屬性是指第一次使用到該變數再進行運算(這裡的運算不能依賴其他成員屬性,經 評論提示,延遲儲存屬性可以使用成員屬性,只是需要在閉包中明確指出其持有物件本身,即加上
self
;也可以直接使用靜態/類屬性)。延遲儲存屬性必須宣告為
var
變數,因為其屬性值在物件例項化前可能無法得到,而常量必須在初始化完成前擁有初始值。在 Swift 中,可以將消耗效能才能得到的值作為延遲儲存屬性,即懶載入。
全域性的常量和變數也是延遲儲存屬性,但不需要顯式宣告為 lazy(不支援 Playground)。
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 {}
可以在延遲儲存屬性運算的程式碼中加入列印語句,即可驗證其何時初始化。
Lazy 初始化的「演變」過程
根據上面 Demo,延遲儲存屬性的初始化程式碼部分可能有些讓人迷惑,但其實也是初始化的一步步的演變過程。在 的 中也有描述這個過程,簡單用程式碼表示也如下所示:
struct Person { var name = "" init() { print(#function) } } // 直接初始化let p1 = Person() // 利用閉包let getOnePerson = { () -> Person in let p = Person() return p }let p2 = getOnePerson() // 閉包執行let p3 = { () -> Person in let p = Person() return p }() // 簡化let p4: Person = { let p = Person() return p }()
Lazy 方法
的 Swifter Tips 中也提到,在 Swift 標準庫中,也有一些 Lazy 方法,就可以在不需要執行時,避免消耗太多的效能。
let data = 0...3let result = data.lazy.map { (i: Int) -> Int in print("Handling...") return i * 2} print("Begin:")for i in result { print(i) }// OUTPUT:// Begin:// Handling...// 0// Handling...// 2// Handling...// 4// Handling...// 6
devxoul/Then
在 的中提到了一個 devxoul/Then 庫,為 Swift 的構造方法加入語法糖。
Demo
ViewController.swift
import UIKitimport Thenclass ViewController: UIViewController { lazy var myLabel = UILabel().then { $0.text = "My Label" } override func viewDidLoad() { super.viewDidLoad() myLabel.frame = CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0) view.addSubview(myLabel) } }
Source Code
Then 的核心原始碼部分總共只有不到 20 行,非常簡單易懂。
Then 庫中定義了一個名為 Then 的空協議,之後透過協議擴充套件(Protocol Extension),來為協議新增預設的方法實現。
then()
因為
then()
內部將self
返回,即可在初始化完成後,呼叫該方法,並在閉包中設定屬性,而且不需要再將自身返回。支援 NSObject 子類,也可以將自定義型別(僅支援 AnyObject 型別,即 class)宣告實現該協議即可(協議擴充套件已經擁有預設實現,所以僅宣告實現協議即可)。
extension Then where Self: AnyObject { /// Makes it available to set properties with closures just after initializing. public func then(_ block: (Self) -> Void) -> Self { block(self) return self } }
with()
then()
適用於引用型別,而with()
適用於值型別。使用了
inout
確保方法內外共用一個值型別變數。
extension Then where Self: Any { /// Makes it available to set properties with closures just after initializing and copying the value types. public func with(_ block: (inout Self) -> Void) -> Self { var copy = self block(©) return copy } }
do()
do()
使得可以直接在閉包中簡單地執行一些操作。
extension Then where Self: Any { /// Makes it available to execute something with closures. public func `do`(_ block: (Self) -> Void) { block(self) } }
計算屬性
舉個例子,一個矩形結構體(類同理),擁有寬度和高度兩個儲存屬性,以及一個只讀面積的計算屬性,因為透過設定矩形的寬度和高度即可計算出矩形的面積,而無需直接設定其值。當寬度或高度改變,面積也應當可以跟隨其變化(反之不能推算,因此為只讀)。為說明 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 = 100rect.height = 50print(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
等於賦值前變數的值,而變數變為新值。這樣,開發者即可在
willSet
和didSet
中進行相應的操作,如果只是取值和設值而不進行額外操作,那麼直接使用點語法即可。但是有時候一個變數只需要被訪問,而不能在外界賦值,那麼可以使用加上(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 = 10print(cat.privateSetProp) cat.hungryValue += 10print(cat.hungryValue)// 0// willSet 0 newValue: 10// didSet 10 oldValue: 0// 10
總結
Swift 的這幾個 feature 我未曾在其他語言中見過,對於初學者確實容易造成凌亂。特別是 getter/setter 以及屬性觀察器中均沒有程式碼提示,容易造成手誤,程式碼似乎也變得臃腫。但是熟悉之後,這些也都能完成之前的功能,甚至更加細分。保持每一部分可控,也使得整個程式更加嚴謹,更加安全。
作者:萌面大道
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3349/viewspace-2820151/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python深入淺出property特性屬性Python
- 理解 Kotlin 中的屬性(property)Kotlin
- Swift 中的屬性Swift
- Property屬性
- 淺談質量屬性
- 淺談Swift中的函式式Swift函式
- 淺談CSS3中display屬性的Flex佈局CSSS3Flex
- python物件屬性管理(2):property管理屬性Python物件
- 淺談WPF之屬性系統
- 淺談Flex佈局的屬性及使用Flex
- 淺談CSS3中display屬性的Flex佈局(轉)CSSS3Flex
- Swift-屬性Swift
- 瞭解下C# 屬性(Property)C#
- Android property屬性許可權新增Android
- PLC結構化文字(ST)——屬性(Property)
- 談談ThreadStatic屬性用法thread
- Swift 擴充套件 Storyboard 屬性Swift套件
- 問題No property 屬性名 found for type 類名
- 這篇文章,我們來談一談Spring中的屬性注入Spring
- 淺談對屬性描述符__get__、__set__、__delete__的理解delete
- 淺談Redis的隱性成本Redis
- 始終使用屬性(Property),而不是欄位(Data Member)
- 淺談JavaScript中的thisJavaScript
- 安卓Property Animator動畫詳解(二)-自定義屬性安卓動畫
- EF Core 索引器屬性(Indexer property)場景及應用索引Index
- 淺談HTML5中canvas中的beginPath()和closePath()的重要性HTMLCanvas
- Python中的屬性Python
- iOS @property 屬性相關的總結iOS
- 淺談react 中的 this 指向React
- 淺談Java中的HashmapJavaHashMap
- 淺談java中的反射Java反射
- 淺談React中的diffReact
- 淺談Docker的安全性支援(上篇)Docker
- 淺談Docker的安全性支援(下篇)Docker
- 淺談HK伺服器租用中機房的重要性伺服器
- 屬性font-family:Font property font-family does not have generic default
- 以太坊中的全域性屬性
- 淺談OA系統在應用中安全性