美文网首页Swift
Swift 闭包(一)

Swift 闭包(一)

作者: 晨曦的简书 | 来源:发表于2022-01-24 19:01 被阅读0次

    函数介绍

    在我们分析闭包之前,先来分析一下函数。

    函数类型

    之前在代码的书写过程中,我们已经或多或少的接触过函数,函数本身也有自己的类型,它由形
    式参数类型,返回类型组成。下面我们看几个案例。

    • 案例 1
    func addTwoInts(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    
    var a = addTwoInts
    
    a(10, 20)
    

    这里定义了一个 addTwoInts 函数, 将 addTwoInts 赋值给 a 这个变量,然后就可以通过 a 这个变量来执行这个函数,这里 addTwoInts 代表的就是这个函数的类型。

    • 案例 2
    // 函数 1
    func addTwoInts(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    
    // 函数 2
    func addTwoInts(_ a: Double, _ b: Double) -> Double {
        return a + b
    }
    
    var a: (Double, Double) -> Double = addTwoInts
    
    a(10.0, 20.0)
    
    var b = a
    
    b(20.0, 30.0)
    

    swift 中如果出现同名函数的时候,如果要讲函数赋值给一个变量,需要指明变量的函数类型((Double, Double) -> Double),这时候 a 的值就是函数 2,分别执行 a(10.0, 20.0) 或者 b(20.0, 30.0) 代码的时候其实就是在调用函数 2 。 这里需要明确的一点是函数也是引用类型。

    函数的 Metadata

    对于函数来说,也有对应的 Metadata,下面我们在源码中来看一下。

    struct TargetFunctionTypeMetadata : public TargetMetadata<Runtime> {
      using StoredSize = typename Runtime::StoredSize;
      using Parameter = ConstTargetMetadataPointer<Runtime, swift::TargetMetadata>;
    
      TargetFunctionTypeFlags<StoredSize> Flags;
    
      /// 返回值类型
      ConstTargetMetadataPointer<Runtime, swift::TargetMetadata> ResultType;
    
      /// 参数类型,这里其实是一个连续的内存空间  
      Parameter *getParameters() { return reinterpret_cast<Parameter *>(this + 1); }
    
      const Parameter *getParameters() const {
        return reinterpret_cast<const Parameter *>(this + 1);
      }
    
      Parameter getParameter(unsigned index) const {
        assert(index < getNumParameters());
        /// 这里读取的时候是通过 index 获取
        return getParameters()[index];
      }
    }
    
    class TargetFunctionTypeFlags {
      enum : int_type {
        // 参数掩码 
        NumParametersMask      = 0x0000FFFFU,
        ConventionMask         = 0x00FF0000U,
        ConventionShift        = 16U,
        ThrowsMask             = 0x01000000U,
        ParamFlagsMask         = 0x02000000U,
        EscapingMask           = 0x04000000U,
        DifferentiableMask     = 0x08000000U,
        GlobalActorMask        = 0x10000000U,
        AsyncMask              = 0x20000000U,
        // 判断是否是逃逸类型 
        SendableMask           = 0x40000000U,
      };
    

    在以上源码中我们可以看到 TargetFunctionTypeMetadata 继承于 TargetMetadata,所以包含 kind 属性,除此之外 Flags 中可以看到参数掩码、SendableMask(是逃逸类型)等参数,ResultType 代表返回值类型,根据源码中的这些信息我们可以还原如下代码:

    func addTwoInts(_ a: Double, _ b: Double) -> Double {
        return a + b
    }
    
    struct TargetFunctionTypeMetadata{
        var kind: Int
        var flags: Int
        var arguments: ArgumentsBuffer<Any.Type>
    
        func numberArguments() -> Int{
            return self.flags & 0x0000FFFF
        }
    }
    
    struct ArgumentsBuffer<Element>{
        var element: Element
    
        mutating func buffer(n: Int) -> UnsafeBufferPointer<Element> {
            return withUnsafePointer(to: &self) {
                let ptr = $0.withMemoryRebound(to: Element.self, capacity: 1) { start in
                    return start
                }
                return UnsafeBufferPointer(start: ptr, count: n)
            }
        }
    
        mutating func index(of i: Int) -> UnsafeMutablePointer<Element> {
            return withUnsafePointer(to: &self) {
                return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
            }
        }
    }
    
    
    let value = type(of: addTwoInts)
    
    let functionType = unsafeBitCast(value as Any.Type, to: UnsafeMutablePointer<TargetFunctionTypeMetadata>.self)
    //输出参数的个数
    print(functionType.pointee.numberArguments())
    

    闭包

    什么是闭包

    闭包是一个捕获了上下文的常量或者是变量的函数。

    func makeIncrementer() -> () -> Int { 
      var runningTotal = 10
      func incrementer() -> Int {
        runningTotal += 1
        return runningTotal 
      }
      return incrementer 
    }
    
    var a = makeIncrementer
    

    这里可以看到,内部函数 incrementer 的生命周期明显比外部函数 makeIncrementer 的生命周期长,当执行 return incrementer 外部函数的生命周期就已经结束了,而内部函数被返回出去,并赋值给了变量 a,所以内部函数可以延时执行,而内部函数中用到了外部函数的变量 runningTotal,所以要对 runningTotal 进行捕获,这里 incrementer 就是闭包。

    • 闭包表达式
    { (param) -> (returnType) in 
      //do something
    }
    

    按照我们之前的知识积累,OC 中的 Block 其实是一个匿名函数,所以这个表达式要具备以下几个条件

    • 作用域(也就是大括号)
    • 参数和返回值
    • 函数体(in)之后的代码

    Swift 中的闭包即可以当做变量,也可以当做参数传递,这里我们来看一下下面的例子熟悉一 下:

    var closure : (Int) -> Int = { (age: Int) in 
      return age
    }
    

    同样的我们也可以把我们的闭包声明一个可选类型:

    //错误的写法
    var closure : (Int) -> Int? closure = nil
    //正确的写法
    var closure : ((Int) -> Int)? closure = nil
    

    还可以通过 let 关键字将闭包声明位一个常量(也就意味着一旦赋值之后就不能改变了)

    let closure: (Int) -> Int
    
    closure = {(age: Int) in
     return age
    }
    
    closure = {(age: Int) in
     return age
    }
    

    同时也可以作为函数的参数

    func test(param : () -> Int){ 
      print(param())
    }
    var age = 10
    test { () -> Int in
      age += 1
      return age 
    }
    

    尾随闭包

    当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很⻓,我们可以通过尾随
    闭包的书写方式来提高代码的可读性。

    func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
        return by(a, b, c)
    }
    
    test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
        return (item1 + item2 < item3)
    })
    

    以上是一个正常的闭包,通过尾随闭包的方式书写如下:

    func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
        return by(a, b, c)
    }
    
    test(10, 20, 30) {item1,item2,item3 in
        return (item1 + item2 < item3)
    }
    

    通过对比可以看到,以闭包的形式书写,代码会简洁很多。

    闭包表达式是 Swift 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处有很多:

    • 利用上下文推断参数和返回值类型
    • 单表达式可以隐士返回,既省略 return 关键字
    • 参数名称的简写(比如我们的 $0)
    • 尾随闭包表达式
    var array = [1, 2, 3]
    
    array.sort(by:{(item1:Int, item2:Int)-> Bool in return item1 < item2 })
    
    array.sort(by:{(item1,item2)-> Bool in return item1 < item2 })
    
    array.sort(by:{(item1,item2)in return item1 < item2 })
    
    array.sort{(item1,item2)in item1 < item2}
    
    array.sort{ return $0 < $1 }
    
    array.sort{ $0 < $1 }
    
    array.sort(by:<)
    

    虽然代码可以像上面一步步的进行省略简写,但是我们在书写代码的时候也要注意代码的可读性,省略的太多的话也会造成代码可读性变差。

    捕获值

    在讲闭包捕获值的时候,我们先来回顾一下 Block 捕获值的情形

    - (void)testBlock {
        NSInteger i = 1;
        void(^block)(void) = ^{
            NSLog(@"block %ld:", i);
        };
        i += 1;
        NSLog(@"before block %ld:", i); 
        block();
        NSLog(@"after block %ld:", i);
    }
    

    这里当我们对 i 的值进行加 1之后,再执行 block,可以看到 block 内部 i 的值并没有变化,那么如果我们想要外部的修改能够影响当前 block 内部捕获的值,我们只需要对当前的 i 添加 __block 修饰符就行。

    - (void)testBlock {
        __block NSInteger i = 1;
        void(^block)(void) = ^{
            NSLog(@"block %ld:", i);
        };
        i += 1;
        NSLog(@"before block %ld:", i);
        block();
        NSLog(@"after block %ld:", i);
    }
    

    那么我将以上代码用 swift 来书写,看一下:

    这里可以看到,用 swift 来写的话闭包中 i 的值是修改过的,那么我们将 swift 转成 SIL 代码来看一下。


    SIL 代码中可以看到,闭包执行的时候是直接获取全局变量 i 的地址,所以在外部修改 i 的值,在闭包中打印 i 的值也是修改过的。

    下面我们将以上代码放到函数中再转成 SIL 代码来看一下。

    func test() {
        var i = 1
        let closure = {
            print("closure:\(i)")
        }
    
        i += 1
    
        print("before closure:\(i)")
        closure()
        print("after closure:\(i)")
    }
    
    test();
    


    通过 SIL 代码可以看到,将闭包执行放到 test 函数中之后,变量 i 会被捕获到堆区。

    OCblock 分为全局 block 、堆区 block 、栈区 block 这三种情况,但是在 Swift 中闭包并没有这些概念,闭包本质上来讲也是一个函数。

    相关文章

      网友评论

        本文标题:Swift 闭包(一)

        本文链接:https://www.haomeiwen.com/subject/wbajhrtx.html