美文网首页
Swift 闭包

Swift 闭包

作者: H丶ym | 来源:发表于2021-01-12 14:03 被阅读0次

    什么是闭包

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

    闭包表达式

    OC中的block类似,这个表达式需要具备

    • 作用域(也就是大括号)
    • 参数和返回值
    • 函数体(也就是in后面的代码)
    var closure : (Int) -> Int = { (age: Int) in
        return age
    }
    

    这个闭包的参数为Int,返回值为Int
    我们可以将这个闭包声明成一个可选类型

    var clourse:((Int)->(Int))?
    

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

    func test(param:(Int)->(Int)){
        let a = param(10)
        print(a)
    }
    
    test { (param) -> (Int) in
        return param+1
    }
    
    尾随闭包

    一种书写方式,用来提高代码可读性,当闭包表达式作为函数的最后一个参数时,将当前闭包表达式的{}放在函数外面,一般编译器会帮我们做

    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: Int, _ item2: Int, _ item3: Int) -> Bool in
       return (item1 + item2 < item3)
    }
    
    闭包强大的推算能力
    var array = [1, 2, 3]
    
    array.sort{(item1 : Int, item2: Int) -> Bool in return item1 < item2 }
    
    //省略参数类型  由array的类型推算
    array.sort(by: {(item1, item2) -> Bool in return item1 < item2 })
    
    //省略闭包返回值,由 in 后面的函数体推算
    array.sort(by: {(item1, item2) in return item1 < item2 })
    
    //尾随闭包写法
    array.sort{(item1, item2) in item1 < item2 }
    
    //省略参数声明,由闭包内嵌参数代替
    array.sort{ return $0 < $1 }
    
    //省略 return 关键字
    array.sort{ $0 < $1 }
    
    //省略 参数
    array.sort(by: <)
    

    捕获值

    这里借助官方文档中的列子

    func makeIncrementer() -> () -> Int {
        var runningTotal = 10
        func incrementer() -> Int {
            runningTotal += 1
            return runningTotal
        }
        return incrementer
    }
    
    let makeInc = makeIncrementer()
    print(makeInc())
    print(makeInc())
    print(makeInc())
    
    11
    12
    13
    Program ended with exit code: 0
    

    runningTotal是一个临时变量,每次进来的时候讲道理应该是10,但是这里的输出是一直累加的,原因是底层调用了swift_allocObject,在堆上分配了一个空间,在内嵌函数使用调用完成后释放

    总结

    • 一个闭包能够从上下文捕获已被定义的常量和变量。即使这些变量和变量的员作用域已经不存在了,闭包仍能够在其函数体内引用和修改这些值。
    • 当我们每次修改捕获值的时候,修改的是堆区中的值
    • 当每次重新执行当前函数时候,都会为捕获值重新创建内存空间
    捕获值的方式
    • 自动捕获
    var age = 10
    let closure = {
       age += 1
    }
    closure()
    print(age)
    
    11
    Program ended with exit code: 0
    
    • 手动捕获
    var age = 10
    let closure = { [age] in
       print("闭包内:\(age)")
    }
    age = 11
    closure()
    print("闭包外:\(age)")
    
    闭包内:10
    闭包外:11
    Program ended with exit code: 0
    

    总结
    自动捕获的参数可以修改,并且影响外部变量
    手动捕获的参数本质是值类型,不可修改,跟外部变量没关系了

    逃逸闭包

    逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了,在闭包参数前些@escaping来明确闭包是允许逃逸的。
    Swift 3.0之后,系统默认闭包参数是@nonescaping

    逃逸闭包一般有两种实现

    • 延迟调用
    • 先存储,在后面进行调用

    先存储,在后面进行调用举例:

    class LGTeacher{
    
        var complitionHandler: ((Int)->Void)?
    
        func makeIncrementer(amount: Int,  handler: @escaping (Int) -> Void){
            var runningTotal = 10
            runningTotal += amount
            self.complitionHandler = handler
        }
    
        func doSomething(){
            self.makeIncrementer(amount: 10) {
                print($0)
            }
        }
    
        deinit {
            print("LGTeaher deinit")
        }
    
    }
    
    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let t = LGTeacher()
    
            t.doSomething()
    
            t.complitionHandler?(10)
        }
    
    }
    
    10
    LGTeaher deinit
    

    延迟调用举例:

    class LGTeacher{
    
        var complitionHandler: ((Int)->Void)?
    
        func makeIncrementer(amount: Int,  handler: @escaping (Int) -> Void){
            var runningTotal = 10
            runningTotal += amount
    //        self.complitionHandler = handler
            DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) {
                handler(runningTotal)
            }
    
        }
    
        func doSomething(){
            self.makeIncrementer(amount: 10) {
                print($0)
            }
        }
    
        deinit {
            print("LGTeaher deinit")
        }
    
    }
    
    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let t = LGTeacher()
    
            t.doSomething()
    
            t.complitionHandler?(10)
        }
    
    }
    
    10
    LGTeaher deinit
    

    逃逸闭包循环引用

    非逃逸闭包是不会产生循环引用的,上下文保存栈上,而不是堆上

    class LGStudent{
        var age:Int = 18
    }
    
    class LGTeacher{
        
        var student = LGStudent()
        
    
        var complitionHandler: (()->Void)?
    
        func makeIncrementer(handler: @escaping () -> Void){
            self.complitionHandler = handler
        }
    
        func doSomething(){
            self.makeIncrementer{
                print(self.student.age)
            }
        }
    
        deinit {
            print("LGTeaher deinit")
        }
    
    }
    
    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let t = LGTeacher()
    
            t.doSomething()
    
            t.complitionHandler?()
        }
    
    }
    
    18
    

    只打印了18,没有打印LGTeaher deinit

    使用[weak self] 或者 [unowned self]解决循环引用

    self.makeIncrementer{ [weak self] in
        print(self?.student.age)
    }
    self.makeIncrementer { [weak student] in
        print(student?.age)
    }
    self.makeIncrementer { [unowned self] in
        print(self.student.age)
    }
    self.makeIncrementer { [unowned student] in
        print(student.age)
    }
    
    18
    LGTeaher deinit
    

    总结

    • 在捕获变量时,由于数据会copy一份,会影响对象的引用计数,使用weak或者unowned修饰,不会影响引用计数
    • 使用weak修饰时,会将捕获的值变为可选类型,有可能为空
    • 使用unowned,修饰时要确保对象存在,如果对象被释放,会出现野指针
    • 非逃逸闭包不会造成循环引用,比如系统的动画闭包,比如snp布局闭包等

    相关文章

      网友评论

          本文标题:Swift 闭包

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