4

swift 闭包(闭包表达式、尾随闭包、逃逸闭包、自动闭包)

 3 years ago
source link: https://juejin.cn/post/6972560642427486238
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
2021年06月11日 阅读 893

swift 闭包(闭包表达式、尾随闭包、逃逸闭包、自动闭包)

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

  • 闭包是独立的函数代码块,可以在代码中被传递和使用
  • 闭包和block的对比
    • swift中闭包与OC的 block 比较相似
    • swift中闭包是一个特殊函数,OC中block是一个匿名函数
    • 闭包和block都经常用于回调
    • block表达式
      类型:返回值类型(^block的名称)(block的参数)
      
      返回值(^block的名称)(形参列表) =  ^( 形参列表){
          // 执行代码
      };
      
      
      NSString* (^blockName)(NSString *str) = ^(NSString *str){
          return str;
      };
      
      blockName(@"block")
      复制代码
    • 闭包表达式
      类型:(参数)->(返回值类型)
      
      闭包名称 = { (形参列表) -> return 返回值 in
          // 执行代码  
      }
      let closure = { (str:String) -> String in
          return str
      }
      
      closure("closure")
      复制代码

  • 无参数无返回值
    let closure = {()->Void in
         print("closure")
    }
    
    closure()
    复制代码
  • 有参数无返回值
    let closure = {(str:String) -> Void in
        print(str)
    }
    
    closure("closure")
    复制代码
  • 无参数有返回值
    let closure = {() -> String in
        return "closure"
    }
    
    closure()
    复制代码
  • 有参数有返回值
    let closure = { (str:String) -> String in
        return str
    }
    
    closure("closure")
    复制代码

闭包表达式

下面例子通过使用几次迭代展示了 sorted(by:) 方法的定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能

  • 通过函数处理

    sorted(by:) 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔值来进行排序。排序闭包函数类型需为:(Int, Int) -> Bool

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted(by: callback)
    func callback(_ numA:Int, _ numB:Int) -> Bool {
        return numA < numB
    }
    print(numArr)    
    
    log:
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码
  • 通过闭包表达式处理

    内联闭包参数和返回值类型声明与callback(_:_:)函数类型声明相同

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted { (numA:Int, numB:Int) -> Bool in
        return numA < numB
    }
    print (numArr) 
    
    log:
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码
  • 根据上下文推断类型

    sorted(by:)方法被一个Int数组调用,因此其参数必须是 (Int, Int) -> Bool类型的函数,因为所有的类型都可以被正确推断,所以返回箭头(->)和围绕在参数周围的括号也可以被省略

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted { numA,numB in 
        return numA < numB 
    }
    print(numArr)
    
    log
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码
  • 单表达式闭包隐式返回

    sorted(by:)方法的参数类型明确了闭包必须返回一个Bool 类型值,因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted { numA,numB in numA < numB }
    print(numArr)    
    
    log:
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码
  • 参数名称缩写

    可以直接通过$0,$1,$2 来顺序调用闭包的参数,以此类推。如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted {$0 < $1}
    print(numArr)
    
    log:
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码
  • 运算符方法

    swift 的 Int类型定义了关于大于号(>)的实现,其作为一个函数接受两个Int类型的参数并返回Bool类型的值。而这正好与sorted(by:)方法的参数需要的函数类型相符合。因此,可以简单地传递一个大于号

    let numbers = [1,9,2,8,3,7,4,6];
    let numArr = numbers.sorted(by: <)
    print(numArr)
    
    log:
    [1, 2, 3, 4, 6, 7, 8, 9]
    复制代码

一个无名闭包作为一个参数传给一个普通的函数

  • 如果闭包是函数的最后一个参数,那么调用的时候将闭包写在括号()后面

    func sayHi(str1:String, str2:String, closure:(String,String)->String)->String{
        return closure(str1,str2);
    }
    
    let message = sayHi(str1: "hello", str2: "world") { (str1:String, str2:String)-> (String) in
        return str1 + " " + str2
    }
    
    print(message)
    
    log:
    hello world
    复制代码
  • 如果函数只有一个参数且为闭包,那么调用的时候括号()可以不写

    func sayHi(closure:(String)->())-> Void {
    
        print("只有一个参数哦")
        closure("hello world")
    }
    
    sayHi { (str:String)  in
        print(str)
    }
    
    log:
    只有一个参数哦
    hello world
    复制代码

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

func add(num:Int) -> ()->Int {
    var value = 0
    func result() -> Int{
        value += num
        return value
    }
    /*
     这有一个叫做 add 的函数,其包含了一个叫做 result 的嵌套函数
     嵌套函数result()从上下文中捕获了两个值,value 和 num
     捕获这些值之后,add 将 result 作为闭包返回
     每次调用 result 时,其会以 num 作为增量增加 value 的值
     */
    return result
}
复制代码
  • 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量,并且保证了在下一次执行函数时,之前捕获的值依旧存在

    func add(num:Int) -> ()->Int {
        var value = 0
        func result() -> Int{
            value += num
            return value
        }
        return result
    }
    
    let result = add(num: 10)
    print(result())  //10
    print(result())  //20
    print(result())  //30
    复制代码
  • 同一个方法中的变量, 会被绑定到属于自己的变量

    func add(num:Int) -> ()->Int {
        var value = 0
        func result() -> Int{
            value += num
            return value
        }
        return result
    }
    
    
    //如果你创建了另一个result1,它会有属于自己的引用,指向一个全新、独立的value变量
    let result1 = add(num: 10)
    print(result1())  //10
    
    //再次调用原来的result会继续增加它自己的value变量,该变量和result1中捕获的变量没有任何联系
    print(result())  //40
    复制代码

闭包是引用类型

  • 函数和闭包都是引用类型

    将函数或闭包赋值给一个常量或变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用

    //这两个常量或变量都引用相同的闭包
    let method = result
    复制代码

  • 一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包。通俗点讲,不在当前方法中使用闭包,而是在方法之外使用
  • 定义函数的参数为逃逸闭包时,只需要在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的
  • 将一个闭包标记为 @escaping意味着你必须在闭包中显式地引用self
    var result: ()->Void = {}
    var str = ""
    func showA(closure: @escaping () -> Void) {
        result = closure
    }
    func showB(closure: () -> Void) {
        closure()
    }
    func doSomething() {
        showA {str = "我是逃逸闭包"}
        showB {str = "我是普通闭包"}
    }
    
    doSomething()
    print(str)    //我是普通的闭包
    result()
    print(str)    //我是逃逸的闭包
    复制代码
    逃逸闭包是在函数执行之后再执行,于是这段代码最后输出“我是逃逸的闭包”

  • 自动闭包: 自动创建一个闭包用来包裹一个表达式,这种闭包不接受任何参数,当闭包被调用时,返回包裹在闭包中的表达式的值

  • 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行

  • 这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包

    var arr = ["a","b","c"]
    print(arr.count)  //3
    let closure = {
        arr.remove(at: 0)
    }
    
    print(arr.count)  //3
    print(closure())  //a
    print(arr.count)  //2
    复制代码
  • 将闭包作为参数传递给函数时,同样可以延时求值

    函数接受一个显式闭包类型的参数

    func delete(closure: ()->String){
        print(closure())
    }
    
    var arr = ["a","b","c"]
    delete(closure:{arr.remove(at: 0)})    
    
    log:
    a
    复制代码

    通过将参数标记为@autoclosure 来接收一个自动闭包,该函数接受的是String类型参数而非闭包

    func delete(closure: @autoclosure ()-> String){
        print(closure())
    }
    
    var arr = ["a","b","c"]
    delete(closure:arr.remove(at: 0))  
    
    log:
    a
    复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK