- 作者: Liwx
- 郵箱: 1032282633@qq.com
- 源碼: 需要
源碼
的同學(xué), 可以在評(píng)論區(qū)
留下您的郵箱
iOS Swift 語(yǔ)法
底層原理
與內(nèi)存管理
分析 專(zhuān)題:【iOS Swift5語(yǔ)法】00 - 匯編
01 - 基礎(chǔ)語(yǔ)法
02 - 流程控制
03 - 函數(shù)
04 - 枚舉
05 - 可選項(xiàng)
06 - 結(jié)構(gòu)體和類(lèi)
07 - 閉包
08 - 屬性
09 - 方法
10 - 下標(biāo)
11 - 繼承
12 - 初始化器init
13 - 可選項(xiàng)
目錄
- 01-閉包表達(dá)式
- 02-閉包表達(dá)式的簡(jiǎn)寫(xiě)
- 03-尾隨閉包
- 04-示例-數(shù)組的排序
- 05-忽略參數(shù)
- 06-閉包(Closure)
- 07-注意
- 08-自動(dòng)閉包
01-閉包表達(dá)式
Swift通過(guò)
func定義一個(gè)函數(shù)
,也可以通過(guò)閉包表達(dá)式定義一個(gè)函數(shù)
閉包表達(dá)式格式
{
(參數(shù)列表) -> 返回值類(lèi)型 in
函數(shù)體代碼
}
- 使用閉包調(diào)用時(shí),
不需要
寫(xiě)參數(shù)標(biāo)簽
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
// 閉包調(diào)用不需要寫(xiě)參數(shù)標(biāo)簽
fn(10, 20)
- 類(lèi)似匿名函數(shù)
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
02-閉包表達(dá)式的簡(jiǎn)寫(xiě)
func exec(v1: Int, v2: Int , fn:(Int, Int) -> Int) {
print(fn(v1, v2))
}
// 完整寫(xiě)法
exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in
return v1 + v2
})
// 簡(jiǎn)寫(xiě)1 省略參數(shù)類(lèi)型
exec(v1: 10, v2: 20, fn: { (v1, v2) -> Int in
return v1 + v2
})
// 簡(jiǎn)寫(xiě)2 省略 返回值, 參數(shù)類(lèi)型, 小括號(hào). 如果閉包體只有一個(gè)表達(dá)式, 省略return
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
// 簡(jiǎn)寫(xiě)3 省略參數(shù), 使用$0 $1表示參數(shù)
exec(v1: 10, v2: 20, fn: {
$0 + $1
})
// 簡(jiǎn)寫(xiě)4 閉包參數(shù)直接寫(xiě)運(yùn)算符
exec(v1: 10, v2: 20, fn: +)
// Xcode 自動(dòng)生成 尾隨閉包
exec(v1: 10, v2: 20) { (v1, v2) -> Int in
v1 + v2
}
03-尾隨閉包
- 如果將一個(gè)很長(zhǎng)的閉包表達(dá)式作為函數(shù)的
最后一個(gè)實(shí)參
, 使用尾隨閉包
可以增強(qiáng)函數(shù)的可讀性- 尾隨閉包是一個(gè)被書(shū)寫(xiě)在函數(shù)調(diào)用
括號(hào)外面(后面)
的閉包表達(dá)式
- 尾隨閉包是一個(gè)被書(shū)寫(xiě)在函數(shù)調(diào)用
- 尾隨閉包簡(jiǎn)單使用
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
// 尾隨閉包
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果閉包表達(dá)式是函數(shù)的
唯一實(shí)參
,而且使用了尾隨閉包的語(yǔ)法,那就不需要在函數(shù)名后面寫(xiě)圓括號(hào)
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
// 簡(jiǎn)寫(xiě)
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }
// Xcode 自動(dòng)生成
exec { (v1, v2) -> Int in
v1 + v2
}
04-示例-數(shù)組的排序
- 數(shù)組排序
var arr = [10, 1, 4, 20, 99]
// 從小到大
arr.sort() // [1, 4, 10, 20, 99]
// 自定義排序
// 從大到小
arr.sort { (v1, v2) -> Bool in
v1 > v2
}
print(arr) // [99, 20, 10, 4, 1]
- Swift標(biāo)準(zhǔn)庫(kù)sort函數(shù)
@inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
- 函數(shù)當(dāng)做閉包調(diào)用
/// 數(shù)值比較
/// - Parameters:
/// - i1: 數(shù)值1
/// - i2: 數(shù)值2
/// - Returns: true: i1排在i2前面, false: i1排在i2后面
func cmp(i1: Int, i2: Int) -> Bool {
return i1 > i2
}
var nums = [11, 2, 18, 6, 5, 68, 45]
nums.sort(by: cmp)
print(nums) // [68, 45, 18, 11, 6, 5, 2]
- sort函數(shù)從小到大排序幾種寫(xiě)法
nums.sort(by: {
(i1: Int, i2: Int) -> Bool in
return i1 < i2
})
nums.sort { (i1, i2) -> Bool in
i1 < i2
}
nums.sort(by: {i1, i2 in return i1 < i2})
nums.sort(by: {i1, i2 in i1 < i2})
nums.sort(by: { $0 < $1 })
nums.sort(by: <)
nums.sort() { $0 < $1 }
nums.sort { $0 < $1 }
print(nums) // [2, 5, 6, 11, 18, 45, 68]
05-忽略參數(shù)
- 使用
下劃線(xiàn)_
忽略參數(shù)
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec { $0 + $1 }
//var fn = { $0 + $1 } // 編譯器無(wú)法推斷出類(lèi)型 error: ambiguous use of operator '+'
var fn: (Int, Int) -> Int = { $0 + $1 }
exec(fn: fn)
// 忽略參數(shù)
exec {_, _ in 10} // 10
06-閉包(Closure)
- 關(guān)于閉包的定義
- 一個(gè)
函數(shù)
和他所捕獲的變量/常量
環(huán)境組合起來(lái),稱(chēng)為閉包
- 一般指定義在
函數(shù)內(nèi)部
的函數(shù)
- 一般它
捕獲的是外層函數(shù)
的局部變量/常量
- 一般指定義在
- 一個(gè)
func fn() -> () -> () {
var a = 10
func fn1() {
// 捕獲的是外層函數(shù)的局部變量/常量
a = 10
}
return fn1
}
- 匯編分析函數(shù)內(nèi)存布局
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn3 = sum // 函數(shù)地址占用16字節(jié),前8個(gè)字節(jié)存放函數(shù)地址, 后8個(gè)字節(jié)存放0
print(fn3(10, 20))
// rcx存放函數(shù)地址: 0x100002524 + 0x146c = 0x100003990
0x10000251d <+6125>: leaq 0x146c(%rip), %rcx ; _7_閉包.sum(Swift.Int, Swift.Int) -> Swift.Int at main.swift:292
0x100002524 <+6132>: movq %rcx, 0x2f15(%rip) ; _7_閉包.fn3 : (Swift.Int, Swift.Int) -> Swift.Int
0x10000252b <+6139>: movq $0x0, 0x2f12(%rip) ; _7_閉包.fn3 : (Swift.Int, Swift.Int) -> Swift.Int + 4
- 匯編分析
swift_allocObject(n)
函數(shù)- swift_allocObject(n) //
至少
需要n個(gè)字節(jié),實(shí)際返回不一定是n個(gè)字節(jié)
- lldb:
bt命令打印函數(shù)調(diào)用棧
- swift_allocObject(n) //
class Test {
// 8, 8
var test1: Int = 0 // 8
var test2: Int = 0 // 8
}
var test = Test() // 斷點(diǎn)調(diào)試, 內(nèi)部會(huì)調(diào)用__allocating_init
07-閉包`Test.__allocating_init():
-> 0x1000038d0 <+0>: pushq %rbp
0x1000038d1 <+1>: movq %rsp, %rbp
0x1000038d4 <+4>: pushq %r13
0x1000038d6 <+6>: pushq %rax
0x1000038d7 <+7>: movl $0x20, %esi ; 分配32個(gè)字節(jié)
0x1000038dc <+12>: movl $0x7, %edx
0x1000038e1 <+17>: movq %r13, %rdi
0x1000038e4 <+20>: callq 0x100003c1e ; symbol stub for: swift_allocObject
0x1000038e9 <+25>: movq %rax, %r13
0x1000038ec <+28>: callq 0x100003900 ; _7_閉包.Test.init() -> _7_閉包.Test at main.swift:94
0x1000038f1 <+33>: addq $0x8, %rsp
0x1000038f5 <+37>: popq %r13
0x1000038f7 <+39>: popq %rbp
0x1000038f8 <+40>: retq
- 使用
bt
指令查看函數(shù)調(diào)用棧
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
* frame #0: 0x00007fff68bf6ce0 libsystem_malloc.dylib`malloc
frame #1: 0x00007fff6835cca9 libswiftCore.dylib`swift_slowAlloc + 25
frame #2: 0x00007fff6835cd27 libswiftCore.dylib`swift_allocObject + 39
frame #3: 0x0000000100001a5d 07-閉包`main at main.swift:203:36
frame #4: 0x00007fff68a40cc9 libdyld.dylib`start + 1
-
匯編窺探閉包的本質(zhì)
- 內(nèi)層函數(shù)
沒(méi)有捕獲
外層函數(shù)的局部變量/常量
- 內(nèi)層函數(shù)
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部變量
var num = 0
func plus(_ i: Int) -> Int {
return i
}
return plus
}
var fn = getFn() // 返回的是函數(shù)地址,16字節(jié), 低8字節(jié): plus函數(shù)地址,高8字節(jié):0
fn(1) // 1
fn(2) // 3
fn(3) // 6
fn(4) // 10
image.png
image.png
image.png
-
內(nèi)層函數(shù)
有捕獲
外層函數(shù)的局部變量/常量
- 閉包存儲(chǔ)和類(lèi)類(lèi)似:
前8
字節(jié):閉包信息
- 如果
捕獲了外層函數(shù)的局部變量/常量
, 則底層會(huì)調(diào)用:swift_allocObject
函數(shù), 分配堆空間
來(lái)存儲(chǔ)需要捕獲的變量/常量
-
函數(shù)指針
地址16個(gè)字節(jié)
,前8
個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址
;有捕獲
參數(shù): num后8個(gè)字節(jié)為 堆空間的地址
,沒(méi)捕獲
: 后8個(gè)字節(jié)為 0
- 閉包存儲(chǔ)和類(lèi)類(lèi)似:
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部變量
var num = 0 // num占用24字節(jié), 8: 類(lèi)型 8: 引用計(jì)數(shù) 8: num值
// 如果plus捕獲了局部變量/常量, 可以認(rèn)為plus函數(shù)傳遞了兩個(gè)參數(shù),i, 和num堆空間的地址值(用于plus內(nèi)部訪(fǎng)問(wèn)堆空間num值)
func plus(_ i: Int) -> Int {
// 捕獲的是外層函數(shù)的局部變量/常量
num += i
return num // 斷點(diǎn)調(diào)試, 觀(guān)察num值變化
}
// 如果有捕獲的是外層函數(shù)的局部變量/常量, 在return之前會(huì)將局部變量num數(shù)據(jù)保存到分配的堆空間中
return plus // 斷點(diǎn)調(diào)試, 目的是要在swift_allocObject執(zhí)行后返回分配的堆空間地址, 之后一行語(yǔ)句打斷點(diǎn)
} // 返回的plus和num形成了閉包
// 函數(shù)指針fn 地址16個(gè)字節(jié),前8個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址, 有捕獲參數(shù): num 后8個(gè)字節(jié): 堆空間的地址, 沒(méi)捕獲: 后8個(gè)字節(jié): 0
var fn = getFn() // 返回的是函數(shù)地址,16字節(jié), 低8字節(jié): 間接調(diào)用plus函數(shù)地址,高8字節(jié):閉包地址
print(MemoryLayout.stride(ofValue: fn1)) // 16
fn(1) // 1 0x0000000100004078 0x0000000000000002 0x0000000000000001 0x0000000000000000
fn(2) // 3 0x0000000100004078 0x0000000000000002 0x0000000000000003 0x0000000000000000
fn(3) // 6 0x0000000100004078 0x0000000000000002 0x0000000000000006 0x0000000000000000
fn(4) // 10 0x0000000100004078 0x0000000000000002 0x000000000000000a 0x0000000000000000
image.png
image.png
- 閉包
捕獲一個(gè)參數(shù)
的內(nèi)存布局
D8FB8159-0DC8-47B0-9B55-32D5293FB403.png
- 可以把
閉包
想象成是一個(gè)類(lèi)的實(shí)例對(duì)象
- 內(nèi)存在
堆空間
-
捕獲的局部變量/常量
就是對(duì)象的成員
(存儲(chǔ)屬性) - 組成
閉包的函數(shù)
就是類(lèi)內(nèi)部定義的方法
- 內(nèi)存在
// 閉包類(lèi)似于以下類(lèi)實(shí)例對(duì)象
class Closure {
var num = 0
func plus(_ i: Int) -> Int {
num += i
return num
}
}
var cs1 = Closure()
var cs2 = Closure()
cs1.plus(1) // 1
cs2.plus(2) // 2
cs1.plus(3) // 4
cs2.plus(4) // 6
cs1.plus(5) // 9
cs2.plus(6) // 12
- 窺探閉包捕獲多個(gè)參數(shù)
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部變量
var num = 0 // num占用24字節(jié), 8: 類(lèi)型 8: 引用計(jì)數(shù) 8: num值
var count = 0
// 如果plus捕獲了局部變量/常量, 可以認(rèn)為plus函數(shù)傳遞了兩個(gè)參數(shù),i, 和num堆空間的地址值(用于plus內(nèi)部訪(fǎng)問(wèn)堆空間num值)
func plus(_ i: Int) -> Int {
// 捕獲的是外層函數(shù)的局部變量/常量
num += i
count += 1
return num // 斷點(diǎn)調(diào)試, 觀(guān)察num值變化
}
// 如果有捕獲的是外層函數(shù)的局部變量/常量, 在return之前會(huì)將局部變量num數(shù)據(jù)保存到分配的堆空間中
return plus // 斷點(diǎn)調(diào)試, 目的是要在swift_allocObject執(zhí)行后返回分配的堆空間地址, 之后一行語(yǔ)句打斷點(diǎn)
} // 返回的plus和num形成了閉包
// 閉包地址16位,前8個(gè)字節(jié): 不是plus函數(shù)地址,是間接調(diào)用plus的函數(shù)地址, 有捕獲參數(shù): num 后8個(gè)字節(jié): 堆空間的地址, 沒(méi)捕獲: 后8個(gè)字節(jié): 0
var fn = getFn()
fn(1) // 1
fn(2) // 3
fn(3) // 6
fn(4) // 10
image.png
0E79C50B-E1AF-4356-AF15-DDA93AB96608.png
- 閉包
捕獲多個(gè)參數(shù)
的內(nèi)存布局
EBF109E8-74CC-4833-A9F1-576426C6E48F.png
07-注意
- 如果
返回值是函數(shù)類(lèi)型
, 那么參數(shù)的修飾要保持統(tǒng)一
// 返回值是函數(shù)類(lèi)型為(inout Int) -> Void
func add(_ num: Int) -> (inout Int) -> Void {
func plus(v: inout Int) { // 參數(shù)也必須修飾為inout Int類(lèi)型
v += num
}
return plus
}
var num = 5
add(20)(&num) // plus參數(shù)類(lèi)型是inout Int, 調(diào)用是需要加上&符號(hào)
print(num) // 25
08-自動(dòng)閉包
- 非自動(dòng)閉包
// 如果第1個(gè)數(shù)大于0,返回第1個(gè)數(shù),否則返回第2個(gè)數(shù)
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4) // -4
// 改成函數(shù)類(lèi)型的參數(shù),可以讓v2延遲加載
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
print("getFirstPositive1")
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, {20}) // 20
getFirstPositive(-4) {20} // 20
- 自動(dòng)閉包的使用
-
@autoclosure
會(huì)自動(dòng)將n封裝成閉包{ n }
- @autoclosure 只支持
() -> T
格式的參數(shù) - @autoclosure
并非
只支持最后一個(gè)參數(shù) - 空合并運(yùn)算符
??
使用了@autoclosure技術(shù)
-
有@autoclosure
,無(wú)@autoclosure,
構(gòu)成了函數(shù)重載
- 為了
避免與期望沖突
,使用了@autoclosure
的地方最好明確注釋清楚: 這個(gè)值會(huì)被推遲執(zhí)行
-
// 非自動(dòng)閉包
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
print("getFirstPositive1")
return v1 > 0 ? v1 : v2()
}
// 自動(dòng)閉包寫(xiě)法 函數(shù)重載
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
print("getFirstPositive2")
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20) // 無(wú)需加大括號(hào),@autoclosure 會(huì)自動(dòng)將20封裝成閉包{ 20 }
getFirstPositive(-4, {30}) // 調(diào)用getFirstPositive(_ v1: Int, _ v2: () -> Int), 30
iOS Swift 語(yǔ)法
底層原理
與內(nèi)存管理
分析 專(zhuān)題:【iOS Swift5語(yǔ)法】下一篇: 08 - 屬性
上一篇: 06 - 結(jié)構(gòu)體和類(lèi)