Swift中的高阶函数

发布时间 2023-04-24 20:34:25作者: AliliWl

闭包是 Swift 中一种高级数据结构,它允许在函数内部访问函数外部的变量和参数。在 Swift 中,闭包是由闭包表达式创建的,闭包表达式是一个包含一个或多个匿名函数的表达式。

闭包的定义

闭包表达式是一个包含一个或多个匿名函数的表达式,它可以访问函数外部的变量和参数。闭包可以用于修改外部函数的参数或变量,或者返回一个新的值。

我们看一下官方给的示例

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


var fn = makeIncrementer();

在代码中,incrementer作为一个闭包,也是一个函数。而且incrementer的生命周期比makeIncrementer要长。当makeIncrementer执行完毕后,内部包含的变量runningTotal也随之消失,但是incrementer有可能还没执行。要想incrementer能够执行,这是就需要捕获runningTotalincrementer内部中,因此构成闭包有两个关键点,一个是函数,另外一个是能够捕获外部变量或者常量

闭包表达式

闭包表达式是 Swift 中一种用于创建闭包的表达式。闭包表达式包含一个或多个匿名函数,这些匿名函数可以访问外部函数的变量和参数。闭包表达式可以用于修改外部函数的变量或参数,或者返回一个新的值。

在 Swift 中,闭包表达式可以用方括号 () 来表示,例如:

let add = { x in x + 1 }  
print(add(2)) // 输出 3  

在上面的代码中,add 是一个闭包表达式,它包含一个匿名函数 { x in x + 1 }。这个匿名函数可以访问外部函数 add 的变量 x,并在函数内部增加 1。最后,闭包表达式将返回一个新的值,这个新值是 x + 1

除了使用方括号 () 来表示闭包表达式外,Swift 中还可以使用简写方式来表示闭包表达式。简写方式使用两个引号包裹闭包表达式,例如:

let add = { x, y in x + y } 
print(add(2, 3)) // 输出 5

在上面的代码中,add 是一个闭包表达式,它包含一个匿名函数 { x, y in x + y }。这个匿名函数可以访问外部函数 add 的变量 xy,并在函数内部增加 y。最后,闭包表达式将返回一个新的值,这个新值是 x + y

闭包和闭包表达式的区别

  1. 闭包表达式是一个包含匿名函数的表达式,而闭包则是闭包表达式的值。
  2. 闭包表达式的匿名函数可以访问外部函数的变量和参数,而闭包只能在当前作用域内访问。
  3. 闭包表达式可以包含一个或多个参数,而闭包只能包含一个参数。

闭包的使用

闭包可以用于修改外部函数的参数或变量,或者返回一个新的值。在 Swift 中,闭包的使用非常方便,只需要在函数外部定义一个闭包表达式,然后在函数内部调用这个闭包表达式即可。

  • 闭包当做变量
var closure : (Int) -> Int = { (age: Int) in 
    return age 
}
  • 闭包声明成一个可选类型
var closure : ((Int) -> Int)? 
closure = nil
  • 闭包当做一个常量(一旦赋值之后就不能改变了)
let closure: (Int) -> Int 

closure = {(age: Int) in 
    return age 
}
  • 闭包当做函数参数
func test(n:() -> Int) {
     print(n());
}
        
var age = 10
test(n: {() -> Int in
   age += 1
   return age
})
        
test {() -> Int in
    age += 1
    return age
}
    

尾随闭包

尾随闭包是 Swift 中一种高级闭包特性,它可以在函数返回后继续执行。尾随闭包可以用于实现循环引用等功能。在 Swift 中,可以使用闭包表达式定义一个尾随闭包,然后将这个闭包表达式作为参数传递给另一个函数。

函数

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)
})
  • 使用尾随闭包
test(10, 20, 30){(_ item1: Int, _ item2: Int, _ item3: Int) in
    return item1 + item2 < item3
}
func exec(fn:(Int,Int) -> Int) {
    print(fn(1,2))
}
        
exec(fn: {$0 + $1})
exec() {$0 + $1}
exec {$0 + $1}

闭包表达式简写

在 Swift 中,可以使用简写方式来表示闭包表达式。简写方式使用两个引号包裹闭包表达式,例如:(x, y) in x + y。使用简写方式可以简化闭包表达式的书写方式,提高代码的可读性。

var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 })

print(array) // [1, 2, 3]
  • 利用上下文推断参数和返回值类型
array.sort(by: {(item1, item2) -> Bool in return item1 < item2 }) 

array.sort(by: {(item1, item2) in return item1 < item2 })
  • 单表达式可以隐士返回,既省略 return 关键字
array.sort{(item1, item2) in item1 < item2 }
  • 参数名称的简写(比如$0
array.sort{ return $0 < $1 }

array.sort{ $0 < $1 }

array.sort{ < }

闭包捕获值

闭包捕获值是指闭包在创建时,可以捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数。

闭包捕获一个全局变量

我们首先来看一下闭包捕获全局变量的情况,代码如下

var i = 1

let closure = {
    print(i) //输出 2
}

i += 1

print(i) //输出 2

closure()

print(i) //输出 3

可以看到,i的值发生变化后,closure里面的i也发生了变化。和OC里面的block很像。

当执行到closure闭包的时候,直接去寻找变量i的地址,然后把i的值取出来。而此时i的值已经发生了变化,因此取出来i的值就是2。

闭包捕获一个局部变量

当闭包捕获一个局部变量时,内部又进行了哪些操作呢? 我们拿官方的例子验证一下。

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

let makeInc = makeIncrementer()

闭包捕获变量其实是在堆空间里面创建一个实例对象,并且把捕获变量的值存储到这个实例对象中,每次调用闭包使用的都是同一个堆空间的实例变量地址,所以在闭包外面修改值,闭包内部的值也会改变。

逃逸闭包

逃逸闭包是 Swift 中一种用于捕获外部变量或参数的闭包。逃逸闭包可以在函数内部创建,并在函数返回后继续执行。逃逸闭包可以用于捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数。

在 Swift 中,可以使用闭包表达式来创建逃逸闭包。闭包表达式的语法类似于普通闭包,但在闭包内部可以使用 return 语句来返回逃逸闭包。例如:

func add(_ a: Int, _ b: Int) -> Int {  
	return a + b       
}       
let add = add(1, 2)       
print(add) // 输出 3`   

在上面的代码中,add 是一个函数,它接受两个参数 ab,并返回它们的和。函数 add 内部使用了闭包表达式来创建逃逸闭包 add。逃逸闭包 add 可以捕获外部函数 add 的变量 ab,并在闭包内部使用这些变量。最后,逃逸闭包 add 返回的值是 a + b,即 3

需要注意的是,逃逸闭包可以捕获外部函数的变量或参数,但这并不意味着逃逸闭包可以访问外部函数的返回值。如果外部函数没有返回值,那么逃逸闭包将无法访问外部函数的返回值。

非逃逸闭包

非逃逸闭包是 Swift 中的一种闭包特性,它可以防止闭包外部的变量或参数被闭包捕获并重用。在 Swift 中,非逃逸闭包通常用于捕获外部函数的变量或参数,并在闭包内部使用这些变量或参数,以防止这些变量或参数被重复捕获并重用,导致内存泄漏等问题。

在 Swift 中,可以使用非逃逸闭包来创建闭包表达式。非逃逸闭包的语法类似于普通闭包,但在闭包内部不能使用 return 语句来返回闭包。例如:

func add(_ a: Int, _ b: Int) -> Int { 
	return a + b 
} 

let add = add(1, 2) 

print(add) // 输出 3

在上面的代码中,add 是一个函数,它接受两个参数 ab,并返回它们的和。函数 add 内部使用了闭包表达式来创建非逃逸闭包 add。非逃逸闭包 add 可以捕获外部函数 add 的变量 ab,并在闭包内部使用这些变量。由于闭包内部不能使用 return 语句,因此非逃逸闭包 add 返回的值是 a + b,即 3

需要注意的是,非逃逸闭包只能用于捕获外部函数的变量或参数,不能用于捕获外部函数的返回值。如果外部函数没有返回值,那么非逃逸闭包将无法访问外部函数的返回值。

自动闭包

@autoclosure是一种自动创建的闭包,用于将参数包装成闭包。这种闭包不接受任何参数,当它被调用的时候,会返回传入的值。这种便利语法让你在调用的时候能够省略闭包的花括号

函数中有一个 ()-> Any类型的参数,用@autoclosure修饰时,调用函数的时候可以传入一个确定的值 a,这个值会被自动包装成(){return a}的闭包,就不需要显示的将闭包表达式写出来

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
      print("debug:(message())")
    }
}
debugOutPrint(true,"Application Error Occured" )
debugOutPrint(true, getString )

func getString()->String{
    return "Application Error Occured"
}

闭包的循环引用

Swift 有如下要求:只要在闭包内使用 self 的成员,就要用 self.someProperty 或者 self.someMethod()(而不只是 somePropertysomeMethod())。这提醒你可能会一不小心就捕获了 self

class ClassA {
   
  var number : Int = 0
  
  lazy var someClosure = {
     [unowned self]
     (index: Int) -> Void in
      DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        print(self.number)
      }
      print(self.number)
  }
    
  lazy var someClosure2 = {
     [weak self]
     index: Int) -> Void in
      DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        print(self?.number ?? 0)
      }
      print(self?.number ?? 0)
  }
  
  deinit {
      print("ClassA---deinit")
  }
}

var n : ClassA? = ClassA();
n?.someClosure(23)
n = nil

学习 Swift,勿忘初心,方得始终。但要陷入困境时,也不要忘了最初的梦想和时代所需要的技能。