Swift5.0
穩定版就已經發布了, 感興趣的小夥伴可看個人這篇博客:Swift 5.0新特性更新Swift
的屬性的相關介紹和剖析, 測試環境: Xcode 11.2.1
, Swift 5.1.2
在Swift
中, 嚴格意義上來說屬性能夠分爲兩大類: 實例屬性和類型屬性html
Instance Property
): 只能經過實例去訪問的屬性
Stored Instance Property
): 存儲在市裏的內存中, 每一個實例都只有一份Computed Instance Property
)Type Property
): 只能經過類型去訪問的屬性
Stored Type Property
): 整個程序運行過程當中就只有一分內存(相似全局變量)Computed Type Property
)static
關鍵字定義; 若是是類也能夠經過class
關鍵字定義上面提到Swift
中跟市裏相關的屬性能夠分爲兩大類:存儲屬性和計算屬性swift
Stored Property
)
var
定義),也能夠是常量存儲屬性(用關鍵字let
定義)Computed Property
)
getter
和一個可選的setter
,來間接獲取和設置其餘屬性或變量的值Swift
中存儲屬性能夠是var
修飾的變量, 也能夠是let
修飾的常量init
初始化器裏爲存儲實行設置一個初始值struct Person { // 定義的時候設置初始值 var age: Int = 24 var weight: Int } // 使用init初始化器設置初始值 var person1 = Person(weight: 75) var person2 = Person(age: 25, weight: 80) 複製代碼
MemoryLayout
獲取數據類型佔用的內存大小// Person結構體實際佔用的內存大小 MemoryLayout<Person>.size // 16 // 系統爲Person分配的內存大小 MemoryLayout<Person>.stride // 16 // 內存對其參數 MemoryLayout<Person>.alignment // 8 複製代碼
還有一種使用方式, 輸出結果一致sass
var person = Person(weight: 75) MemoryLayout.size(ofValue: person) MemoryLayout.stride(ofValue: person) MemoryLayout.alignment(ofValue: person) 複製代碼
getter
和一個可選的setter
,來間接獲取和設置其餘屬性或變量的值struct Square { var side: Int var girth: Int { set { side = newValue / 4 } get { return side * 4 } } } // 其中set也可使用下面方式 set(newGirth) { side = newGirth / 4 } 複製代碼
下面咱們先看一下Square
所佔用的內存大小, 這裏方便查看都去掉了print
函數安全
var squ = Square(side: 4) MemoryLayout.size(ofValue: squ) // 8 MemoryLayout.stride(ofValue: squ) // 8 MemoryLayout.alignment(ofValue: squ) // 8 複製代碼
從上面輸出結果能夠看出, Square
只佔用8個內存大小, 也就是一個Int
佔用的內存大小, 若是仍是看不出來, 能夠看一下下面這個bash
struct Square { var girth: Int { get { return 4 } } } // 輸出結果0 print(MemoryLayout<Square>.size) // 0 複製代碼
get、set
方法來取值或賦值set
方法修改其餘相關聯的屬性的值; 若是該計算屬性是隻讀的, 則不須要set
方法, 傳入的新值默認值newValue
, 也能夠自定義get
方法獲取該計算屬性的值, 即便是隻讀的, 計算屬性的值也是可能發生改變的var
, 不能使用let
Always Show Disassembly
, 右斷點時Xcode
就會在運行過程當中自動跳到斷電的彙編代碼中var squ = Square(side: 4) var c = squ.girth // 在此處加上斷點時 複製代碼
上述代碼的執行流程, 經過彙編的方式看, 核心代碼以下所示微信
下面是在iOS模擬器環境下一些彙編經常使用的指令markdown
// 將rax的值賦值給rdi movq %rax, %rdi // 將rbp-0x18這個地址值賦值給rsi leaq -0x18(%rbp), %rsi // 函數跳轉指令 callq 0x100005428 複製代碼
從上圖能夠看到上面代碼對應的彙編代碼, 其核心代碼大概能夠分爲四部分閉包
Square
調用init
初始化器, 即Square
的初始化(詳細彙編代碼可進入callq 0x100001300
中查看)Square
的對象的內存地址賦值給一個全局變量, 即squ
Square
對象裏面girth
計算屬性的getter
方法, 獲取girth
的值girth
的值賦值給一個全局變量如上圖中中斷點位置, 當斷電執行到此處時, 執行
si
命令便可查看getter
函數的的執行過程, 以下圖所示, 其中imulq
是執行乘法指令ide
// 把rdx和rax的相乘的結果在賦值給rax imulq %rdx, %rax 複製代碼
下面再看一下, 計算屬性的賦值操做, 代碼以下函數
var squ = Square(side: 4) squ.girth = 12; print(squ.side) // 3 複製代碼
對應的彙編代碼以下, 執行流程和上面的取值操做相似, 不一樣的是賦值操做最後執行的是girth
的setter
方法
0x1000010c9 <+25>: callq 0x100001300 ; SwiftLanguage.Square.init(side: Swift.Int) -> SwiftLanguage.Square at main.swift:11 0x1000010ce <+30>: leaq 0x6123(%rip), %rsi ; SwiftLanguage.squ : SwiftLanguage.Square 0x1000010d5 <+37>: xorl %ecx, %ecx 0x1000010d7 <+39>: movq %rax, 0x611a(%rip) ; SwiftLanguage.squ : SwiftLanguage.Square 0x1000010de <+46>: movq %rsi, %rdi 0x1000010e1 <+49>: leaq -0x20(%rbp), %rsi 0x1000010e5 <+53>: movl $0x21, %edx 0x1000010ea <+58>: callq 0x10000540a ; symbol stub for: swift_beginAccess 0x1000010ef <+63>: movl $0xc, %edi 0x1000010f4 <+68>: leaq 0x60fd(%rip), %r13 ; SwiftLanguage.squ : SwiftLanguage.Square 0x1000010fb <+75>: callq 0x100001200 ; SwiftLanguage.Square.girth.setter : Swift.Int at main.swift:14 複製代碼
get
沒有set
// 你能夠這樣寫 struct Square { var side: Int var girth: Int { get { return side * 4 } } } // 也能夠這樣寫 var girth: Int { return side * 4 } // 還能夠這樣寫 var girth: Int { side * 4 } var squ = Square(side: 4) // 不可賦值修改 //squ.girth = 12; print(squ.girth) 複製代碼
枚舉的rawValue
的本質就是計算屬性, 並且是隻讀的計算屬性
enum Test: Int { case test1 = 1 case test2 = 2 } var c = Test.test1.rawValue print(c) // 1 複製代碼
至於如何肯定, 那麼久簡單粗暴點, 看彙編
rawValue
的值, 其實就是調用的rawValue
的getter
方法rawValue
進行從新賦值, 會報錯Test.test1.rawValue = 2 // 這裏報錯: Cannot assign to property: 'rawValue' is immutable 複製代碼
那麼咱們就能夠根據rawValue
的計算屬性修改rawValue
的值
enum Test: Int { case test1 = 1 case test2 = 2 var rawValue: Int { switch self { case .test1: return 10 case .test2: return 20 } } } var c = Test.test1.rawValue // 10 複製代碼
lazy
能夠定義一個延遲存儲屬性(Lazy Stored Property
), 延遲存儲屬性只有在第一次使用的時候纔會進行初始化lazy
屬性修飾必須是var
, 不能是let
let
修飾的常量必須在實例的初始化方法完成以前就擁有值class Car { init() { print("Car init") } func run() { print("Car is runing") } } class Person { lazy var car = Car() init() { print("Person init") } func goOut() { car.run() } } let person = Person() print("--------") person.goOut() // 輸出結果 // Person init // -------- // Car init // Car is runing 複製代碼
上述代碼, 在初始化car
的時候若是沒有lazy
, 則輸出結果以下
/* Car init Person init -------- Car is runing */ 複製代碼
OC
中的懶加載class Preview { lazy var image: Image = { let url = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/20/16f238c1c709a6a9~tplv-t2oaga2asx-image.image" let data = Data.init(contentsOf: url) return Image(data: data) }() } 複製代碼
在Swift
中能夠爲非lazy
的而且只能是var
修飾的存儲屬性設置屬性觀察器, 形式以下
struct Person { var age: Int { willSet { print("willSet", newValue) } didSet { print("didSet", oldValue, age) } } init() { self.age = 3 print("Person init") } } var p = Person() p.age = 10 print(p.age) /* 輸出結果 Person init willSet 10 didSet 3 10 10 */ 複製代碼
willSet
或didSet
觀察者,來觀察和響應屬性值的變化, 從上述輸出結果咱們也能夠看到
willSet
會傳遞新值, 在存儲值以前被調用, 其默認的參數名是newValue
didSet
會傳遞舊值, 在存儲新值以後當即被調用, 其默認的參數名是oldValue
willSet
或didSet
Stored Type Property
): 整個程序運行過程當中就只有一分內存(相似全局變量)Computed Type Property
): 不佔用系統內存static
關鍵字定義; 若是是類也能夠經過class
關鍵字定義init
初始化器去設置初始值的方式lazy
), 不須要使用lazy
修飾符標記, 只會在第一次使用的時候初始化, 即便是被多個線程訪問, 也能保證只會被初始化一次// 在結構體中只能使用static struct Person { static var weight: Int = 30 static let height: Int = 100 } // 取值 let a = Person.weight let b = Person.height // 賦值 Person.weight = 12 // let修飾的不可被賦值 //Person.height = 10 複製代碼
在類中可使用static
和class
class Animal { static var name: String = "name" class var age: Int { return 10 } } // 取值 let a1 = Animal.name let a2 = Animal.age // 賦值 Animal.name = "animal" // class定義的屬性是隻讀的 // Animal.age = 20 複製代碼
static
class
、struct
、enum
類型的屬性或者方法class
中的屬性和方法不能夠在子類中被重寫, 重寫會報錯struct Person { // 存儲屬性 static var weight: Int = 30 // 計算屬性 static var height: Int { get { 140 } } // 類型方法 static func goShoping() { print("Person shoping") } } 複製代碼
class
class Animal { // 計算屬性 class var height: Int { get { 140 } } // 類型方法 class func running() { print("Person running") } } 複製代碼
先看下下面這行代碼的內存地址
var num1 = 3 var num2 = 5 var num3 = 7 複製代碼
rip
做爲指令指針,rip
中存儲着CPU
下一條要執行的指令的地址CPU
讀取一條指令, rip
會自動指向下一條指令(存儲下一條指令的地址)rip
存儲的地址就是第三條指令的地址0x10000138c
0x10000137f <+15>: xorl %ecx, %ecx // $0x3賦值給num1, 則num1的地址值就是: 0x10000138c + 0x5e6c = 0x1000071F8 0x100001381 <+17>: movq $0x3, 0x5e6c(%rip) ; lazy cache variable for type metadata for Swift.Array<Swift.UInt8> + 4 // $0x5賦值給num2, 則num2的地址值就是: 0x100001397 + 0x5e69 = 0x100007200 0x10000138c <+28>: movq $0x5, 0x5e69(%rip) ; SwiftLanguage.num1 : Swift.Int + 4 // $0x7賦值給num3, 則num3的地址值就是: 0x1000013a2 + 0x5e66 = 0x100007208 0x100001397 <+39>: movq $0x7, 0x5e66(%rip) ; SwiftLanguage.num2 : Swift.Int + 4 0x1000013a2 <+50>: movl %edi, -0x1c(%rbp) 複製代碼
從上面三個內存地址能夠看出三個全局變量的內存地址是相鄰的, 而且彼此相差8個字節, 由於每個
Int
就佔用8個字節; 下面再看一下類型屬性和全局變量的內存地址
class Animal { static var age: Int = 10 } var num1 = 3 Animal.age = 7 var num2 = 5 複製代碼
相關彙編代碼如圖所示
根據圖中的相關核心代碼, 分別計算出num1
, age
和num2
的內存地址以下
// $0x3賦值給num1, 則num1的地址值就是: 0x100000fd3 + 0x6785 = 0x100007330 // 經過register命令獲得rax的地址爲0x100007338, 即爲age所在的內存地址 // $0x5賦值給num2, 則num2的地址值就是: 0x100001027 + 0x6319 = 0x100007340 /* 0x100007330 0x100007338 0x100007340 */ // 上述三個內存地址一樣也是相鄰, 而且彼此相差8個字節 複製代碼
因此, 類型屬性也能夠理解爲全局變量, 不一樣的是全局變量能夠直接訪問, 類型屬性必須經過類名訪問, 有必定的訪問限制而已
lazy
), 不須要使用lazy
修飾符標記, 只會在第一次使用的時候初始化age
賦值以前, 執行了不少彙編代碼callq
// 進入查看具體執行的那些操做 0x100000fda <+26>: callq 0x1000010d0 ; SwiftLanguage.Animal.age.unsafeMutableAddressor : Swift.Int at main.swift 複製代碼
將斷點加在此處, 執行si
指令便可進入該模塊
swift_once
, 天然就可以聯想到dispatch_once
和OC
中的單例模式swift_once
裏面究竟是如何操做的, 仍是在swift_once
加上斷點, 並執行si
指令, 以下圖所示dispatch_once
實現的dispatch_once
裏面執行的, 保證age
的初始化操做永遠只被執行一次歡迎您掃一掃下面的微信公衆號,訂閱個人博客!