struct Circle {
//存儲屬性
var radius: Double
//計算屬性
var diamiter: Double {
set {
radius = newValue / 2
}
get {
radius * 2
}
}
}
複製代碼
Stored Property
)
不能夠
定義存儲屬性 咱們知道枚舉的內存裏面能夠存放的是全部的case
以及關聯值
,並無所謂的成員變量概念,可所以也不存在所謂的存儲屬性 * 計算屬性(Computed Property
) + 本質就是方法(函數)這個也能夠經過彙編來證實一下
+ 不佔用實例的內存
+ 枚舉、結構體、類均可以定義計算屬性swift
Swift
有個明確的規定
set
傳入的新值默認叫作newValue
,也能夠自定義var
, 不能用let
let
表明常量,也就是值是一成不變的get
, 沒有set
rawValue
的本質是:只讀計算屬性,直接看彙編就能夠證實 看現這段代碼數組
class Car {
init() {
print("Car init")
}
func run() {
print("Car is running!")
}
}
class Person {
var car = Car()
init() {
print("Person init")
}
func goOut() {
car.run()
}
}
let p = Person()
print("-----------")
p.goOut()
複製代碼
運行結果以下安全
Car init
Person init
-----------
Car is running!
Program ended with exit code: 0
複製代碼
咱們給上面代碼的car屬性增長一個關鍵字lazy
修飾markdown
class Car {
init() {
print("Car init")
}
func run() {
print("Car is running!")
}
}
class Person {
lazy var car = Car()
init() {
print("Person init")
}
func goOut() {
car.run()
}
}
let p = Person()
print("-----------")
p.goOut()
複製代碼
再看下如今的運行結果網絡
Person init
-----------
Car init
Car is running!
Program ended with exit code: 0
複製代碼
能夠看出,lazy
的做用,是將屬性var car
的初始化延遲到了它首次使用的時候進行,例子中也就是p.goOut()
這句代碼執行的時候,纔回去初始化屬性car
多線程
經過lazy
關鍵字修飾的存儲屬性就要作延遲存儲屬性
,這個功能的好處是顯而易見的,由於有些屬性可能須要花費不少資源進行初始化,而極可能在某些極少狀況下才會被觸發使用,因此lazy
關鍵字就能夠用在這種狀況下,讓核心對象的初始化變得快速而輕量。好比下面這個例子閉包
class PhotoView {
lazy var image: Image = {
let url = "https://www.520it.com/xx.png"
let data = Data(url: url)
return Image(dada: data)
}()
}
複製代碼
網絡圖片的加載每每是須要一些時間的,上面例子裏面圖片的加載過程封裝在閉包表達式裏面,而且將其返回值做爲了image
屬性的初始化賦值,經過lazy
,就講這個加載的過程推遲到了image
在實際被用到的時候去執行,這樣就能夠提高app順滑度,改善卡頓狀況。app
- 使用
lazy
能夠定義一個延遲存儲屬性,在第一次用到屬性的時候纔會進行初始化lazy
屬性必須是var
, 不能是let
- 這個要求很容易理解,
let
必須在實例
的初始化方法完成以前就擁有值,而lazy
剛好是爲了在實例建立並初始化以後的某個時刻對其某個屬性進行初始化賦值,因此lazy
只能做用域var
屬性- 若是多線程同時第一次訪問
lazy
屬性,沒法保證屬性只被初始化1
次
var
才能訪問延遲存儲屬性由於延遲屬性初始化時須要改變結構體的內存 案例中,由於
p
是常量,因此內存的內容初始化以後不能夠變化,可是p.z會使得結構體Point
的lazy var z
屬性進行初始化,由於結構體的成員是在結構體的內存裏面的,所以就須要改變結構體的內存,所以便產生了後面的報錯。ide
非lazy
的var
存儲屬性設置屬性觀察器willSet
會傳遞新值,默認叫作newValue
didSet
會傳遞舊值,默認叫作oldValue
willSet
和didSet
willSet
和didSet
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
var circle = Circle()
circle.radius = 10.5
print(circle.radius)
複製代碼
運行結果函數
Circle init!
willSet 10.5
didSet 1.0 10.5
10.5
Program ended with exit code: 0
複製代碼
屬性觀察器、計算屬性的功能,一樣能夠應用在全局變量、局部變量身上
var num: Int { get { return 10 } set { print("setNum", newValue) } } num = 12 print(num) func test() { var age = 10 { willSet { print("willSet", newValue) } didSet { print("didSet", oldValue, age) } } age = 11 } test() 複製代碼
inout
的再次研究首先看下面的代碼
func test(_ num: inout Int) {
num = 20
}
var age = 10
test(&age) // 此處加斷點
複製代碼
將程序運行至斷點處,觀察彙編
SwiftTest`main:
0x1000010b0 <+0>: pushq %rbp
0x1000010b1 <+1>: movq %rsp, %rbp
0x1000010b4 <+4>: subq $0x30, %rsp
0x1000010b8 <+8>: leaq 0x6131(%rip), %rax ; SwiftTest.age : Swift.Int
0x1000010bf <+15>: xorl %ecx, %ecx
0x1000010c1 <+17>: movq $0xa, 0x6124(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
0x1000010cc <+28>: movl %edi, -0x1c(%rbp)
-> 0x1000010cf <+31>: movq %rax, %rdi
0x1000010d2 <+34>: leaq -0x18(%rbp), %rax
0x1000010d6 <+38>: movq %rsi, -0x28(%rbp)
0x1000010da <+42>: movq %rax, %rsi
0x1000010dd <+45>: movl $0x21, %edx
0x1000010e2 <+50>: callq 0x10000547c ; symbol stub for: swift_beginAccess
0x1000010e7 <+55>: leaq 0x6102(%rip), %rdi ; SwiftTest.age : Swift.Int
0x1000010ee <+62>: callq 0x100001110 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
0x1000010f3 <+67>: leaq -0x18(%rbp), %rdi
0x1000010f7 <+71>: callq 0x10000549a ; symbol stub for: swift_endAccess
0x1000010fc <+76>: xorl %eax, %eax
0x1000010fe <+78>: addq $0x30, %rsp
0x100001102 <+82>: popq %rbp
0x100001103 <+83>: retq
複製代碼
咱們能夠看到函數test
調用以前,參數的傳遞狀況以下 對於上述比較簡單的狀況,咱們知道
inout
的本質就是進行引用傳遞,接下來,咱們考慮一些更加複雜的狀況
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width= \(width), side= \(side), girth= \(girth)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.width) // 斷點1
s.show()
print("-------------")
test(&s.side) //斷點2
s.show()
print("-------------")
test(&s.girth) //斷點3
s.show()
print("-------------")
複製代碼
上述案例裏面,全局變量s的類型是結構體 Struct Shape
,它的內存放的是兩個存儲屬性width
和side
,其中side
帶有屬性觀察器,另外Shape還有一個計算屬性girth
,咱們首先不加斷點運行一下程序,觀察一下運行結果
getGirth
width= 20, side= 4, girth= 80
-------------
willSetSide 20
didSetSide 4 20
getGirth
width= 20, side= 20, girth= 400
-------------
getGirth
setGirth 20
getGirth
width= 1, side= 20, girth= 20
-------------
Program ended with exit code: 0
複製代碼
看得出來,inout
對於三種屬性都產生了做用,那麼它的底層究竟是如何處理和實現的呢?咱們仍是要經過彙編來一探究竟。便於彙編分析,咱們截取部分代碼進行編譯運行
首先看
普通的屬性
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width= \(width), side= \(side), girth= \(girth)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.width) // 斷點處,傳入普通屬性width做爲test的inout參數
複製代碼
彙編結果以下
SwiftTest`main:
0x100001310 <+0>: pushq %rbp
0x100001311 <+1>: movq %rsp, %rbp
0x100001314 <+4>: subq $0x30, %rsp
0x100001318 <+8>: movl $0xa, %eax
0x10000131d <+13>: movl %edi, -0x1c(%rbp)
0x100001320 <+16>: movq %rax, %rdi
0x100001323 <+19>: movl $0x4, %eax
0x100001328 <+24>: movq %rsi, -0x28(%rbp)
0x10000132c <+28>: movq %rax, %rsi
0x10000132f <+31>: callq 0x100001d60 ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) -> SwiftTest.Shape at main.swift:630
0x100001334 <+36>: leaq 0x6ebd(%rip), %rcx ; SwiftTest.s : SwiftTest.Shape
0x10000133b <+43>: xorl %r8d, %r8d
0x10000133e <+46>: movl %r8d, %esi
0x100001341 <+49>: movq %rax, 0x6eb0(%rip) ; SwiftTest.s : SwiftTest.Shape
0x100001348 <+56>: movq %rdx, 0x6eb1(%rip) ; SwiftTest.s : SwiftTest.Shape + 8
-> 0x10000134f <+63>: movq %rcx, %rdi
0x100001352 <+66>: leaq -0x18(%rbp), %rax
0x100001356 <+70>: movq %rsi, -0x30(%rbp)
0x10000135a <+74>: movq %rax, %rsi
0x10000135d <+77>: movl $0x21, %edx
0x100001362 <+82>: movq -0x30(%rbp), %rcx
0x100001366 <+86>: callq 0x100006312 ; symbol stub for: swift_beginAccess
0x10000136b <+91>: leaq 0x6e86(%rip), %rdi ; SwiftTest.s : SwiftTest.Shape
0x100001372 <+98>: callq 0x100001d70 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
0x100001377 <+103>: leaq -0x18(%rbp), %rdi
0x10000137b <+107>: callq 0x100006330 ; symbol stub for: swift_endAccess
0x100001380 <+112>: xorl %eax, %eax
0x100001382 <+114>: addq $0x30, %rsp
0x100001386 <+118>: popq %rbp
0x100001387 <+119>: retq
複製代碼
參數傳遞流程以下圖
因此對於普通的存儲屬性
,test
函數是直接將它的地址值傳入。
接下來便於直觀的對比,咱們再看一下
計算屬性
的狀況
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width= \(width), side= \(side), girth= \(girth)")
}
}
func test(_ num: inout Int) {
print("開始test函數")
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.girth)
複製代碼
斷點處彙編以下
SwiftTest`main:
0x1000012f0 <+0>: pushq %rbp
0x1000012f1 <+1>: movq %rsp, %rbp
0x1000012f4 <+4>: pushq %r13
0x1000012f6 <+6>: subq $0x38, %rsp
0x1000012fa <+10>: movl $0xa, %eax
0x1000012ff <+15>: movl %edi, -0x2c(%rbp)
0x100001302 <+18>: movq %rax, %rdi
0x100001305 <+21>: movl $0x4, %eax
0x10000130a <+26>: movq %rsi, -0x38(%rbp)
0x10000130e <+30>: movq %rax, %rsi
0x100001311 <+33>: callq 0x100001d60 ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) -> SwiftTest.Shape at main.swift:630
0x100001316 <+38>: leaq 0x6edb(%rip), %rcx ; SwiftTest.s : SwiftTest.Shape
0x10000131d <+45>: xorl %r8d, %r8d
0x100001320 <+48>: movl %r8d, %esi
0x100001323 <+51>: movq %rax, 0x6ece(%rip) ; SwiftTest.s : SwiftTest.Shape
0x10000132a <+58>: movq %rdx, 0x6ecf(%rip) ; SwiftTest.s : SwiftTest.Shape + 8
-> 0x100001331 <+65>: movq %rcx, %rdi
0x100001334 <+68>: leaq -0x20(%rbp), %rax
0x100001338 <+72>: movq %rsi, -0x40(%rbp)
0x10000133c <+76>: movq %rax, %rsi
0x10000133f <+79>: movl $0x21, %edx
0x100001344 <+84>: movq -0x40(%rbp), %rcx
0x100001348 <+88>: callq 0x100006312 ; symbol stub for: swift_beginAccess
0x10000134d <+93>: movq 0x6ea4(%rip), %rdi ; SwiftTest.s : SwiftTest.Shape
0x100001354 <+100>: movq 0x6ea5(%rip), %rsi ; SwiftTest.s : SwiftTest.Shape + 8
0x10000135b <+107>: callq 0x1000016d0 ; SwiftTest.Shape.girth.getter : Swift.Int at main.swift:646
0x100001360 <+112>: movq %rax, -0x28(%rbp)
0x100001364 <+116>: leaq -0x28(%rbp), %rdi
0x100001368 <+120>: callq 0x100001d70 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
0x10000136d <+125>: movq -0x28(%rbp), %rdi
0x100001371 <+129>: leaq 0x6e80(%rip), %r13 ; SwiftTest.s : SwiftTest.Shape
0x100001378 <+136>: callq 0x100001820 ; SwiftTest.Shape.girth.setter : Swift.Int at main.swift:642
0x10000137d <+141>: leaq -0x20(%rbp), %rdi
0x100001381 <+145>: callq 0x100006330 ; symbol stub for: swift_endAccess
0x100001386 <+150>: xorl %eax, %eax
0x100001388 <+152>: addq $0x38, %rsp
0x10000138c <+156>: popq %r13
0x10000138e <+158>: popq %rbp
0x10000138f <+159>: retq
複製代碼
這一次從彙編代碼量就能夠判斷,對於計算屬性的處理確定比存儲屬性要複雜,仍是經過圖例來展現一下整個過程
能夠看出,因爲計算屬性在實例內部沒有對應的內存空間,編譯器經過在函數棧裏面開闢一個局部變量的方法,利用它做爲計算屬性的值的臨時宿主,而且將該局部變量的地址做爲test
函數的inout
參數傳入函數,因此本質上,仍然是引用傳遞
。
test
函數調用前,計算屬性值給複製到局部變量上,以及test
函數調用以後,局部變量的值傳遞給setter函數的這兩個過程,被蘋果成爲 Copy In Copy Out,上面案例代碼的運行結果也驗證了這個結論
getGirth
開始test函數
setGirth 20
Program ended with exit code: 0
複製代碼
最後,咱們來看對於
帶有屬性觀察器的存儲屬性
,處理過程會有哪些獨到之處
struct Shape {
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
return width * side
}
}
func show() {
print("width= \(width), side= \(side), girth= \(girth)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width: 10, side: 4)
test(&s.side) //side是帶屬性觀察期的存儲屬性, 斷點在這裏
複製代碼
斷點處彙編結果以下
SwiftTest`main:
0x100001230 <+0>: pushq %rbp
0x100001231 <+1>: movq %rsp, %rbp
0x100001234 <+4>: pushq %r13
0x100001236 <+6>: subq $0x38, %rsp
0x10000123a <+10>: movl $0xa, %eax
0x10000123f <+15>: movl %edi, -0x2c(%rbp)
0x100001242 <+18>: movq %rax, %rdi
0x100001245 <+21>: movl $0x4, %eax
0x10000124a <+26>: movq %rsi, -0x38(%rbp)
0x10000124e <+30>: movq %rax, %rsi
0x100001251 <+33>: callq 0x100001ca0 ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) -> SwiftTest.Shape at main.swift:630
0x100001256 <+38>: leaq 0x6f9b(%rip), %rcx ; SwiftTest.s : SwiftTest.Shape
0x10000125d <+45>: xorl %r8d, %r8d
0x100001260 <+48>: movl %r8d, %esi
0x100001263 <+51>: movq %rax, 0x6f8e(%rip) ; SwiftTest.s : SwiftTest.Shape
0x10000126a <+58>: movq %rdx, 0x6f8f(%rip) ; SwiftTest.s : SwiftTest.Shape + 8
-> 0x100001271 <+65>: movq %rcx, %rdi
0x100001274 <+68>: leaq -0x20(%rbp), %rax
0x100001278 <+72>: movq %rsi, -0x40(%rbp)
0x10000127c <+76>: movq %rax, %rsi
0x10000127f <+79>: movl $0x21, %edx
0x100001284 <+84>: movq -0x40(%rbp), %rcx
0x100001288 <+88>: callq 0x100006302 ; symbol stub for: swift_beginAccess
0x10000128d <+93>: movq 0x6f6c(%rip), %rax ; SwiftTest.s : SwiftTest.Shape + 8
0x100001294 <+100>: movq %rax, -0x28(%rbp)
0x100001298 <+104>: leaq -0x28(%rbp), %rdi
0x10000129c <+108>: callq 0x100001cb0 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
0x1000012a1 <+113>: movq -0x28(%rbp), %rdi
0x1000012a5 <+117>: leaq 0x6f4c(%rip), %r13 ; SwiftTest.s : SwiftTest.Shape
0x1000012ac <+124>: callq 0x100001350 ; SwiftTest.Shape.side.setter : Swift.Int at main.swift:632
0x1000012b1 <+129>: leaq -0x20(%rbp), %rdi
0x1000012b5 <+133>: callq 0x100006320 ; symbol stub for: swift_endAccess
0x1000012ba <+138>: xorl %eax, %eax
0x1000012bc <+140>: addq $0x38, %rsp
0x1000012c0 <+144>: popq %r13
0x1000012c2 <+146>: popq %rbp
0x1000012c3 <+147>: retq
複製代碼
此次,咱們發現跟計算屬性有些相似,這裏也用到了函數棧的局部變量,它的做用是用來承載計算屬性的值,而後被傳入test函數的一樣是這個局部變量的地址(引用),可是我很好奇爲什麼要畫蛇添足,計算屬性由於自己沒有固定的內存,因此很好理解必須藉助局部變臉做爲臨時宿主,可是計算屬性是有固定內存的,能夠猜的到,這麼設計的緣由確定跟屬性觀察器有關,可是目前的代碼還不足以解釋這麼設計的意圖,可是咱們看到這裏最後一步,調用了side.setter函數,🤔️side是存儲屬性,怎麼會有setter函數呢?那咱們就進入它內部看看嘍,它的彙編以下
SwiftTest`Shape.side.setter:
-> 0x100001350 <+0>: pushq %rbp
0x100001351 <+1>: movq %rsp, %rbp
0x100001354 <+4>: pushq %r13
0x100001356 <+6>: subq $0x28, %rsp
0x10000135a <+10>: movq $0x0, -0x10(%rbp)
0x100001362 <+18>: movq $0x0, -0x18(%rbp)
0x10000136a <+26>: movq %rdi, -0x10(%rbp)
0x10000136e <+30>: movq %r13, -0x18(%rbp)
0x100001372 <+34>: movq 0x8(%r13), %rax
0x100001376 <+38>: movq %rax, %rcx
0x100001379 <+41>: movq %rdi, -0x20(%rbp)
0x10000137d <+45>: movq %r13, -0x28(%rbp)
0x100001381 <+49>: movq %rax, -0x30(%rbp)
0x100001385 <+53>: callq 0x1000013b0 ; SwiftTest.Shape.side.willset : Swift.Int at main.swift:633
0x10000138a <+58>: movq -0x28(%rbp), %rax
0x10000138e <+62>: movq -0x20(%rbp), %rcx
0x100001392 <+66>: movq %rcx, 0x8(%rax)
0x100001396 <+70>: movq -0x30(%rbp), %rdi
0x10000139a <+74>: movq %rax, %r13
0x10000139d <+77>: callq 0x1000014d0 ; SwiftTest.Shape.side.didset : Swift.Int at main.swift:636
0x1000013a2 <+82>: movq -0x30(%rbp), %rax
0x1000013a6 <+86>: addq $0x28, %rsp
0x1000013aa <+90>: popq %r13
0x1000013ac <+92>: popq %rbp
0x1000013ad <+93>: retq
複製代碼
原來,這個
side
的兩個屬性觀察器willSet
和didSet
被包裹在了這個setter
函數裏面,並且,對於屬性side
的賦值真正發生在這個setter
函數裏面。
所以咱們看出了一個細節,屬性side
內存裏的值被修改的時間點,是在test
函數以後,也就是這個setter
函數裏,也就是test
函數其實並無修改side
的值。
由於test
函數的功能拿到一段內存,而且修改裏面的值,若是當前咱們將side
的地址提交給test
,除了可以修改side
內存裏值之外,它是沒法觸發side
的屬性觀察器的。因此看得出局部變量以及setter
函數出如今這裏的意義就是爲了可以去觸發屬性side
的屬性觀察器。由於咱們使用了局部變量,所以對於帶有屬性觀察器的存儲屬性,也能夠說inout
對其採用了Copy In Copy Out
的作法。
經過程序運行以後的輸出結果,也能夠驗證咱們已上的結論
開始test函數
willSetSide 20
didSetSide 4 20
Program ended with exit code: 0
複製代碼
inout
的本質總結則直接將實參的內存地址傳入函數(實參進行引用傳遞
)
則採起了 Copy In Copy Out的作法 - 調用該函數時,先複製實參的值,產生副本【能夠理解成get
操做】 - 將副本的內存地址傳入函數(副本進行引用傳遞
),在函數內部能夠修改副本的值 - 函數返回後,再將副本的值覆蓋實參的值【能夠理解成set
操做】
總結:
inout
的本質就是引用傳遞
(地址傳遞)
- 類型屬性(**Type Property**):只能經過類型去訪問
+ 存儲類型屬性(**Stored Type Property**):整個程序的運行過程當中,就只有一分內存,它的本質就是全局變量
+ 計算類型屬性(**Computed Type Property**)
複製代碼
static
定義類型屬性,對於類來講,還能夠用關鍵字class
由於類型沒有像實例那樣的init
初始化器來初始化存儲屬性
存儲類型屬性默認就是lazy
, 會在第一次使用的時候才初始化
let
,由於這裏壓根不存在實例初始化的過程枚舉類型也能夠定義類型屬性(存儲類型屬性、計算類型屬性)
public class FileManager {
public static let shared = FileManager()
private init(){
}
}
複製代碼
public static let shared = FileManager()
:
static
定義了一個類型存儲屬性,public
確保在任何場景下,外界都能訪問,let
保證了FileManager()
只會被賦值給shared
一次,而且確保了線程安全,也就是說init()
方法只會被調用一次,這樣就確保FileManager
只會存在惟一一個實例,這就是Swift中的單例。private init()
:private
確保了外界是沒法手動調用FileManager()
來建立實例,所以經過shared
屬性獲得的FileManager
實例永遠是相同的一份,這也符合了咱們對與單例的要求。前面咱們介紹static存儲屬性的時候,提到了它其實是全局變量,如今來證實一下,首先咱們看看普通的全局變量是怎麼樣的
var num1 = 10 // 此處加斷點
var num2 = 11
var num3 = 12
複製代碼
運行至斷點處,彙編以下
SwiftTest`main:
0x100001120 <+0>: pushq %rbp
0x100001121 <+1>: movq %rsp, %rbp
0x100001124 <+4>: xorl %eax, %eax
-> 0x100001126 <+6>: movq $0xa, 0x60af(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
0x100001131 <+17>: movq $0xb, 0x60ac(%rip) ; SwiftTest.num1 : Swift.Int + 4
0x10000113c <+28>: movq $0xc, 0x60a9(%rip) ; SwiftTest.num2 : Swift.Int + 4
0x100001147 <+39>: popq %rbp
0x100001148 <+40>: retq
複製代碼
很明顯,下圖的這三句分別對應的就是num1
、num2
、num3
咱們來算一下他們的實際內存地址
&num1 = 0x60af + 0x100001131 = 0x1000071E0
&num2 = 0x60ac + 0x10000113c = 0x1000071E8
&num3 = 0x60a9 + 0x100001147 = 0x1000071F0
它們就是全局數據段上的3段連續內存空間。接下來咱們加入static存儲屬性以下
var num1 = 10 // 斷點處
class Car {
static var num2 = 1
}
Car.num2 = 11
var num3 = 12
複製代碼
打開斷點處的彙編
SwiftTest`main:
0x100000d80 <+0>: pushq %rbp
0x100000d81 <+1>: movq %rsp, %rbp
0x100000d84 <+4>: subq $0x30, %rsp
-> 0x100000d88 <+8>: movq $0xa, 0x6595(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
0x100000d93 <+19>: movl %edi, -0x1c(%rbp)
0x100000d96 <+22>: movq %rsi, -0x28(%rbp)
0x100000d9a <+26>: callq 0x100000e40 ; SwiftTest.Car.num2.unsafeMutableAddressor : Swift.Int at main.swift
0x100000d9f <+31>: xorl %ecx, %ecx
0x100000da1 <+33>: movq %rax, %rdx
0x100000da4 <+36>: movq %rdx, %rdi
0x100000da7 <+39>: leaq -0x18(%rbp), %rsi
0x100000dab <+43>: movl $0x21, %edx
0x100000db0 <+48>: movq %rax, -0x30(%rbp)
0x100000db4 <+52>: callq 0x1000053a2 ; symbol stub for: swift_beginAccess
0x100000db9 <+57>: movq -0x30(%rbp), %rax
0x100000dbd <+61>: movq $0xb, (%rax)
0x100000dc4 <+68>: leaq -0x18(%rbp), %rdi
0x100000dc8 <+72>: callq 0x1000053c6 ; symbol stub for: swift_endAccess
0x100000dcd <+77>: xorl %eax, %eax
0x100000dcf <+79>: movq $0xc, 0x655e(%rip) ; static SwiftTest.Car.num2 : Swift.Int + 4
0x100000dda <+90>: addq $0x30, %rsp
0x100000dde <+94>: popq %rbp
0x100000ddf <+95>: retq
複製代碼
如上圖所示,首先咱們能夠快速定位
num1
和num3
,咱們能夠先記錄一下他們的內存地址
&num1 = 0x6595 + 0x100000d93 = 0x100007328
&num3 = 0x655e + 0x100000dda = 0x100007338
在num1
和num2
中間,咱們發現了一個叫Car.num2.unsafeMutableAddressor
的函數被調用,而且經過將它的返回值做爲地址訪問了一段內存空間,並向其賦值11
,從Car.num2.unsafeMutableAddressor
這個名字,咱們能夠看出,這個函數返回出來的地址,就是Car.num2
的地址,首先咱們運行到0x100000dbd <+61>: movq $0xb, (%rax)
這句彙編,記錄一下這個地址的值
(lldb) register read rax
rax = 0x0000000100007330 SwiftTest`static SwiftTest.Car.num2 : Swift.Int
複製代碼
能夠看到,這個地址正好是
num1
和num3
之間的那段空間,所以雖然num2
做爲Car
的static
存儲屬性,可是從它在內存中的位置來看,跟普通的全局變量沒有區別,所以能夠說static存儲屬性的本質就是全局變量。
代碼稍微調整一下
var num1 = 10
class Car {
static var num2 = 1
}
//Car.num2 = 11 //將這一句註釋掉
var num3 = 12
**********************對應彙編***********************
SwiftTest`main:
0x100000dc0 <+0>: pushq %rbp
0x100000dc1 <+1>: movq %rsp, %rbp
0x100000dc4 <+4>: xorl %eax, %eax
-> 0x100000dc6 <+6>: movq $0xa, 0x6557(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
0x100000dd1 <+17>: movq $0xc, 0x655c(%rip) ; static SwiftTest.Car.num2 : Swift.Int + 4
0x100000ddc <+28>: popq %rbp
0x100000ddd <+29>: retq
複製代碼
能夠看出,彙編裏
Car.num2
相關的代碼就消失了,也就是說若是沒有用到Car.num2
,那麼它是不會被初始化的,所以咱們說static
存儲屬性是默認lazy
(延遲)的。
咱們將代碼恢復,再次更深刻的跟蹤一下彙編過程
var num1 = 10 // 斷點處
class Car {
static var num2 = 1
}
Car.num2 = 11
var num3 = 12
**********************對應彙編***********************
SwiftTest`main:
0x100000d80 <+0>: pushq %rbp
0x100000d81 <+1>: movq %rsp, %rbp
0x100000d84 <+4>: subq $0x30, %rsp
-> 0x100000d88 <+8>: movq $0xa, 0x6595(%rip) ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
0x100000d93 <+19>: movl %edi, -0x1c(%rbp)
0x100000d96 <+22>: movq %rsi, -0x28(%rbp)
0x100000d9a <+26>: callq 0x100000e40 ; SwiftTest.Car.num2.unsafeMutableAddressor : Swift.Int at main.swift
0x100000d9f <+31>: xorl %ecx, %ecx
0x100000da1 <+33>: movq %rax, %rdx
0x100000da4 <+36>: movq %rdx, %rdi
0x100000da7 <+39>: leaq -0x18(%rbp), %rsi
0x100000dab <+43>: movl $0x21, %edx
0x100000db0 <+48>: movq %rax, -0x30(%rbp)
0x100000db4 <+52>: callq 0x1000053a2 ; symbol stub for: swift_beginAccess
0x100000db9 <+57>: movq -0x30(%rbp), %rax
0x100000dbd <+61>: movq $0xb, (%rax)
0x100000dc4 <+68>: leaq -0x18(%rbp), %rdi
0x100000dc8 <+72>: callq 0x1000053c6 ; symbol stub for: swift_endAccess
0x100000dcd <+77>: xorl %eax, %eax
0x100000dcf <+79>: movq $0xc, 0x655e(%rip) ; static SwiftTest.Car.num2 : Swift.Int + 4
0x100000dda <+90>: addq $0x30, %rsp
0x100000dde <+94>: popq %rbp
0x100000ddf <+95>: retq
複製代碼
這一次咱們從
unsafeMutableAddressor
這個函數跟進去看看
SwiftTest`Car.num2.unsafeMutableAddressor:
-> 0x100000e40 <+0>: pushq %rbp
0x100000e41 <+1>: movq %rsp, %rbp
0x100000e44 <+4>: cmpq $-0x1, 0x64f4(%rip) ; SwiftTest.num3 : Swift.Int + 7
0x100000e4c <+12>: sete %al
0x100000e4f <+15>: testb $0x1, %al
0x100000e51 <+17>: jne 0x100000e55 ; <+21> at main.swift:719:16
0x100000e53 <+19>: jmp 0x100000e5e ; <+30> at main.swift
0x100000e55 <+21>: leaq 0x64d4(%rip), %rax ; static SwiftTest.Car.num2 : Swift.Int
0x100000e5c <+28>: popq %rbp
0x100000e5d <+29>: retq
0x100000e5e <+30>: leaq -0x45(%rip), %rax ; globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 at main.swift
0x100000e65 <+37>: leaq 0x64d4(%rip), %rdi ; globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_token0
0x100000e6c <+44>: movq %rax, %rsi
0x100000e6f <+47>: callq 0x1000053fc ; symbol stub for: swift_once
0x100000e74 <+52>: jmp 0x100000e55 ; <+21> at main.swift:719:16
複製代碼
看到在最後,調用了swift_once
函數,GCD裏面咱們知道有個dispatch_once
,是否有關聯呢,咱們進入這個函數
libswiftCore.dylib`swift_once:
-> 0x7fff73447820 <+0>: pushq %rbp
0x7fff73447821 <+1>: movq %rsp, %rbp
0x7fff73447824 <+4>: cmpq $-0x1, (%rdi)
0x7fff73447828 <+8>: jne 0x7fff7344782c ; <+12>
0x7fff7344782a <+10>: popq %rbp
0x7fff7344782b <+11>: retq
0x7fff7344782c <+12>: movq %rsi, %rax
0x7fff7344782f <+15>: movq %rdx, %rsi
0x7fff73447832 <+18>: movq %rax, %rdx
0x7fff73447835 <+21>: callq 0x7fff7349c19c ; symbol stub for: dispatch_once_f
0x7fff7344783a <+26>: popq %rbp
0x7fff7344783b <+27>: retq
0x7fff7344783c <+28>: nop
0x7fff7344783d <+29>: nop
0x7fff7344783e <+30>: nop
0x7fff7344783f <+31>: nop
複製代碼
真相出現了,原來swift_once
函數裏面確實是調用了GCD的dispatch_once_f
,那麼dispatch_once
裏面的block
是什麼呢,直覺告訴咱們應該就是Car.num2
的初始化代碼,也就是這句代碼static var num2 = 1
如何證實呢?我先咱們將彙編運行到callq 0x7fff7349c19c ; symbol stub for: dispatch_once_f
處,由於此時,dispatch_once_f
函數所需的參數按照彙編的慣例,已經放到了rsi
、rdx
等寄存起裏面了,咱們能夠查看一下此時這兩個寄存器的內容
(lldb) register read rsi
rsi = 0x00007ffeefbff598
(lldb) register read rdx
rdx = 0x0000000100000e20 SwiftTest`globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 at main.swift
(lldb)
複製代碼
能夠看到rdx
此時存放的是一個跟globalinit(全局初始化)相關的函數func0
,地址爲0x0000000100000e20
,該函數就是dispatch_once_f
所接受的block
。接下來咱們回到Swift源碼,在以下處加一個斷點
那麼咱們繼續運行程序,斷點會停在上面這句代碼上,若是咱們猜想正確的話,那麼此時的彙編應該就在globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0
這個函數裏面,咱們運行程序後,彙編以下
SwiftTest`globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0:
0x100000e20 <+0>: pushq %rbp
0x100000e21 <+1>: movq %rsp, %rbp
-> 0x100000e24 <+4>: movq $0x1, 0x6501(%rip) ; SwiftTest.num1 : Swift.Int + 4
0x100000e2f <+15>: popq %rbp
0x100000e30 <+16>: retq
複製代碼
確實是處在globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0
函數內部,而且這裏進行初始化的內存地址是 0x100000e2f + 0x6501 = 0x100007330
,從初始值很明顯看出這段內存就是num2
,而且跟咱們在unsafeMutableAddressor
函數返回處記錄的返回值相同,結果正如預期,證實完畢。
在Swift底層,是經過
unsafeMutableAddressor
->libswiftCore.dylib-swift_once
->libswiftCore.dylib-dispatch_once_f:
---------->static var num2 = 1
來對num2
進行初始化的,由於使用了GCD
的dispatch_once
,所以咱們說static
存儲屬性是線程安全的,而且只能被初始化一次。
class Car {
static var count = 0
init() {
Car.count += 1
}
// Type Method
static func getCount() -> Int {
//如下幾種訪問count的方法是等價的
count += 1
self.count += 1
Car.self.count += 1
Car.count += 1
return count
}
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 經過類名進行調用
複製代碼
枚舉、結構體、類均可以定義實例方法、類型方法
Instance Method
):經過實例對象進行調用Type Method
):經過類型調用,用static
或者class
關鍵字來定義self
在類型方法static func getCount
中,如下幾種寫法等價
count
self.count
Car.count
Car.self.count
Swift語法規定,對於結構體和枚舉這兩種值類型,默認狀況下,他們的屬性是不能被自身的實例方法所修改的(對於類沒有這個規定)
func
關鍵字前面加mutating
就能夠容許這種修改行爲,以下struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
enum StateSwitch {
case low, middle, high
mutating func next() {
switch self {
case .low:
self = .middle
case .middle:
self = .high
case .high:
self = .low
}
}
}
複製代碼
在func前面加上@discardableResult,能夠消除:函數調用後的返回值未被使用的警告信息️
struct Point {
var x = 0.0, y = 0.0
@discardableResult mutating
func moveX(deltaX: Double) -> Double {
x += deltaX
return x
}
}
var p = Point()
p.moveX(deltaX: 10)
複製代碼
使用subscript
能夠給任意類型(枚舉、類、結構體)增長下表功能。subscript
的語法相似於實例方法、計算屬性,它的本質就是方法(函數)
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x) // 11.1
print(p.y) // 22.2
print(p[0]) // 11.1
print(p[1]) // 22.2
複製代碼
從上面的案例來看,subscript
爲咱們提供了經過[i]
的方式去訪問成員變量,就像數組/字典那樣去使用。下標與函數的表面區別,只是在定義的時候,用subscript
代替了func funcName
,在調用的時候經過[arg]
代替了funcName(arg)
。而subscript
的內部包含了get
和set
,很像計算屬性。
咱們簡化一下代碼
class Point {
var x = 0, y = 0
subscript(index: Int) -> Int {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 10 // 0xa 在這裏放一個斷點️
p[1] = 11 // 0xb
複製代碼
運行程序至斷點處,彙編以下
咱們咱們根據當即數10和11,找到綠框處代碼,紅色標記處的函數顯然不是下標的調用,咱們從兩個綠框處的間接函數調用跟進去看看
0x1000016b1 <+145>: callq *0x98(%rcx) ---進入該函數-->
SwiftTest`Point.subscript.setter:
-> 0x100001c10 <+0>: pushq %rbp
0x100001c11 <+1>: movq %rsp, %rbp
0x100001c14 <+4>: pushq %r13
0x100001c16 <+6>: subq $0x48, %rsp
0x100001c1a <+10>: xorl %eax, %eax
0x100001c1c <+12>: leaq -0x10(%rbp), %rcx
0x100001c20 <+16>: movq %rdi, -0x28(%rbp)
..........
..........
..........
複製代碼
0x100001715 <+245>: callq *0x98(%rcx) ---進入該函數-->
SwiftTest`Point.subscript.setter:
-> 0x100001c10 <+0>: pushq %rbp
0x100001c11 <+1>: movq %rsp, %rbp
0x100001c14 <+4>: pushq %r13
0x100001c16 <+6>: subq $0x48, %rsp
0x100001c1a <+10>: xorl %eax, %eax
0x100001c1c <+12>: leaq -0x10(%rbp), %rcx
0x100001c20 <+16>: movq %rdi, -0x28(%rbp)
..........
..........
..........
複製代碼
上面的結果說明callq *0x98(%rcx)
= Point.subscript.setter
等價於 p[i] =
所以,證實了下標的本質就是函數。
這裏爲何是
callq *[內存地址]
來間接調用函數呢,由於p
不是一個函數名,而是一個變量,因此想要調用下標函數,因此確定是經過間接調用
的方式來操做的。 直接調用:callq 函數地址
間接調用:callq *內存地址
注意點️
subscript
中定義的返回值類型能夠決定:
get
方法的返回值類型set
方法中國呢newValue
的類型subscript
能夠接受多個參數,而且是任意類型subscript
能夠沒有set
方法,可是必需要有get
方法,若是隻有get
方法,能夠理解爲只讀
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
複製代碼
若是隻有get
方法,還能夠省略get
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
複製代碼
還能夠設置參數標籤
class Point {
var x = 0.0, y = 0.0
subscript(index i: Int) -> Double {
if i == 0 {
return x
} else if i == 1 {
return y
}
return 0
}
}
var p = Point()
p.y = 22.2
print(p[index: 1]) // 若是有標籤的話,在使用的時候,就必定要帶上標籤才行
複製代碼
上面咱們看到的subscript
都是至關於實例方法(默認),下標也能夠是類型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10,20])
複製代碼
struct Point {
var x = 0
var y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
set { point = newValue } // 若是後面有堆point進行賦值,則必需要加上set方法。
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
複製代碼
上面的案例中,PointManager
這個類有一個下標,返回類型是結構體struct Point
,而且注意這個下標的特色,不管下標值傳什麼,它返回的都是結構體變量point
,咱們須要注意的是,下標裏面的set
的寫法應該以下
set { point = newValue }
複製代碼
這樣你可能會好奇,pm[0].x = 11
或者 pm[0].y = 22
時,在set方法裏面咱們怎麼知道這個newValue
的值究竟是給.x
仍是給.y
的。其實你應該注意到,這裏的newValue應該是struct Point
類型的,若是這樣,其實設計者的思路就不難猜到 pm[0].x = 11
---> newValue = (11, pm[0].y)
---> set { point = newValue = (11, pm[0].y) }
pm[0].y = 22
---> newValue = (pm[0].x, 22)
---> set { point = newValue = (pm[0].x, 22) }
若是把strtct Point
換成 class Point
, 這個set
方法就能夠不用寫了
class Point {
var x = 0
var y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
複製代碼
由於咱們經過pm[0]
拿到的是point
這個對象實例指針,那麼pm[0].x
等價於point.x
,因此point.x = 11
是符合規範的。
class Grid {
var data = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
subscript( row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return
}
data[row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return 0
}
return data[row][column]
}
}
}
var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)
*********************運行結果
[[0, 77, 2], [3, 4, 88], [99, 7, 8]]
Program ended with exit code: 0
複製代碼
好了,屬性和方法,暫時梳理到這裏,period!