【问题标题】:Swift closure is still in memory after VC deinit is called调用 VC deinit 后,Swift 闭包仍在内存中
【发布时间】:2019-12-14 21:47:13
【问题描述】:

我有一个蓝牙类,当 char 值更新为视图控制器中的闭包(以及单例类中的相同闭包)时,它会通过。当调用 VC deinit 时,更新 char 值时,VC 中的闭包仍在执行。我在 VC 中使用 [weak self] 进行关闭。我希望能够在取消初始化视图时阻止调用此 VC 闭包。但我也不明白为什么单例中的其他回调在 VC 出现后没有被执行!

下面是VC内部闭包的语法

bluetooth.updatedCharacteristicsValue { [weak self] char in

【问题讨论】:

  • 请提供更多代码。更清楚地写出你想要什么。

标签: ios swift closures automatic-ref-counting deinit


【解决方案1】:

[weak self] 并不意味着可以丢弃闭包,它只是阻止闭包保留 VC(从而防止 VC 被取消)。

只需以以下方式开始您的关闭:

guard let self = self else { return }

...如果 VC 不再存在,则提前退出。

至于为什么调用 VC 提供的闭包但单例中的闭包没有,听起来你的蓝牙类不理解多个“用户”的概念。最后注册回调的人就是被调用的人。

一种使用方便的自注销令牌处理您自己的观察者注册的方法:

class ObserverToken {
    let id = UUID()
    private let onDeinit: (UUID) -> ()

    init(onDeinit: @escaping (UUID) -> ()) {
        self.onDeinit = onDeinit
    }

    deinit {
        onDeinit(id)
    }
}

class BluetoothThing {
    // Associate observers with the .id of the corresponding token
    private var observers = [UUID: (Int) -> ()]()

    func addObserver(using closure: @escaping (Int) -> ()) -> ObserverToken {
        // Create a token which sets the corresponding observer to nil
        // when it is deinit'd
        let token = ObserverToken { [weak self] in self?.observers[$0] = nil }
        observers[token.id] = closure
        return token
    }

    func tellObserversThatSomethingHappened(newValue: Int) {
        // However many observers we currently have, tell them all
        observers.values.forEach { $0(newValue) }
    }

    deinit {
        print("?")
    }
}

// I've only made this var optional so that it can later be set to nil
// to prove there's no retain cycle with the tokens
var bluetooth: BluetoothThing? = BluetoothThing()

// For as long as this token exists, updates will cause this closure
// to be called. As soon as this token is set to nil, it's deinit
// will automatically deregister the closure
var observerA: ObserverToken? = bluetooth?.addObserver { newValue in
    print("Observer A saw: \(newValue)")
}

// Results in:
// Observer A saw: 42
bluetooth?.tellObserversThatSomethingHappened(newValue: 42)

// A second observer
var observerB: ObserverToken? = bluetooth?.addObserver { newValue in
    print("Observer B saw: \(newValue)")
}

// Results in:
// Observer A saw: 123
// Observer B saw: 123
bluetooth?.tellObserversThatSomethingHappened(newValue: 123)

// The first observer goes away.
observerA = nil

// Results in:
// Observer B saw: 99
bluetooth?.tellObserversThatSomethingHappened(newValue: 99)

// There is still one 'live' token. If it is retaining the
// Bluetooth object then this assignment won't allow the
// Bluetooth to deinit (no wavey hand)
bluetooth = nil

因此,如果您的 VC 将其令牌存储为属性,则当 VC 消失时,令牌会消失并且闭包会被注销。

【讨论】:

  • 我已经实现了那个保护声明,所以你说的有道理(谢谢!)。我的问题是闭包仍然只在 VC 中被调用。而我希望它被调用到我的单例中,而不是我也有一个回调(在我进入那个 VC 之前它工作正常)
  • @Phil 查看我的答案的补充以获得可能的解释。蓝牙代码是你自己的还是 SwiftyBluetooth 之类的?
  • 代码是我自己的,是的。是的,这是有道理的,所以基本上我想我的问题是——我能拥有这些“多个用户”吗?或者如果没有,我可以在 VC 中删除该闭包并以某种方式重新启用我的单例中的闭包吗?
  • 您可以保留一个观察者列表,并让他们通过蓝牙类中的函数注册他们的闭包(但理想情况下,您需要提供一种方法在您的 VC 消失时取消注册它们,那么您需要能够单独识别它们才能取消注册正确的)。或者您可以更改您的蓝牙类以发出通知,然后您的“用户”只需通过通常的 NotificationCenter 机制注册和注销。
  • 好的,我想我现在明白问题所在了。我认为我可以同时执行多个闭包。生病检查保持观察员名单。感谢您的帮助伙伴!
猜你喜欢
  • 2017-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-15
  • 1970-01-01
  • 1970-01-01
  • 2022-12-15
  • 1970-01-01
相关资源
最近更新 更多