- 作者: Liwx
- 郵箱: 1032282633@qq.com
- 源碼: 需要
源碼
的同學(xué), 可以在評(píng)論區(qū)
留下您的郵箱
iOS Swift 語(yǔ)法
底層原理
與內(nèi)存管理
分析 專題:【iOS Swift5語(yǔ)法】00 - 匯編
01 - 基礎(chǔ)語(yǔ)法
02 - 流程控制
03 - 函數(shù)
04 - 枚舉
05 - 可選項(xiàng)
06 - 結(jié)構(gòu)體和類
07 - 閉包
08 - 屬性
09 - 方法
10 - 下標(biāo)
11 - 繼承
12 - 初始化器init
13 - 可選項(xiàng)
目錄
- 01-結(jié)構(gòu)體
- 02-結(jié)構(gòu)體的初始化器
- 03-思考下面代碼能通過(guò)么?
- 04-自定義初始化器
- 05-窺探初始化器的本質(zhì)
- 06-結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)
- 07-類
- 08-類的初始化器
- 09-結(jié)構(gòu)體與類的本質(zhì)區(qū)別
- 10-值類型
- 11-值類型的賦值操作
- 12-引用類型
- 13-對(duì)象的堆空間申請(qǐng)過(guò)程
- 14-引用類型的賦值操作
- 15-值類型荐健、引用類型的let
- 16-嵌套類型
- 17-枚舉、結(jié)構(gòu)體、類都可以定義方法
01-結(jié)構(gòu)體
- 在Swift標(biāo)準(zhǔn)庫(kù)中,
絕大多數(shù)
的公開(kāi)類型都是結(jié)構(gòu)體
,而枚舉
和類
只占很小一部分
- 比如
Bool、Int、Double咬最、String、Array、Dictionary
等常見(jiàn)類型都是結(jié)構(gòu)體
- 所有的結(jié)構(gòu)體都有一個(gè)編譯器
自動(dòng)生成的初始化器
(initializer, 初始化方法,構(gòu)造器,構(gòu)造方法) - 調(diào)用構(gòu)造方法時(shí),可以傳入
所有成員值
,用以初始化所有成員(存儲(chǔ)屬性
, Stored Property)
struct Date {
var year: Int
var month: Int
var day: Int
}
var date = Date(year: 2019, month: 6, day: 1)
02-結(jié)構(gòu)體的初始化器
編譯器會(huì)根據(jù)情況,可能會(huì)為
結(jié)構(gòu)體生成多個(gè)初始化器
,宗旨是:保證所有成員都有初始值
示例1
// 結(jié)構(gòu)體所有存儲(chǔ)屬性都沒(méi)有設(shè)置初始值
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // 報(bào)錯(cuò): missing argument for parameter 'x' in call
//var p3 = Point(x: 10) // 報(bào)錯(cuò): missing argument for parameter 'y' in call
//var p4 = Point() // 報(bào)錯(cuò): missing arguments for parameters 'x', 'y' in call
- 示例2
// 結(jié)構(gòu)體部分存儲(chǔ)屬性沒(méi)有設(shè)置初始值
struct Point {
var x: Int = 0
var y: Int
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
//var p3 = Point(x: 10) // 報(bào)錯(cuò): missing argument for parameter 'y' in call
//var p4 = Point() // 報(bào)錯(cuò): missing argument for parameter 'y' in call
- 示例3
// 結(jié)構(gòu)體部分存儲(chǔ)屬性沒(méi)有設(shè)置初始值
struct Point {
var x: Int
var y: Int = 0
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // 報(bào)錯(cuò): missing argument for parameter 'x' in call
var p3 = Point(x: 10)
//var p4 = Point() // 報(bào)錯(cuò): missing argument for parameter 'x' in call
- 示例4
// 結(jié)構(gòu)體所有存儲(chǔ)屬性都有設(shè)置初始值
struct Point {
var x: Int = 0
var y: Int = 0
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
03-思考下面代碼能通過(guò)么?
-
可選項(xiàng)
都有個(gè)默認(rèn)值nil
- 因此可以編譯通過(guò)
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
04-自定義初始化器
- 一旦在定義結(jié)構(gòu)體時(shí)
自定義了初始化器
,編譯器就不會(huì)
再幫它自動(dòng)生成其他初始化器
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // error: missing argument for parameter 'x' in call
//var p3 = Point(x: 10) // error: missing argument for parameter 'y' in call
//var p4 = Point() // error: missing arguments for parameters 'x', 'y' in call
05-窺探初始化器的本質(zhì)
- 以下2段代碼完全等效
struct Point {
var x: Int = 0
var y: Int = 0
}
var p = Point()
struct Point {
var x: Int
var y: Int
init() {
x = 0
y = 0
}
}
var p = Point()
- 通過(guò)匯編代碼對(duì)比词爬,上面兩段代碼的
匯編代碼一模一樣
06-結(jié)構(gòu)體和類`Point.init():
-> 0x100000bf0 <+0>: pushq %rbp
0x100000bf1 <+1>: movq %rsp, %rbp
0x100000bf4 <+4>: xorps %xmm0, %xmm0
0x100000bf7 <+7>: movaps %xmm0, -0x10(%rbp)
0x100000bfb <+11>: movq $0x0, -0x10(%rbp)
0x100000c03 <+19>: movq $0x0, -0x8(%rbp)
0x100000c0b <+27>: xorl %eax, %eax
0x100000c0d <+29>: movl %eax, %ecx
0x100000c0f <+31>: movq %rcx, %rax
0x100000c12 <+34>: movq %rcx, %rdx
0x100000c15 <+37>: popq %rbp
0x100000c16 <+38>: retq
06-結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)
struct Point {
var x: Int = 0
var y: Int = 0
var origin: Bool = false
}
print(MemoryLayout<Point>.size) // 17
print(MemoryLayout<Point>.stride) // 24
print(MemoryLayout<Point>.alignment)// 8
var p1 = Point(x: 10, y: 20, origin: true)
print(Mems.ptr(ofVal: &p1)) // 查看地址0x000000010000a6e8
// 0A 00 00 00 00 00 00 00
// 14 00 00 00 00 00 00 00
// 01 00 00 00 00 00 00 00
print(Mems.memStr(ofVal: &p1)) // 查看地址里面的內(nèi)容 0x000000000000000a 0x0000000000000014 0x0000000000000001
image.png
07-類
- 類的定義和結(jié)構(gòu)體類似,但編譯器并沒(méi)有為類
自動(dòng)生成
可以傳入成員值的初始化器
// 結(jié)構(gòu)體
struct Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
let p2 = Point(x: 10, y: 10)
let p3 = Point(y: 10)
let p4 = Point(x: 10)
// 類
class Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
//let p2 = Point(x: 10, y: 10) // error: argument passed to call that takes no arguments
//let p3 = Point(y: 10) // error: argument passed to call that takes no arguments
//let p4 = Point(x: 10) // error: argument passed to call that takes no arguments
- 如果類成員
未指定初始值
,編譯器不會(huì)自動(dòng)生成無(wú)參的初始化器
class Point { // error: class 'Point' has no initializers
var x: Int
var y: Int
}
let p1 = Point() // error: 'Point' cannot be constructed because it has no accessible initializers
08-類的初始化器
- 如果類的
所有成員
都在定義的時(shí)候
指定了初始值
,編譯器會(huì)為類生成無(wú)參的初始化器
- 成員的
初始化
是在這個(gè)初始化器
中完成的
- 成員的
- 以下2段代碼完全等效
class Point {
var x: Int = 10
var y: Int = 20
}
let p1 = Point()
class Point {
var x: Int
var y: Int
init() {
x = 10
y = 20
}
}
let p1 = Point()
據(jù)匯編觀察, 2段代碼的匯編代碼一模一樣
06-結(jié)構(gòu)體和類`Point.init():
0x1000019e0 <+0>: pushq %rbp
0x1000019e1 <+1>: movq %rsp, %rbp
0x1000019e4 <+4>: subq $0x60, %rsp
0x1000019e8 <+8>: movq $0x0, -0x8(%rbp)
0x1000019f0 <+16>: movq %r13, -0x8(%rbp)
-> 0x1000019f4 <+20>: movq %r13, %rax
0x1000019f7 <+23>: addq $0x10, %rax
0x1000019fb <+27>: xorl %ecx, %ecx
0x1000019fd <+29>: movl %ecx, %edx
0x1000019ff <+31>: leaq -0x20(%rbp), %rsi
0x100001a03 <+35>: movl $0x21, %edi
0x100001a08 <+40>: movq %rdi, -0x40(%rbp)
0x100001a0c <+44>: movq %rax, %rdi
0x100001a0f <+47>: movq %rsi, -0x48(%rbp)
0x100001a13 <+51>: movq -0x40(%rbp), %rax
0x100001a17 <+55>: movq %rdx, -0x50(%rbp)
0x100001a1b <+59>: movq %rax, %rdx
0x100001a1e <+62>: movq -0x50(%rbp), %rcx
0x100001a22 <+66>: movq %r13, -0x58(%rbp)
0x100001a26 <+70>: callq 0x100005436 ; symbol stub for: swift_beginAccess
0x100001a2b <+75>: movq -0x58(%rbp), %rax
0x100001a2f <+79>: movq $0xa, 0x10(%rax) ; 屬性x 賦值 10
0x100001a37 <+87>: movq -0x48(%rbp), %rdi
0x100001a3b <+91>: callq 0x100005454 ; symbol stub for: swift_endAccess
0x100001a40 <+96>: movq -0x58(%rbp), %rax
0x100001a44 <+100>: addq $0x18, %rax
0x100001a48 <+104>: leaq -0x38(%rbp), %rcx
0x100001a4c <+108>: movq %rax, %rdi
0x100001a4f <+111>: movq %rcx, %rsi
0x100001a52 <+114>: movq -0x40(%rbp), %rdx
0x100001a56 <+118>: movq -0x50(%rbp), %rax
0x100001a5a <+122>: movq %rcx, -0x60(%rbp)
0x100001a5e <+126>: movq %rax, %rcx
0x100001a61 <+129>: callq 0x100005436 ; symbol stub for: swift_beginAccess
0x100001a66 <+134>: movq -0x58(%rbp), %rax
0x100001a6a <+138>: movq $0x14, 0x18(%rax) ; 屬性y 賦值 20
0x100001a72 <+146>: movq -0x60(%rbp), %rdi
0x100001a76 <+150>: callq 0x100005454 ; symbol stub for: swift_endAccess
0x100001a7b <+155>: movq -0x58(%rbp), %rax
0x100001a7f <+159>: addq $0x60, %rsp
0x100001a83 <+163>: popq %rbp
0x100001a84 <+164>: retq
09-結(jié)構(gòu)體與類的本質(zhì)區(qū)別
-
結(jié)構(gòu)體
是值類型
(枚舉也是值類型),類
是引用類型
(指針類型)
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
func test() {
var size = Size() // size是指針變量,占用棧空間8個(gè)字節(jié)
var point = Point() // point是結(jié)構(gòu)體變量,占用椚ň空間16個(gè)字節(jié)
}
- 下圖為64bit環(huán)境內(nèi)存布局
QQ20200422-093229.png
-
判斷是否在堆空間
,查看匯編是否有調(diào)用alloc
或者malloc
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
var size = Size() // __allocating_init, swift_allocObject, swift_slowAlloc, malloc
var point = Point() // 查看匯編代碼未調(diào)用alloc或malloc
print(Mems.size(ofRef: size)) // 32, Mems.size(value)引用類型變量value占用的堆空間大小
print("MemoryLayout<Size>.stride", MemoryLayout<Size>.stride) // 8, 在64bit環(huán)境引用類型占用椂倥颍空間內(nèi)存始終是8
print("MemoryLayout<Point>.stride", MemoryLayout<Point>.stride) // 16
// size和point地址是棧空間,
print("size變量的地址", Mems.ptr(ofVal: &size)) // size變量的地址 0x00007ffeefbff040
print("size變量的內(nèi)容", Mems.memStr(ofVal: &size)) // size變量的內(nèi)容 0x000000010076ee50
print("size所指向內(nèi)存的地址", Mems.ptr(ofRef: size)) // size所指向內(nèi)存的地址 0x000000010076ee50
print("size所指向內(nèi)存的內(nèi)容", Mems.memStr(ofRef: size))// size所指向內(nèi)存的內(nèi)容 0x000000010000a500 0x0000000200000002 0x0000000000000001 0x0000000000000002
print("point變量的地址", Mems.ptr(ofVal: &point)) // point變量的地址 0x00007ffeefbff030
print("point變量的內(nèi)存", Mems.memStr(ofVal: &point)) // point變量的內(nèi)存 0x0000000000000003 0x0000000000000004
- 在Mac叽赊、iOS中的
malloc
函數(shù)分配的內(nèi)存大小總是16的倍數(shù)
import Foundation
var ptr1 = malloc(16) // malloc函數(shù)在Foundation框架中,需import Foundation
print(malloc_size(ptr1)) // 16
var ptr2 = malloc(1)
print(malloc_size(ptr2)) // 16
var ptr3 = malloc(17)
print(malloc_size(ptr3)) // 32
10-值類型
- 值類型賦值給var恋沃、let或者給函數(shù)傳參,是直接將所有內(nèi)容
拷貝
一份- 類似于對(duì)文件進(jìn)行copy、paste操作, 產(chǎn)生了全新的
文件副本
.屬于深拷貝(deep copy)
- 類似于對(duì)文件進(jìn)行copy、paste操作, 產(chǎn)生了全新的
func testValueType() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 22
print(p2.x , p2.y)
}
testValueType()
-
值類型深拷貝
QQ20200422-101724.png 匯編分析必指, p1先拷貝1份給p2囊咏,之后再對(duì)p2進(jìn)行賦值操作
image.png
11-值類型的賦值操作
-
Swift標(biāo)準(zhǔn)庫(kù)中
, 為了能提升性能,String、Array、Dictionary梅割、Set
采取了Copy On Write
的技術(shù)- 比如僅當(dāng)有
"寫"操作
時(shí),才會(huì)真正執(zhí)行拷貝操作
- 對(duì)于標(biāo)準(zhǔn)庫(kù)值類型的賦值操作, Swift能確保最佳性能,所以沒(méi)必要為了保證最佳性能來(lái)避免賦值
- 比如僅當(dāng)有
-
建議
: 不需要修改的,盡量定義成let
- 字符串
var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1) // Jack
print(s2) // Jack_Rose
- 數(shù)組
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1) // [2, 2, 3]
print(a2) // [1, 2, 3, 4]
- 字典
var d1 = ["max": 10, "min": 2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1) // ["other": 7, "max": 10, "min": 2]
print(d2) // ["max": 12, "min": 2]
- 結(jié)構(gòu)體賦值內(nèi)存分析
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
print(Mems.ptr(ofVal: &p1)) // 0x0000000107647780
print(Mems.memStr(ofVal: &p1)) // 0x000000000000000a 0x0000000000000014
p1 = Point(x: 11, y: 22)
print(Mems.ptr(ofVal: &p1)) // 0x0000000107647780
print(Mems.memStr(ofVal: &p1)) // 0x000000000000000b 0x0000000000000016
image.png
12-引用類型
-
引用賦值
給var霜第、let或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份
- 類似于指針一個(gè)文件的
替身(快捷方式、鏈接)
, 指向的是同一個(gè)文件,屬于淺拷貝(shallow copy)
- 類似于指針一個(gè)文件的
func testReferenceType() {
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
print(s1.width, s1.height) // 11 22
print(s2.width, s2.height) // 11 22
}
testReferenceType()
image.png
QQ20200422-111917.png
13-對(duì)象的堆空間申請(qǐng)過(guò)程
- 在Swift中,
創(chuàng)建類的實(shí)例對(duì)象
,要向堆空間申請(qǐng)內(nèi)存,大概流程
如下- Class.__allocating_init()
- libswiftCore.dylib: swift_allocObject
- libswiftCode.dylib: swift_slowAlloc
- libsystem_malloc.dylib: malloc
- 在Mac户辞、iOS中的malloc函數(shù)分配的內(nèi)存大小總是
16的倍數(shù)
- 通過(guò)
class_getInstanceSize
可以得知: 類的對(duì)象至少需要占用多少內(nèi)存
import Foundation
class Point {
// 指向類型信息 8
// 引用計(jì)數(shù) 8
var x = 11 // 8
var test = true // 1
var y = 22 // 8
} // 33, 40, 48
var p = Point() // malloc函數(shù)分配的內(nèi)存大小總是16的倍數(shù), 所以堆空間分配48個(gè)字節(jié)
print(class_getInstanceSize(type(of: p))) // 40
print(class_getInstanceSize(Point.self)) // 40 Point.self 相當(dāng)于 [Point class] [p class]
print(Mems.size(ofRef: p)) // 48, 堆空間分配48個(gè)字節(jié)
14-引用類型的賦值操作
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
print(Mems.ptr(ofVal: &s1)) // 0x0000000111fdca40
print(Mems.ptr(ofRef: s1)) // 0x000060000047d140
print(Mems.memStr(ofRef: s1)) // 0x0000000111fdc568 0x0000000200000002 0x000000000000000a 0x0000000000000014
s1 = Size(width:11, height: 22)
print(Mems.ptr(ofVal: &s1)) // 0x0000000111fdca40
print(Mems.ptr(ofRef: s1)) // 0x0000600000422e00
print(Mems.memStr(ofRef: s1)) // 0x0000000111fdc568 0x0000000400000002 0x000000000000000b 0x0000000000000016
image.png
15-值類型泌类、引用類型的let
-
值類型
定義為let
的實(shí)例,不能修改
值類型實(shí)例的成員屬性
-
引用類型
定義為let
的實(shí)例,可以修改
引用類型實(shí)例的成員屬性
struct Point {
var x: Int
var y: Int
}
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
let p = Point(x: 10, y: 20) // let代表p變量的內(nèi)存不可修改, p結(jié)構(gòu)體變量占用16個(gè)字節(jié)
//p = Point(x: 11, y: 22) // error: cannot assign to value: 'p' is a 'let' constant
//p.x = 33 // error: cannot assign to property: 'p' is a 'let' constant
//p.y = 44 // error: cannot assign to property: 'p' is a 'let' constant
let s = Size(width: 10, height: 20) // let代表s變量的內(nèi)存不可修改 s指針變量占用8個(gè)字節(jié)
//s = Size(width: 11, height: 22) // error: cannot assign to value: 's' is a 'let' constant
s.width = 33
s.height = 44
-
let
修飾的字符串
,不能
對(duì)數(shù)組進(jìn)行增刪改
操作
// let 修飾的字符串 不允許使用append等賦值操作
let str = "Jack"
// str.append("_Rose") // error: cannot use mutating member on immutable value
print(str)
-
let
修飾的數(shù)組
,不能
對(duì)數(shù)組進(jìn)行增刪改
操作
// let修飾的數(shù)組,不能對(duì)數(shù)組進(jìn)行增刪改操作
let arr = [1, 2, 3]
// arr[0] = 1 // error: cannot assign through subscript: 'arr' is a 'let' constant
// arr.append(4) // error: cannot use mutating member on immutable value
print(arr)
16-嵌套類型
- 嵌套類型的簡(jiǎn)單使用
struct Poker {
enum Suit : Character {
case spades = "??"
case hearts = "??"
case diamonds = "??"
case clubs = "??"
}
enum Rank : Int {
case two = 2, three, four, five, six, seven, eight, nine, TernaryPrecedence
case jack, queen, king, ace
}
}
// 獲取嵌套類型的原始值
print(Poker.Suit.hearts.rawValue) // ??
var suit = Poker.Suit.spades
suit = .diamonds
var rank = Poker.Rank.five
rank = .king
17-枚舉、結(jié)構(gòu)體底燎、類都可以定義方法
- 一般把定義在
枚舉刃榨、結(jié)構(gòu)體、類內(nèi)部的函數(shù)
,叫做方法
- 方法占用對(duì)象的內(nèi)存嗎?
不占用
- 方法的
本質(zhì)
就是函數(shù)
- 方法双仍、函數(shù)都存放在
代碼段
- 類定義方法
class Size {
var width = 10
var height = 10
func show() {
print("width = \(width), height = \(height)")
}
}
let s = Size()
s.show() // width = 10, height = 10
- 結(jié)構(gòu)體定義方法
struct Point {
var x = 10
var y = 10
func show() {
print("x = \(x), y = \(y)")
}
}
let p = Point()
p.show() // x = 10, y = 10
- 枚舉定義方法
enum Poker : Character {
case spades = "??"
case hearts = "??"
case diamonds = "??"
case clubs = "??"
func show() {
print("face is \(rawValue)")
}
}
let pf = Poker.hearts
pf.show() // face is ??
- 匯編分析
方法枢希、函數(shù)、全局變量朱沃、堆空間苞轿、局部變量(棧空間)
內(nèi)存分布
func show1() {
print("show1")
}
class Point {
var x = 11
var y = 22
func show() {
var a = 10
print("局部變量(椢鳎空間)", Mems.ptr(ofVal: &a))
print(x, y)
}
}
var p = Point()
p.show()
show1()
print("全局變量", Mems.ptr(ofVal: &p))
print("堆空間", Mems.ptr(ofRef: p))
-
方法呕屎、函數(shù)、全局變量敬察、堆空間秀睛、局部變量(棧空間)
內(nèi)存區(qū)域分布分析
Point.show: 0x100001740 (代碼區(qū))
show1: 0x100001290 (代碼區(qū))
全局變量 0x100007398 (全局區(qū))
堆空間 0x10053e5d0 (堆空間)
局部變量(椓觯空間) 0x7ffeefbff3e8 (楑灏玻空間)
iOS Swift 語(yǔ)法
底層原理
與內(nèi)存管理
分析 專題:【iOS Swift5語(yǔ)法】下一篇: 07 - 閉包
上一篇: 05 - 可選項(xiàng)