美文网首页
swift 闭包与闭包表达式

swift 闭包与闭包表达式

作者: 三国韩信 | 来源:发表于2021-03-19 17:34 被阅读0次

    闭包与闭包表达式

    在swift里闭包大家都很熟悉,相当于oc中的block。闭包表达式又是啥?很多人把闭包表达式等同于闭包,认为是同一个东东,其实严格上来说,闭包和闭包表达式是不一样的

    闭包表达式

    1、什么是闭包表达式
    闭包表达式其实就是函数,是对函数的另一种定义方式。在swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数。
    闭包表达一个函数的格式是这样的:

    {
        (参数列表)->返回值类型  in 
        函数体代码
    }
    
    // 普通func表达一个函数
    func sum(_v1: Int, _v2: Int) -> Int {
        return v1+v2
    }
    //执行这个函数 
    sum(1,2)
    
    // 通过闭包表达式来表达一个函数
    var fn = {
        (v1: Int, v2: Int) -> Int  in
        return v1 + v2
    }
    //执行这个闭包表达式 
    fn(1,2)
    
    

    2、闭包表达式的简写
    闭包表达式更多的情况下是作为普通函数的参数来使用,比如下面这个函数,第三个入参就是一个函数fn

    func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
        return fn(v1+v2)
    }
    // 这里exec 函数有3个参数,最后一个参数传递的是一个函数,用闭包表达式来表示第三个参数。
    

    由于swift的编译器特别智能,闭包表达式可以简写,编译也能识别出来。比如上面的函数在调用的时候,就可以有如下的简写:

    // 正常的闭包表达式写法
    exec(v1:10, v2:20, fn:{
        (v1:Int, v2:Int) -> Int in 
        return v1 + v2 
    })
    
    // 可以把参数类型和返回值类型省略
    exec(v1:10, v2:20, fn:{
        v1, v2 in return v1 + v2 
    })
    
    // 把return都省略
    exec(v1:10, v2:20, fn:{
        v1, v2 in v1 + v2 
    })
    
    // 用$0和$1表示第一个入参和第二个入参
    exec(v1:10, v2:20, fn: {$0 + $1}
    
    // 甚至$0和$1都可以省略,只留一个+号
    exec(v1:10, v2:20, fn: { + } 
    

    注:虽然Swift的编译器很智能,当个人不建议写的很省略,可读性不是很好滴。

    如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式。类似上面的例子中exec函数的最后一个参数是一个闭包表达式,那么可以用尾随闭包的方式,如下:

    // 正常的闭包表达式写法
    exec(v1:10, v2:20, fn:{
        (v1:Int, v2:Int) -> Int in 
        return v1 + v2 
    })
    
    // 尾随闭包的写法
    exec(v1:10, v2:20) {
        (v1:Int, v2:Int) -> Int in 
        return v1 + v2 
    }
    
    // 尾随闭包的简写
    exec(v1:10, v2:20) {
        $0 + $1 
    }
    

    如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号。

    // 闭包表达式是函数的唯一实参
    func exec(fn:(Int, Int) -> Int) {
        return fn(v1+v2)
    }
    
    // 正常的写法
    exec(fn: {
        (v1,v2) in 
        return v1 + v2
    })
    
    // 精简的写法
    exec(fn:{$0 + $1})
    
    // 尾随闭包的写法
    exec() {
      $0+$1
    }
    
    // 更精简的写法
    exec { $0+$1 }
    // 最后这种写法是表示一个函数只有一个参数,且执行这个函数使用了尾随闭包的表示方式。
    
    闭包

    1、定义
    一个函数和它所捕获的变量\常量环境组合起来,称为闭包。一般指定义在函数内部的函数,一般它捕获的是外层函数的局部变量\常量;

    typealias Fn = (Int) -> Int
    func getFn () -> Fn {
        var num = 0
        func plus(_i: Int ) -> Int {
            num += i
            return num 
        }
        return plus
    }
    
    /* plus函数是定义在函数内且捕获了外部的变量num,整个plus函数就构成了一个闭包。
    如果plus函数不捕获num变量的话,严格意义上也不算是闭包。*/
    
    var fn1 = getFn()
    fn1(1)  //1 
    fn1(3)  //4 
    fn1(5)  //9 
    
    /*
    从fn1的3次调用结果可以看出num在fn1里是一直存在的。
    实际上num已经从原来的函数内的栈空间被移到堆空间了,而且一直被fn1 持有。
    实际上整个fn1闭包16个字节,前8个字节是函数的地址,后8个字节是num的地址。
    */
    
    
    可以把闭包想象成是一个类的实例对象;
    内存在堆空间;
    捕获的局部变量\常量就是对象的成员(存储属性);
    组成闭包的函数就是类内部定义的方法;
    class Closure {
        var num = 0 
        func plus(_i: Int) -> Int {
            num += i 
            return num 
        }
    }
    

    2、闭包的内存结构
    闭包的内存结构结论: 16个字节,前8个字节是函数的地址(不是直接的地址,间接能找到的地址),后8个字节是对应捕获的变量的堆空间的地址。(以上面的getFn为例。)
    3、闭包的循环引用
    和oc中的block类似,在闭包中也会强引用着捕获的外部变量,如果闭包本身也被外部变量强引用着,那么此时也会构成循环引用。在实际开发过程中也要用[weak self]来打破循环引用。

    class Student {
        var name: String?
        var giveAnswerClosure: ((Int) -> Void)?
        
        // 学生回答问题
        func giveAnswer() {
            // 调用闭包给出答案
            giveAnswerClosure?(1)
        }
        
        deinit {
            print("deinit---Student")
        }
    }
    
    class Teacher: NSObject {
        var student: Student?
        var isRight: Bool? // 答案是否正确
        
        override init() {
            super.init()
        
            student = Student()
            // 闭包回调
            student?.giveAnswerClosure = { answer in
                // 答案是1
                self.isRight = answer == 1 ? true : false
            }
        }
        
        // 提问问题
        func askQuestion() {
            // 学生回答
            student?.giveAnswer()
        }
        
        deinit {
            print("deinit---Teacher")
        }
    }
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
           
            let teacher = Teacher()
            teacher?.askQuestion()
        }
    }
    

    上面的例子中就存在着循环引用的情况,student持有者giveAnswerClosure这个闭包,而teacher由持有着student对象,在giveAnswerClosure闭包内又捕获了teacher对象(持有teacher)。这样就构成了teacher -> student -> giveAnswerClosure -> teacher的循环引用,导致teacher和student对象都不能释放。 打破循环引用的方式就是在闭包内用[weak self]。

    // 写法一
    student?.giveAnswerClosure = { [weak self] answer in
        self?.isRight = answer == 1 ? true : false
    }
    
    // 写法二
    weak var weakSelf = self
    student?.giveAnswerClosure = { answer in
        weakSelf?.isRight = answer == 1 ? true : false
    }
    

    更多的内存管理的请移步 https://www.jianshu.com/p/12688f7b9daf

    相关文章

      网友评论

          本文标题:swift 闭包与闭包表达式

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