swift进阶十三:闭包

2020-12-24  本文已影响0人  markhetao

swift进阶 学习大纲

swift进阶八:闭包 & Runtime & Any等类型 中,我们介绍了闭包 捕获变量特性。本节,我们继续了解闭包:

  1. 什么是闭包
  2. 闭包结构
  3. 逃逸闭包非逃逸闭包
  4. 自动闭包
  5. 函数作形参

1. 什么是闭包

来自维基百科的解释:

1.1 全局函数

一种特殊闭包

func test(){
     print(a)
}

1.2 内嵌函数

也是闭包,会捕获外部变量
incrementer内嵌函数,也是一个闭包,捕获了上层函数runningTotal

func makeIncrementer() -> (()-> Int){
    var runningTotal = 10
    
    // 内嵌函数(也是一个闭包,捕获了runningTotal)
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    
    return incrementer
}

print(makeIncrementer())   // (()-> Int)匿名函数
print(makeIncrementer()()) // Int

打印值:

image.png

1.3 闭包表达式

  • 闭包是一个匿名函数
  • 所有代码都在花括号内
  • 参数返回类型in关键字之前
  • in之后是主体内容
{ (参数)-> 返回类型 in
    // do something
}
// 常量
let closure1: (Int) -> Int

// 变量
var closure2 : (Int) -> Int = { (age: Int) in 
     return age
}

// 参数传递
func test(params: ()->Int) {
    print(params())
}

var age = 10
// 执行(尾随闭包)
test { () -> Int in
    age += 1
    return age
}
// 可选值:在()外使用?声明
var closure: ((Int) -> Int)?

1.4 尾随闭包

闭包表达式作为函数最后一个参数时,可通过尾随闭包书写方式提高代码的可读性

func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item: Int, _ item3: Int) -> Bool) -> Bool{
    return by(a, b, c)
}

// 常规写法
test(10, 20, 30, by: { (_ itme1: Int, _ itme2: Int, _ itme3: Int) -> Bool in
    return itme1 + itme2 < itme3
})

// 快捷写法(小括号提到最后一个参数前)
test(10, 20, 30) { (_ itme1: Int, _ itme2: Int, _ itme3: Int) -> Bool in
    return itme1 + itme2 < itme3
}

// 最简洁写法 (入参直接使用$0 $1 $2代替,单行代码可省略return)
test(10, 20, 30) { $0 + $1 < $2 }

可以看到,最简洁写法看上去非常舒服语义表达清晰

闭包表达式swift语法。使用闭包表达式可以更简洁传递信息。好处多多:

  • 利用上下文推断参数返回类型
  • 单表达式可以隐式返回省略return关键字
  • 参数名称可以直接使用简写(如$0,$1,元组的$0.0)
  • 尾随闭包可以更简洁的表达
var array = [1, 2, 3]

// 1. 常规写法
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })

// 2. 省略类型 (根据上下文,可自动推断参数类型)
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })

// 3. 省略返回类型 (根据上下文,可自动推断返回类型)
array.sort(by: {(item1, item2) in return item1 < item2 })

// 4. 省略return (单行表达式,可省略return)
array.sort{(item1, item2) in item1 < item2 }

// 5. 参数简写 (使用$0 $1,按位置顺序获取参数)
array.sort{ return $0 < $1 }

// 6. 省略return (单行表达式,可省略return)
array.sort{ $0 < $1 }

// 7. 使用高阶函数,传递排序规则
array.sort(by: <)

2. 闭包的结构

func makeIncrementer() -> (()-> Int){
    var runningTotal = 10
    
    // 内嵌函数(也是一个闭包,捕获了runningTotal)
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    
    return incrementer
}

// 函数变量 (存储格式是怎样?)
var makeInc = makeIncrementer()

print(makeInc()) // 打印: 11
print(makeInc()) // 打印: 12
print(makeInc()) // 打印: 13

print(makeIncrementer()()) // 打印: 11
print(makeIncrementer()()) // 打印: 11
print(makeIncrementer()()) // 打印: 11

Q: 把函数赋值给变量,变量存储的是函数地址还是什么

image.png

拓展IRIR语法:

image.png
Swift编译流程
  1. swift源码编译为AST语法树
    swiftc -dump-ast HTPerson.swift > ast.swift
  2. 生成SIL源码
    swiftc -emit-sil HTPerson.swift > ./HTPerson.sil
  3. 生成IR中间代码
    swiftc -emit-ir HTPerson.swift > ir.swift
  4. 输出.o机器文件
    swiftc -emit-object HTPerson.swift

ir语法 👉 官方文档

  • 数组
[<elementnumber> x <elementtype>]  // [数组梳理 x 数组类型]
// example
alloca[24 x i8],align8 // 24个i8都是0
  • 结构体
%swift.refcounted = type { %swift.type*, i64 }   // { 指针类型,类型}
// example
%T = type {<type list>}  // 和C语言结构体类似 
  • 指针类型
<type> *
// example
i64*    // 64位的整形
  • getelementptr指针别名
    可通过getelementptr读取数组结构体的成员:
<result> = getelementptr <ty>, <ty>* <ptrval> {, [inrange] <ty> <id x>}*
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
  • getelementptr读取案例
    ( 创建一个c语言工程,main.c中加入测试代码
// example
struct munger_struct {
        int f1;
        int f2;
};

void munge(struct munger_struct *p) { 
   p[0].f1 = p[1].f1 + p[2].f2;  // 假设P是有3个元素的数组。就可以直接通过下标读取
}
 
struct munger_struct array[3];

int main(int argc, const char * argv[]) {
   munge(array); //调用
   return 0;
}
  • LLVM中,C语言需要使用Clang输出IR文件:
    clang -S -fobjc-arc -emit-llvm main.c
    image.png
func makeIncrementer() -> (()-> Int){
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    return incrementer
}
// 函数变量 (存储格式是怎样?)
var makeInc = makeIncrementer()

由于无法直接读取()-> Int函数的地址,所以利用结构体地址就是首元素地址的特性,将函数设为结构体第一个属性。通过读取结构体指针地址,获取到函数地址

struct FunctionData<T> {
    var pointer: UnsafeRawPointer
    var captureValue: UnsafePointer<T>
}

struct Refcounted {
    var pointer: UnsafeRawPointer
    var refCount: Int64
}

struct Type {
    var type: Int64
}

struct Box<T> {
    var refcounted: Refcounted
    var value: T  // 8字节类型,可由外部动态传入
}

struct BoxMetadata<T> {
    var refcounted: UnsafePointer<Refcounted>
    var undefA: UnsafeRawPointer
    var type: Type
    var undefB: Int32
    var undefC: UnsafeRawPointer
}

// 由于无法直接读取`()-> Int`函数的地址,所以利用结构体地址就是首元素地址的特性。将函数设为结构体第一个属性
struct VoidIntFunc {
    var f: ()->Int
}

func makeIncrementer() -> (()-> Int){
    var runningTotal = 10
    // 内嵌函数(也是一个闭包,捕获了runningTotal)
    func incrementer() -> Int {
        runningTotal += 1
        return runningTotal
    }
    
    return incrementer
}

// 使用struct包装的函数
var makeInc = VoidIntFunc(f: makeIncrementer())

// 读取指针
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)

// 初始化
ptr.initialize(to: makeInc)

// 将指针绑定为FunctionData<Box<Int>>类型,返回指针
let context = ptr.withMemoryRebound(to: FunctionData<Box<Int>>.self, capacity: 1) { $0.pointee }

// 打印指针值 与 内部属性值
print(context.pointer)   // 打印 0x00000001000054b0
print(context.captureValue.pointee.value) // 打印  10
image.png

验证打印的函数地址是否是makeIncrementer函数:

image.png
打开终端,输入命令:nm -p 编译后的machO文件地址 | grep 函数地址
// 例如:
nm -p /Users/asetku/Library/Developer/Xcode/DerivedData/Demo-bhpsxmnrzusvmeaotyclgmelcxpp/Build/Products/Debug/Demo | grep 00000001000054b0
  • 可以看到,该地址正是函数地址
    image.png
  • 使用xcrun swift-demangle XXXX命令,还原函数名
    image.png

所以:
函数事故引用类型,被赋值的变量记录了函数地址
函数内变量,会alloc开辟空间,调用前后,会retainrelease管理引用计数

拓展: 如果函数内部多个属性结构是怎样呢?

func makeIncrementer() -> (()-> Int){
   var aa = 10
   var bb = 20
   // 内嵌函数(也是一个闭包,捕获了runningTotal)
   func incrementer() -> Int {
       aa += 6
       bb += 9
       return bb
   }
   return incrementer
}
var makeInc = makeIncrementer()
  1. 基础结构没有变化


    image.png
  2. 相比起单变量,多了一个临时结构,把两个变量分别用指针记录:

    image.png
struct FunctionData<T> {
   var pointer: UnsafeRawPointer
   var captureValue: UnsafePointer<T>
}

struct Refcounted {
   var pointer: UnsafeRawPointer
   var refCount: Int64
}

struct Type {
   var type: Int64
}

struct Box<T> {
   var refcounted: Refcounted
   var value: T  // 8字节类型,可由外部动态传入
}

// 多了一个Box2结构,每个变量都是`Box`结构的对象
struct Box2<T> {
   var refcounted: Refcounted
   var value1: UnsafePointer<Box<T>>
   var value2: UnsafePointer<Box<T>>
}

struct BoxMetadata<T> {
   var refcounted: UnsafePointer<Refcounted>
   var undefA: UnsafeRawPointer
   var type: Type
   var undefB: Int32
   var undefC: UnsafeRawPointer
}

// 由于无法直接读取`()-> Int`函数的地址,所以利用结构体地址就是首元素地址的特性。将函数设为结构体第一个属性
struct VoidIntFunc {
   var f: ()->Int
}

func makeIncrementer() -> (()-> Int){
  var aa = 10
  var bb = 20
  // 内嵌函数(也是一个闭包,捕获了runningTotal)
  func incrementer() -> Int {
      aa += 6
      bb += 9
      return bb
  }
  return incrementer
}

// 使用struct包装的函数
var makeInc = VoidIntFunc(f: makeIncrementer())

// 读取指针
let ptr = UnsafeMutablePointer<VoidIntFunc>.allocate(capacity: 1)

// 初始化
ptr.initialize(to: makeInc)

// 将指针绑定为FunctionData<Box<Int>>类型,返回指针
let context = ptr.withMemoryRebound(to: FunctionData<Box2<Int>>.self, capacity: 1) { $0.pointee }

// 打印指针值 与 两个内部属性值
print(context.pointer)   // 打印: 0x00000001000050c0
print(context.captureValue.pointee.value1.pointee.value) // 打印:10
print(context.captureValue.pointee.value2.pointee.value) // 打印: 20
image.png
  • MachO文件中校验函数地址,确定是makeIncrementer函数:
    image.png

3. 逃逸闭包与非逃逸闭包

3.1 逃逸闭包

class HTPerson {
    
    var completion: ((Int)->Void)?
    
    func test1(handler: @escaping (Int)->Void) {
        
        // 1. 外部变量持有handler,handler的生命周期逃到了`makeIncrementer`函数外
        self.completion = handler
    }
    
    func test2(handler: @escaping (Int)->Void) {
        
        // 2. 异步线程延时调用handler,handler的生命周期逃到了`makeIncrementer`函数外
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            handler(10)
        }
    }
    
}

3.2 非逃逸闭包

系统默认闭包为非逃逸闭包 ,编译期自动加上@noescape声明,生命周期函数一致

func test(closure: (()->())) {}

4. 自动闭包

// 自动闭包`@autoclosure`声明
func testPrint(_ message: @autoclosure ()->String) {
    print(message())
}

func doSomeThing() -> String {
    return "吃了吗?"
}
// 入参传`函数`
testPrint(doSomeThing()) 
// 入参传`字符串`
testPrint("干啥呢")

5. 函数作形参

func testPrint(_ condition: Bool, _ message: String) {
    if condition {
        print("错误信息: \(message)")
    }
    print("结束")
}

func doSomeThing() -> String {
    print("执行了")
    // 耗时操作,从0到1000拼接成字符串
    return (0...1000).reduce("") { $0 + " \($1)"}
}

testPrint(false, doSomeThing())
image.png
func testPrint(_ condition: Bool, _ message: ()->String) {
    if condition {
        print("错误信息: \(message())")
    }
    print("结束")
}

func doSomeThing() -> String {
    print("执行了")
    // 耗时操作,从0到1000拼接成字符串
    return (0...1000).reduce("") { $0 + " \($1)"}
}

testPrint(false, doSomeThing())
image.png

注意

  • 上述只是演示函数作为入参没有单次调用时,可延时合适时期调用,避免资源提前计算
  • 但如果该资源会被多次调用,还是提前计算资源节省资源

至此,我们对闭包有了完整认识。下一节介绍Optional & Equatable & 访问控制

上一篇下一篇

猜你喜欢

热点阅读