记录学习Swift过程,原网址是:https://www.journaldev.com/15163/swift-closure

闭包:

函数也是闭包的一种;闭包是没有名字的函数,也没有标识符func.
三种形式:全局闭包、嵌套闭包、闭包表达式;
前两种函数中已经讨论过了;一般我们所提到的闭包都是第三者形式。

闭包优势:

  1. 闭包比函数简单,swift可以从定义闭包的上下文中推断出参数类型和返回类型,从而便于定义和传递给函数。
  2. 闭包可以用于捕获和存储某个特定时间点的某个变量的状态,并在以后使用它
  3. 闭包允许我们在函数返回后运行一段代码

定义闭包

1.基本语法:

{}:代表闭包主体
(parameters) : 入参及参数类型
-> (return type) : 返回与返回值类型
in : 将参数和返回类型与主体分开。
{(parameters) -> (return type) in

}

2.定义一个闭包变量

var myClosure : (ParameterTypes) -> ReturnType

示例

1.定义一个函数和一个闭包,无参无返回

打印Hello world

//函数
func helloWorldFunc(){
    print("Hello world")
}
//闭包
var helloWorldClosure = {() -> () in
    print("Hello world")
}

当我们不需要有返回值,或者返回值类型可以从上下文推断出来,我们可以省略返回部分的代码;
当我们没有入参,或者入参类型可以从上下文推断出来时,我们也可以省略参数部分的代码
所以上面的闭包可以简化为:

var helloWorldClosureSimply = {
    print("Hello world")
}

2.定义一个有参有返回的函数和闭包

将两个字符串拼接起来

func appendString(_ a:String,with b:String) -> String{
    return a + " : " + b
}
print(appendString("Swift", with: "Functions")) //打印结果: "Swift : Functions"

var appendStringClosure = {(a: String,b : String) -> String in
    return a + " : " + b
}
print(appendStringClosure("Swift","Closure"))//打印结果:Swift : Closure

//由于swift可以从上下文中推断出入参与返回值类型,上面的闭包可以变换成以下几种格式
//从入参类型,推断出返回值类型,所以可以省略返回值类型
var appendStringClosureWithOutReturnType = {(a:String,b:String) in
    return a + " : " + b
}

var appendStringClosureTypeDeclaredOnTheVariable : (String,String) -> String = {(a,b) in
    return a + " : " + b  //也可以省略return关键字
}
//Swift允许我们使用简写的参数名称来引用传递给closure的参数:分别为第一个、第二个、第三个参数引用$0、$1、$2等参数。
var appendStringUseShortHandName : (String,String) -> String = {
    $0 + " : " + $1
}

var appendStringUseShortHandNameTakeOneOrMoreAhead = {
    $0 + " : " + $1 + " : " + $2
}

print(appendStringUseShortHandNameTakeOneOrMoreAhead("Swift", "Closures", "Awesome")) //打印结果: "Swift : Closures Awesome"

3.创建一个用函数或闭包做参数的函数

func operationsSq(a:Int,b:Int,myFunction:(Int,Int) -> Int) -> Int{
    return myFunction(a,b)
}
//创建函数作为参数也是可以实现,但是会增加样板代码
func addSquareFunc(_ a:Int,_ b:Int) -> Int{
    return a*a + b*b
}
operationsSq(a:2,b:4,myFunction:addSquareFunc)//打印结果:20

//下面使用闭包
var addSquareClosure : (Int,Int) -> Int = {$0 * $0 + $1 * $1}
var subtractSquareClosure: (Int, Int) -> Int = { $0*$0 - $1*$1 }
var multiplySquareClosure: (Int, Int) -> Int = { $0*$0 * $1*$1 }
var divideSquareClosure: (Int, Int) -> Int = { ($0*$0) / ($1*$1) }
var remainderSquareClosure: (Int, Int) -> Int = { ($0*$0) % ($1*$1) }
operationsSq(a:4,b:2,myFunction:addSquareClosure)//打印结果:20
operationsSq(a:4,b:2,myFunction:subtractSquareClosure)//打印结果:12
operationsSq(a:4,b:2,myFunction:multiplySquareClosure)//打印结果:64
operationsSq(a:4,b:2,myFunction:divideSquareClosure)//打印结果:4
operationsSq(a:4,b:2,myFunction:remainderSquareClosure)//打印结果:0

因为Swift中表明,如果闭包中的入参和返回值可以从上下文中判断出来时,可以省略入参和返回,所以将上面定义的闭包变量进行了如下尝试:

var add = {$0 * $0 + $1 * $1}

但是一直报错 错误原因为:Ambiguous use of operator '*';了解了一下,因为没有确认入参类型和出参类型,结合上下文也不能确认类型。所以改为下面的代码,就不报错了,所以不确认输入或输出类型时,body体中一定要给出一个有类型的数据。

var add = {$0 * $0 *  + $1 * $1 * 1}

言归正传。然后也可以不定义闭包变量,直接写在调用函数中

operationsSq(a:2,b:2, myFunction: { $0*$0 + $1*$1 })
operationsSq(a:4,b:5, myFunction: { $0*$0 - $1*$1 })
operationsSq(a:5,b:5, myFunction: { $0*$0 * $1*$1 })
operationsSq(a:10,b:5, myFunction: { ($0*$0) / ($1*$1) })
operationsSq(a:7,b:5, myFunction: { ($0*$0) % ($1*$1) })

如果闭包在函数末尾,则函数也可以简写如下:

operationsSq(a:2,b:2){
    ($0*$0) + ($1*$1)
}

下面看一个例子,我们将用闭包表达式将指数相加

func sumOfExponentials(from:Int,to:Int,myClosure:(Int)->Int) -> Int{
    var sum = 0
    for i in from...to{
        sum = sum + myClosure(i)
    }
    print(sum)
    return sum
}

sumOfExponentials(from:0,to:5){$0} //0-5相加之和
sumOfExponentials(from:0,to:5){$0 * $0}//0-5平方之和
sumOfExponentials(from:0,to:5){$0 * $0 * $0}//0-5立方之和

4.map、sorted也是一种尾随闭包

//将整数型数组转变为字符型数组
var numbersArray = [1,2,3,4,5,6]
var closureString = numbersArray.map{
    return "\($0)"
}
print(closureString)//打印结果:["1", "2", "3", "4", "5", "6"]

//使用尾随闭包按降序排列数组
var descendingArray = numbersArray.sorted{$0 > $1}
print(descendingArray)//打印结果:[6, 5, 4, 3, 2, 1]

5.捕获闭包中的列表

var y = 0
var myClosure = {
    print("The value of x is start of \(y)")
}
myClosure()//打印结果:The value of x is start of 0

//但是当我们重新为 y 赋值时,
y = 5
myClosure()//打印结果:The value of x is start of 5

myClosure定义和初始化都在y = 5 赋值之前,为什么结果是The value of x is start of 5?因为闭包捕获的是y的引用(内存地址)。对该内存地址的值所做的任何更改都将在调用它时由闭包显示。要使x表现为值类型,我们需要使用Capture Lists。Capture Lists是一个数组[],它保存变量的本地副本。

//修改如下:
var variable = 5
var capturesClosure = { [variable] in
    print("The value of variable is start of \(variable)")
}
capturesClosure()
variable = 10
capturesClosure()

此时再改变变量的值,闭包中的值也不会改变;

5.防止强引用造成内存泄露示例

捕获引用类型在类中使用时可能是破坏性的,因为闭包可能持有对实例变量的强引用,并导致内存泄漏。我们来看一个例子

class Person {

    var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }
    func initClosure()
    {
        myClosure = { print("Initial value is not defined yet")}
    }

    deinit{
    print("\(self) escaped")
    }


}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5
a?.myClosure()
a = nil

打印结果:Initial value is not defined yet __lldb_expr_12.Person escaped
类正常释放

class Person {

   var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }

    func initClosure()
    {
        myClosure = { print("Intial value is \(self.x)")}
    }

   deinit{
    print("\(self) escaped")
    }

}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5
a?.myClosure()//打印结果:Intial value is 5
a = nil

deinit中未打印,因为闭包强引用了self.x,修改如下:

class Person {

    var x: Int
    var myClosure: ()->() = {print("Hey there")}
    init(x: Int)
    {
    self.x = x
    }

    func initClosure()
    {
        myClosure = {[weak self] in guard let weakSelf = self else { return }
            print("Intial value is \(weakSelf.x)")}
    }

    deinit{
    print("\(self) escaped")
    }


}

var a:Person? = Person(x: 0)
a?.initClosure()
a?.x = 5//打印结果:Intial value is 5
a?.myClosure()
a = nil//打印结果:__lldb_expr_16.Person escaped

因为弱引用是这样一种引用,它不会对它所引用的实例保持强持有,因此不会阻止ARC处理被引用的实例.

6.逃逸(转义)闭包

1.转义闭包是在传递给它的函数返回后调用的闭包。换句话说,它比传递给它的函数更长寿。转义闭包通常用于完成处理程序,因为它们在函数结束后被调用。
2.非转义闭包是在传递给它的函数中调用的闭包,即在它返回之前。默认情况下,闭包是不可转义的

var completionHandlers: [() -> Void] = []

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)

}
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure {[weak self] in guard let weakSelf = self else { return }
            weakSelf.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
    
    deinit{
    print("deinitalised")
    }
}

var s:SomeClass? = SomeClass()
s?.doSomething()
print(s?.x ?? -1) //prints 200


completionHandlers.first?()
print(s?.x ?? -1) //prints 100
s = nil
posted on 2021-06-21 16:18  蓬松小辉煌  阅读(106)  评论(1编辑  收藏  举报