美文网首页
Swift iOS - weak self

Swift iOS - weak self

作者: me_967d | 来源:发表于2024-10-08 18:15 被阅读0次

在 Swift iOS 开发中,`weak self` 常用于避免**强引用循环**(retain cycle),特别是在闭包(closure)和异步操作的上下文中。强引用循环会导致内存泄漏,使得某些对象不能被释放。因此,在特定场景下使用 `weak self` 是非常必要的。

下面是 `weak self` 的一些具体应用场景:

### 1. 闭包中的强引用循环

当我们在闭包内使用 `self` 时,闭包会对 `self` 产生强引用,而 `self`(通常是 `UIViewController` 或者某个类)可能也对这个闭包有强引用,这样就会导致强引用循环,导致 `self` 不能被释放。

#### 示例:

```swift

class MyViewController: UIViewController {

    var loadDataCompletion: (() -> Void)?

    override func viewDidLoad() {

        super.viewDidLoad()

        loadData {

            // 如果不使用 weak self,这里 self 会对闭包产生强引用,导致循环引用

            self.updateUI()

        }

    }

    func loadData(completion: @escaping () -> Void) {

        // 模拟异步网络请求

        DispatchQueue.global().async {

            // 网络请求完成,调用回调

            DispatchQueue.main.async {

                completion()

            }

        }

    }

    func updateUI() {

        // 更新 UI 逻辑

    }

}

```

在上面的代码中,`loadData` 方法执行了异步网络请求,网络请求的回调闭包中使用了 `self`,这就会产生强引用循环,因为闭包捕获了 `self`,而 `self`(即 `MyViewController` 实例)又强引用着这个闭包。如果 `MyViewController` 被销毁,闭包依然保持着对它的引用,造成了内存泄漏。

#### 解决方案:使用 `weak self`

```swift

class MyViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        loadData { [weak self] in

            guard let self = self else { return }

            self.updateUI()

        }

    }

}

```

通过将 `self` 声明为弱引用(`weak self`),可以打破强引用循环。闭包不会强引用 `self`,而是会持有一个弱引用。当 `self` 被销毁时,闭包中的 `self` 会自动变为 `nil`。

### 2. 异步任务

在异步任务(例如:网络请求、动画、定时器等)中,`weak self` 也是一个常见的用法。由于异步任务的执行时间不确定,使用 `self` 很容易导致强引用循环,尤其是在异步任务尚未完成但视图控制器已经被销毁的情况下。

#### 示例:

```swift

class NetworkViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        performAsyncOperation()

    }

    func performAsyncOperation() {

        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {

            // 异步操作完成后更新 UI

            self.updateUI()  // 这里 self 可能已经被销毁

        }

    }

    func updateUI() {

        print("UI Updated")

    }

}

```

在上面的代码中,如果 `NetworkViewController` 在 2 秒内被销毁,那么异步操作完成时仍然会尝试调用 `self.updateUI()`,这就导致潜在的崩溃问题。

#### 解决方案:使用 `weak self`

```swift

class NetworkViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

        performAsyncOperation()

    }

    func performAsyncOperation() {

        DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { [weak self] in

            guard let self = self else { return }

            self.updateUI()

        }

    }

}

```

通过使用 `weak self`,在异步任务完成时检查 `self` 是否还存在,如果 `self` 已经被销毁,闭包中的逻辑将不会再执行。

### 3. 定时器(Timer)

`Timer` 也是导致强引用循环的常见场景。`Timer` 会对它的 target(即 `self`)产生强引用,而 `self` 也对 `Timer` 保持引用,这样就形成了强引用循环,导致 `self` 无法释放。

#### 示例:

```swift

class TimerViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {

        super.viewDidLoad()

        startTimer()

    }

    func startTimer() {

        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in

            self.timerFired()  // 这里 self 和 timer 会产生循环引用

        }

    }

    func timerFired() {

        print("Timer fired")

    }

    deinit {

        timer?.invalidate()

        print("TimerViewController deinit")

    }

}

```

如果 `TimerViewController` 被销毁,定时器仍然持有 `self`,`self` 不能被释放。

#### 解决方案:使用 `weak self`

```swift

class TimerViewController: UIViewController {

    var timer: Timer?

    override func viewDidLoad() {

        super.viewDidLoad()

        startTimer()

    }

    func startTimer() {

        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in

            guard let self = self else { return }

            self.timerFired()

        }

    }

    func timerFired() {

        print("Timer fired")

    }

    deinit {

        timer?.invalidate()

        print("TimerViewController deinit")

    }

}

```

通过使用 `weak self`,我们确保当 `TimerViewController` 被销毁后,闭包中的 `self` 也会变成 `nil`,从而避免强引用循环。

### 4. 动画回调(UIView Animation)

类似于定时器,动画回调也可能导致强引用循环,尤其是在动画完成后需要操作 `self` 的情况下。

#### 示例:

```swift

UIView.animate(withDuration: 1.0) {

    self.view.alpha = 0

} completion: { finished in

    self.view.removeFromSuperview()  // 这里的闭包会强引用 self

}

```

#### 解决方案:使用 `weak self`

```swift

UIView.animate(withDuration: 1.0) { [weak self] in

    self?.view.alpha = 0

} completion: { [weak self] finished in

    self?.view.removeFromSuperview()

}

```

### 总结

`weak self` 的主要作用是在闭包、异步任务、定时器、动画等场景下,避免闭包对 `self` 产生强引用,导致强引用循环和内存泄漏。通过将 `self` 声明为弱引用,可以确保对象在不再需要时能够被正确释放,避免潜在的崩溃和性能问题。

在Swift中,`weak self` 主要用于解决闭包中的强引用循环问题。强引用循环是指两个或多个对象相互持有对方的强引用,导致它们都无法被释放,从而引发内存泄漏。`weak self` 通过创建一个弱引用,避免了这种强引用循环的发生。

具体来说,当一个类的实例在一个闭包中被引用时,如果不使用 `weak self`,闭包会持有该实例的强引用,从而导致该实例无法被释放。使用 `weak self` 可以避免这种情况,因为弱引用不会增加对象的引用计数,当对象被销毁时,弱引用会自动变为 `nil`,从而打破强引用循环。

例如,在网络请求的回调闭包中,如果直接使用 `self`,可能会导致 `self` 持有回调闭包的强引用,从而形成强引用循环。为了避免这种情况,可以使用 `weak self`:

```swift

networkManager.fetchData { [weak self] data in

    guard let self = self else { return }

    // 使用 self 进行操作

}

```

在这个例子中,`weak self` 确保了在回调闭包执行时,不会因为持有 `self` 的强引用而导致内存泄漏。如果 `self` 在回调闭包执行时已经被销毁(例如视图控制器被释放),`weak self` 会自动变为 `nil`,从而避免了潜在的崩溃风险。

`weak self` 在闭包中使用时,确保了即使 `self` 被销毁,闭包仍然可以安全地访问 `self` 的属性和方法,而不会导致内存泄漏或崩溃。

相关文章

网友评论

      本文标题:Swift iOS - weak self

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