MST
星途 面试题库

面试题:Swift函数式编程的闭包与引用语义

在Swift函数式编程场景下,闭包捕获外部变量时会涉及到引用语义。请解释闭包捕获变量的原理,以及如何避免在复杂的函数式代码结构中因闭包捕获变量导致的循环引用问题。同时给出一段存在闭包循环引用隐患的Swift代码,并展示如何修复它。
43.2万 热度难度
编程语言Swift

知识考点

AI 面试

面试题答案

一键面试

闭包捕获变量的原理

在Swift中,闭包可以捕获其定义上下文中的常量和变量。当闭包被定义时,它会捕获所有在其体内引用的外部变量。这意味着即使定义闭包的作用域结束,闭包仍然可以访问和修改这些被捕获的变量。闭包通过引用语义捕获变量,这意味着闭包保存的是对变量的引用,而不是变量的值的副本。例如:

func outerFunction() {
    var number = 10
    let closure = {
        print(number)
    }
    number = 20
    closure() // 输出 20
}

这里闭包closure捕获了number变量,当number的值改变后,闭包中访问到的number的值也随之改变。

避免闭包循环引用问题

  1. 弱引用(Weak References):使用weak关键字来修饰被闭包捕获的对象,weak引用不会增加对象的引用计数,从而避免循环引用。例如,在类的实例方法中,如果闭包捕获了self,可以使用weak引用self
  2. 无主引用(Unowned References):当你确定被捕获的对象在闭包的生命周期内不会变为nil时,可以使用unowned引用。unowned引用和weak类似,也不会增加对象的引用计数,但unowned引用是一个非可选类型,访问unowned引用时不会解包。

存在闭包循环引用隐患的Swift代码

class Person {
    let name: String
    var completionHandler: (() -> Void)?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
    
    func doWork() {
        completionHandler = { [unowned self] in // 这里应该用weak而不是unowned,下面修复时会改
            print("Work done by \(self.name)")
        }
        completionHandler?()
    }
}

var person: Person? = Person(name: "John")
person?.doWork()
person = nil // Person实例没有被释放,因为循环引用

在这个例子中,Person类有一个闭包属性completionHandler,在doWork方法中,闭包捕获了self。由于闭包持有self,而self又持有闭包,形成了循环引用,导致Person实例在person置为nil时不会被释放。

修复后的代码

class Person {
    let name: String
    var completionHandler: (() -> Void)?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
    
    func doWork() {
        completionHandler = { [weak self] in
            guard let self = self else { return }
            print("Work done by \(self.name)")
        }
        completionHandler?()
    }
}

var person: Person? = Person(name: "John")
person?.doWork()
person = nil // Person实例被正确释放

在修复后的代码中,使用[weak self]捕获self,这样闭包对self的引用是弱引用,不会增加self的引用计数,从而避免了循环引用。在闭包内部,使用guard let self = self else { return }来确保self在闭包执行时仍然存在。