如何从UIViewController中删除传递的闭包

时间:2019-04-13 21:18:05

标签: ios swift

对不起,标题可能没有那么多信息,所以这是我的问题

我想创建ThemeManager并应用于所有屏幕,可以在应用程序中更改主题,这就是为什么我添加了closureList来触发并更新所有相关屏幕的原因

class ThemeManager {

static let shared = ThemeManager()

private(set) var theme: Theme
private var bindedList: [()->Void] = []

private init () {
    self.theme = AppGreenTheme()
}

func apply(theme: Theme) {
    self.theme = theme
}

func bind(closure: @escaping ()->Void) {
    bindedList.append(closure)
}

func bindAndFire(closure: @escaping ()->Void) {
    bind(closure: closure)
    closure()
}
}

这是我想从任何UIViewController或任何UIView

中使用它的方式
ThemeManager.shared.bindAndFire {
    // here I will get theme changes and update my screen
}

所以我想知道,在这种情况下,我将为UIViewControllerUIView创建参考周期,这是在父项UIViewController之后从列表中删除闭包的最佳方法或UIView,将从内存中删除。

2 个答案:

答案 0 :(得分:2)

只要您通过UIViewController作为弱引用,就很安全

ThemeManager.shared.bindAndFire { [weak self] in
    guard let strongSelf = self else { return }

    // here I will get theme changes and update my screen
}

但是NotificationCenter是更好的依赖方法,这是基本的ThemeManager示例

class ThemeManager {
    static let shared = ThemeManager()
    static let NotificationName = NSNotification.Name("Notifacation.ThemeManager")

    var theme: Theme!

    func switchTheme(_ theme: Theme) {
        self.theme = theme
        NotificationCenter.default.post(name: ThemeManager.NotificationName, object: self.theme)
    }
}

用法:

class ViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        NotificationCenter.default.addObserver(self, selector: #selector(themeDidUpdate(_:)), name: ThemeManager.NotificationName, object: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self)
    }

    @objc func themeDidUpdate(_ notification: Notification) {
        guard let theme = notification.object as? Theme else { return }

        // here I will get theme changes and update my screen
    }

}

Update-2 带有闭包的NotificationCenter示例

NotificationCenter.default.addObserver(forName: ThemeManager.NotificationName, object: nil, queue: OperationQueue.main) { [weak self] (notification) in
    guard let strongSelf = self, let theme = notification.object as? Theme else { return }

    // here I will get theme changes and update my screen
}

答案 1 :(得分:1)

您可以使用struct包装闭包,该闭包还具有一个属性,可以检查是否相等,并在添加闭包时返回该属性的值。如果要删除闭包,则视图控制器可以传递此id。您可以在其余代码中隐藏包装器。如果您不想跟踪某个计数器,也可以使用UUID。您还可以使用字典来存储以id为键的闭包。

class ThemeManager {
    private var counter = 0
    private var closures: [ClosureWrapper] = []

    private struct ClosureWrapper {
        let id: Int
        let closure: () -> Void
    }

    func bind(closure: @escaping () -> Void) -> Int {
        counter += 1
        let wrapper = ClosureWrapper(id: counter, closure: closure)
        closures.append(wrapper)
        return wrapper.id
    }

    func removeClosure(with id: Int) {
        guard let index = closures.firstIndex(where: { $0.id == id }) else {
            return
        }
        closures.remove(at: index)
    }
}

在此版本中,您无需跟踪闭包的ID。它使用带有弱键的NSMapTable来存储闭包。您可以将视图控制器作为键传递,并在取消分配时,将自动从映射表中删除传递的闭包。

class ThemeManager {
    private let closureTable = NSMapTable<NSObject, ClosureWrapper>(keyOptions: .weakMemory, valueOptions: .strongMemory)

    private class ClosureWrapper {
        let closure: () -> Void
        init(closure: @escaping () -> Void) {
            self.closure = closure
        }
    }

    func bind(from source: NSObject, closure: @escaping () -> Void) {
        let wrapper = ClosureWrapper(closure: closure)
        closureTable.setObject(wrapper, forKey: source)
    }

    func callClosures() {
        for key in closureTable.keyEnumerator().allObjects {
            let wrapper = closureTable.object(forKey: key as? NSObject)
            wrapper?.closure()
        }
    }
}