Swift

Swift语法 Swift5 【07 - 闭包】

2020-05-08  本文已影响0人  Liwx

iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

00 - 汇编
01 - 基础语法
02 - 流程控制
03 - 函数
04 - 枚举
05 - 可选项
06 - 结构体和类
07 - 闭包
08 - 属性
09 - 方法
10 - 下标
11 - 继承
12 - 初始化器init
13 - 可选项


目录


01-闭包表达式

{
    (参数列表) -> 返回值类型 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]
@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]
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

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

(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
image.png image.png image.png
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
image.png image.png
// 闭包类似于以下类实例对象
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
image.png 0E79C50B-E1AF-4356-AF15-DDA93AB96608.png

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
// 非自动闭包
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 - 结构体和类


上一篇 下一篇

猜你喜欢

热点阅读