Swift函数类型和闭包

函数类型

  1. 函数类型定义

    函数类型形式:(参数类型1,参数类型2,…) -> 返回值类型

    函数类型是一种引用类型,类似于函数指针。可以将函数类型应用于任何使用类型的地方:变量、参数、返回值。

    函数类型实例化支持:全局函数、嵌套函数、成员函数(实例方法和静态方法)

  2. 函数的内存模型

    示例说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    func add(a:Double,b:Double) ->Double{
    return a+b
    }

    func devide(a:Double, b:Double) ->Double{
    return a/b
    }

    func multiply(a:Double,b:Double)->Double{
    return a*b
    }
    func minus(a:Double,b:Double)->Double{
    return a-b
    }

    class Matrix {
    var row = 0.0
    var column = 0.0
    init(row:Double, column:Double) {
    self.row = row
    self.column = column
    }

    func process(x:Double, y:Double) -> Double {
    return x * row + y * column
    }

    static func invoke(x:Double, y:Double) ->Double{
    return x * x + y * y
    }
    }

    var compute:(Double,Double) -> Double
    compute = add//指向全局函数
    let result1 = compute(10,20)
    print(result1)

    compute = devide//指向全局函数
    let result2 = compute(10,20)
    print(result2)

    var matrix = Matrix(row: 10, column: 20)
    compute = matrix.process//指向实例方法
    compute(10,20)
    compute = Matrix.invoke//指向静态方法
    compute(30,40)

    compute的内存模型

    在上面的例子中,compute是一个引用类型,在栈上有一个指针,指向堆上的一个对象。堆上的对象包含两个部分,一个是对象指针,一个函数指针。

    函数类型的内存模型

    如果compute是一个全局函数(adddevide),那么它的对象指针就是空的,函数指针就是这个函数(add或devide)的地址(任何一个函数编译之后加载到内存中后,都有一个函数的入口点地址);

    compute = add就是把add函数的入口点地址赋值给compute的函数指针;

    compute = matrix.process,对象指针指向matrix,函数指针指向process的入口点地址,process函数有一个隐含参数self,其实就是把对象指针指向了self。

    只有当compute指向实例方法的时候,对象指针才会指向实例;当compute指向全局函数、嵌套函数、静态方法的时候,对象指针都是空的。

    compute指向matrix.process时,可以用伪码理解为:

    1
    2
    3
    4
    compute = matrix.process//实例方法
    // compute.object = matrix
    // compute.method = &process
    compute(10,20)//JMP compute.method
  3. 闭包的理解

    闭包是函数类型的实例,是一段自包含的代码块,可被用于函数类型的变量、参数或返回值。

    三种闭包形式:

    • 全局函数:具名函数,但不捕获任何值
    • 嵌套函数:在函数内部嵌套定义具名函数,可以捕获包含函数中的值
    • 闭包表达式:匿名函数类型实例,不具名的代码块,可捕获上下文的值

    闭包是引用类型,闭包变量拷贝具有引用语义

    闭包和函数类型实例拥有同样的内存模型。

    闭包表达式定义:

    1
    2
    3
    {(参数类型1,参数类型2...) -> 返回值类型 in
    语句块
    }

    闭包表达式的几种简化缩写形式:

    • 自动类型推断:省略参数类型和返回值类型
    • 单表达式闭包可以省略return关键词
    • 盛勇参数缩略形式$0,​$1…省略参数声明和in
    • 将操作符函数自动推导为函数类型
    • 尾随闭包:当闭包表达式为函数最后一个参数时,可将其写在括号后
    • 自动闭包:不接受任何参数,直接返回表达式的值,允许延迟计算
  4. 函数类型与闭包的变量捕获

    函数类型和闭包可以捕获其所在上下文的任何值:

    • 函数参数
    • 局部变量
    • 对象实例属性
    • 全局变量
    • 类的类型属性

    如果捕获值(参数和局部变量)生存周期小于闭包对象,系统会将被捕获的值封装在一个临时对象里,然后在闭包对象上创建一个对象指针,指向该临时对象。临时对象和闭包对象之间是强引用关系,生存周期跟随闭包对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //捕获参数或局部变量
    func addHandler(step:Int)->()->Int{
    var sum = 15
    return {
    sum += step
    return sum
    }
    }

    let addByTen = addHandler(step: 10)
    addByTen()//25
    addByTen()//35
    addByTen()//45

    let addBySix = addHandler(step: 6)
    addBySix()//21
    addBySix()//27
    addBySix()//33

    在上面的实例中,闭包捕获了sum和step,系统会将sum和step封装到一个对象中,上面的代码可以理解成下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //生成一个类来保存sum和step
    class AddHelper{
    var sum = 0
    var step = 0
    func add() -> Int {
    sum += step
    return sum
    }
    }

    func addHandlerComplete(step:Int)->()->Int{
    let sum = 0
    let obj = AddHelper()
    obj.sum = sum
    obj.step = step
    return obj.add
    }

    let addByTen = addHandlerComplete(step: 10)//闭包的对象指针指向obj,函数指针指向add函数的入口地址
    addByTen()//每次调用addByTen()时,obj不变,add改变了obj的sum
    addByTen()
    addByTen()

    addByTen这个闭包的内存模型:

    闭包捕获值的内存模型