- 作者: Liwx
- 邮箱: 1032282633@qq.com
- 源码: 需要
源码
的同学, 可以在评论区
留下您的邮箱
iOS Swift 语法
底层原理
与内存管理
分析 专题:【iOS Swift5语法】00 - 汇编
01 - 基础语法
02 - 流程控制
03 - 函数
04 - 枚举
05 - 可选项
06 - 结构体和类
07 - 闭包
08 - 属性
09 - 方法
10 - 下标
11 - 继承
12 - 初始化器init
13 - 可选项
目录
- 01-闭包表达式
- 02-闭包表达式的简写
- 03-尾随闭包
- 04-示例-数组的排序
- 05-忽略参数
- 06-闭包(Closure)
- 07-注意
- 08-自动闭包
01-闭包表达式
Swift通过
func定义一个函数
,也可以通过闭包表达式定义一个函数
闭包表达式格式
{
(参数列表) -> 返回值类型 in
函数体代码
}
- 使用闭包调用时,
不需要
写参数标签
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
// 闭包调用不需要写参数标签
fn(10, 20)
- 类似匿名函数
{
(v1: Int, v2: Int) -> Int in
return v1 + v2
}(10, 20)
02-闭包表达式的简写
func exec(v1: Int, v2: Int , fn:(Int, Int) -> Int) {
print(fn(v1, v2))
}
// 完整写法
exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in
return v1 + v2
})
// 简写1 省略参数类型
exec(v1: 10, v2: 20, fn: { (v1, v2) -> Int in
return v1 + v2
})
// 简写2 省略 返回值, 参数类型, 小括号. 如果闭包体只有一个表达式, 省略return
exec(v1: 10, v2: 20, fn: {
v1, v2 in v1 + v2
})
// 简写3 省略参数, 使用$0 $1表示参数
exec(v1: 10, v2: 20, fn: {
$0 + $1
})
// 简写4 闭包参数直接写运算符
exec(v1: 10, v2: 20, fn: +)
// Xcode 自动生成 尾随闭包
exec(v1: 10, v2: 20) { (v1, v2) -> Int in
v1 + v2
}
03-尾随闭包
- 如果将一个很长的闭包表达式作为函数的
最后一个实参
, 使用尾随闭包
可以增强函数的可读性- 尾随闭包是一个被书写在函数调用
括号外面(后面)
的闭包表达式
- 尾随闭包是一个被书写在函数调用
- 尾随闭包简单使用
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
print(fn(v1, v2))
}
// 尾随闭包
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果闭包表达式是函数的
唯一实参
,而且使用了尾随闭包的语法,那就不需要在函数名后面写圆括号
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
// 简写
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }
// Xcode 自动生成
exec { (v1, v2) -> Int in
v1 + v2
}
04-示例-数组的排序
- 数组排序
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标准库sort函数
@inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
- 函数当做闭包调用
/// 数值比较
/// - Parameters:
/// - i1: 数值1
/// - i2: 数值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函数从小到大排序几种写法
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-忽略参数
- 使用
下划线_
忽略参数
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec { $0 + $1 }
//var fn = { $0 + $1 } // 编译器无法推断出类型 error: ambiguous use of operator '+'
var fn: (Int, Int) -> Int = { $0 + $1 }
exec(fn: fn)
// 忽略参数
exec {_, _ in 10} // 10
06-闭包(Closure)
- 关于闭包的定义
- 一个
函数
和他所捕获的变量/常量
环境组合起来,称为闭包
- 一般指定义在
函数内部
的函数
- 一般它
捕获的是外层函数
的局部变量/常量
- 一般指定义在
- 一个
func fn() -> () -> () {
var a = 10
func fn1() {
// 捕获的是外层函数的局部变量/常量
a = 10
}
return fn1
}
- 汇编分析函数内存布局
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
var fn3 = sum // 函数地址占用16字节,前8个字节存放函数地址, 后8个字节存放0
print(fn3(10, 20))
// rcx存放函数地址: 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)
函数- swift_allocObject(n) //
至少
需要n个字节,实际返回不一定是n个字节
- lldb:
bt命令打印函数调用栈
- swift_allocObject(n) //
class Test {
// 8, 8
var test1: Int = 0 // 8
var test2: Int = 0 // 8
}
var test = Test() // 断点调试, 内部会调用__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个字节
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
指令查看函数调用栈
(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
-
汇编窥探闭包的本质
- 内层函数
没有捕获
外层函数的局部变量/常量
- 内层函数
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部变量
var num = 0
func plus(_ i: Int) -> Int {
return i
}
return plus
}
var fn = getFn() // 返回的是函数地址,16字节, 低8字节: plus函数地址,高8字节:0
fn(1) // 1
fn(2) // 3
fn(3) // 6
fn(4) // 10
-
内层函数
有捕获
外层函数的局部变量/常量
- 闭包存储和类类似:
前8
字节:闭包信息
- 如果
捕获了外层函数的局部变量/常量
, 则底层会调用:swift_allocObject
函数, 分配堆空间
来存储需要捕获的变量/常量
-
函数指针
地址16个字节
,前8
个字节: 不是plus函数地址,是间接调用plus的函数地址
;有捕获
参数: num后8个字节为 堆空间的地址
,没捕获
: 后8个字节为 0
- 闭包存储和类类似:
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部变量
var num = 0 // num占用24字节, 8: 类型 8: 引用计数 8: num值
// 如果plus捕获了局部变量/常量, 可以认为plus函数传递了两个参数,i, 和num堆空间的地址值(用于plus内部访问堆空间num值)
func plus(_ i: Int) -> Int {
// 捕获的是外层函数的局部变量/常量
num += i
return num // 断点调试, 观察num值变化
}
// 如果有捕获的是外层函数的局部变量/常量, 在return之前会将局部变量num数据保存到分配的堆空间中
return plus // 断点调试, 目的是要在swift_allocObject执行后返回分配的堆空间地址, 之后一行语句打断点
} // 返回的plus和num形成了闭包
// 函数指针fn 地址16个字节,前8个字节: 不是plus函数地址,是间接调用plus的函数地址, 有捕获参数: num 后8个字节: 堆空间的地址, 没捕获: 后8个字节: 0
var fn = getFn() // 返回的是函数地址,16字节, 低8字节: 间接调用plus函数地址,高8字节:闭包地址
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
- 闭包
捕获一个参数
的内存布局
- 可以把
闭包
想象成是一个类的实例对象
- 内存在
堆空间
-
捕获的局部变量/常量
就是对象的成员
(存储属性) - 组成
闭包的函数
就是类内部定义的方法
- 内存在
// 闭包类似于以下类实例对象
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
- 窥探闭包捕获多个参数
typealias Fn = (Int) -> Int
func getFn() -> Fn {
// 局部变量
var num = 0 // num占用24字节, 8: 类型 8: 引用计数 8: num值
var count = 0
// 如果plus捕获了局部变量/常量, 可以认为plus函数传递了两个参数,i, 和num堆空间的地址值(用于plus内部访问堆空间num值)
func plus(_ i: Int) -> Int {
// 捕获的是外层函数的局部变量/常量
num += i
count += 1
return num // 断点调试, 观察num值变化
}
// 如果有捕获的是外层函数的局部变量/常量, 在return之前会将局部变量num数据保存到分配的堆空间中
return plus // 断点调试, 目的是要在swift_allocObject执行后返回分配的堆空间地址, 之后一行语句打断点
} // 返回的plus和num形成了闭包
// 闭包地址16位,前8个字节: 不是plus函数地址,是间接调用plus的函数地址, 有捕获参数: num 后8个字节: 堆空间的地址, 没捕获: 后8个字节: 0
var fn = getFn()
fn(1) // 1
fn(2) // 3
fn(3) // 6
fn(4) // 10
- 闭包
捕获多个参数
的内存布局
07-注意
- 如果
返回值是函数类型
, 那么参数的修饰要保持统一
// 返回值是函数类型为(inout Int) -> Void
func add(_ num: Int) -> (inout Int) -> Void {
func plus(v: inout Int) { // 参数也必须修饰为inout Int类型
v += num
}
return plus
}
var num = 5
add(20)(&num) // plus参数类型是inout Int, 调用是需要加上&符号
print(num) // 25
08-自动闭包
- 非自动闭包
// 如果第1个数大于0,返回第1个数,否则返回第2个数
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
return v1 > 0 ? v1 : v2
}
getFirstPositive(10, 20) // 10
getFirstPositive(-2, 20) // 20
getFirstPositive(0, -4) // -4
// 改成函数类型的参数,可以让v2延迟加载
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
print("getFirstPositive1")
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, {20}) // 20
getFirstPositive(-4) {20} // 20
- 自动闭包的使用
-
@autoclosure
会自动将n封装成闭包{ n }
- @autoclosure 只支持
() -> T
格式的参数 - @autoclosure
并非
只支持最后一个参数 - 空合并运算符
??
使用了@autoclosure技术
-
有@autoclosure
,无@autoclosure,
构成了函数重载
- 为了
避免与期望冲突
,使用了@autoclosure
的地方最好明确注释清楚: 这个值会被推迟执行
-
// 非自动闭包
func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
print("getFirstPositive1")
return v1 > 0 ? v1 : v2()
}
// 自动闭包写法 函数重载
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
print("getFirstPositive2")
return v1 > 0 ? v1 : v2()
}
getFirstPositive(-4, 20) // 无需加大括号,@autoclosure 会自动将20封装成闭包{ 20 }
getFirstPositive(-4, {30}) // 调用getFirstPositive(_ v1: Int, _ v2: () -> Int), 30
iOS Swift 语法
底层原理
与内存管理
分析 专题:【iOS Swift5语法】下一篇: 08 - 属性
上一篇: 06 - 结构体和类