UI: use NotificationToken to properly clean up observers

When the variable goes out of scope, the observer isn't removed unless
an explicit call is made to the token.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
This commit is contained in:
Andrej Mihajlov
2020-12-22 11:09:18 +01:00
committed by Jason A. Donenfeld
parent 74cd7041dc
commit 631286e2d1
4 changed files with 67 additions and 25 deletions
+33
View File
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
/// This source file contains bits of code from:
/// https://oleb.net/blog/2018/01/notificationcenter-removeobserver/
/// Wraps the observer token received from
/// `NotificationCenter.addObserver(forName:object:queue:using:)`
/// and unregisters it in deinit.
final class NotificationToken {
let notificationCenter: NotificationCenter
let token: Any
init(notificationCenter: NotificationCenter = .default, token: Any) {
self.notificationCenter = notificationCenter
self.token = token
}
deinit {
notificationCenter.removeObserver(token)
}
}
extension NotificationCenter {
/// Convenience wrapper for addObserver(forName:object:queue:using:)
/// that returns our custom `NotificationToken`.
func observe(name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NotificationToken {
let token = addObserver(forName: name, object: obj, queue: queue, using: block)
return NotificationToken(notificationCenter: self, token: token)
}
}
@@ -23,9 +23,9 @@ class TunnelsManager {
private var tunnels: [TunnelContainer]
weak var tunnelsListDelegate: TunnelsManagerListDelegate?
weak var activationDelegate: TunnelsManagerActivationDelegate?
private var statusObservationToken: AnyObject?
private var waiteeObservationToken: AnyObject?
private var configurationsObservationToken: AnyObject?
private var statusObservationToken: NotificationToken?
private var waiteeObservationToken: NSKeyValueObservation?
private var configurationsObservationToken: NotificationToken?
init(tunnelProviders: [NETunnelProviderManager]) {
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
@@ -406,7 +406,7 @@ class TunnelsManager {
}
private func startObservingTunnelStatuses() {
statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
statusObservationToken = NotificationCenter.default.observe(name: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
guard let self = self,
let session = statusChangeNotification.object as? NETunnelProviderSession,
let tunnelProvider = session.manager as? NETunnelProviderManager,
@@ -438,7 +438,7 @@ class TunnelsManager {
}
func startObservingTunnelConfigurations() {
configurationsObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNConfigurationChange, object: nil, queue: OperationQueue.main) { [weak self] _ in
configurationsObservationToken = NotificationCenter.default.observe(name: .NEVPNConfigurationChange, object: nil, queue: OperationQueue.main) { [weak self] _ in
DispatchQueue.main.async { [weak self] in
// We schedule reload() in a subsequent runloop to ensure that the completion handler of loadAllFromPreferences
// (reload() calls loadAllFromPreferences) is called after the completion handler of the saveToPreferences or
@@ -18,6 +18,9 @@ class LogViewController: NSViewController {
}
}
private var boundsChangedNotificationToken: NotificationToken?
private var frameChangedNotificationToken: NotificationToken?
let scrollView: NSScrollView = {
let scrollView = NSScrollView()
scrollView.hasVerticalScroller = true
@@ -104,13 +107,13 @@ class LogViewController: NSViewController {
clipView.documentView = tableView
scrollView.contentView = clipView
_ = NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
boundsChangedNotificationToken = NotificationCenter.default.observe(name: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
let lastVisibleRowIndex = self.tableView.row(at: NSPoint(x: 0, y: self.scrollView.contentView.documentVisibleRect.maxY - 1))
self.isInScrolledToEndMode = lastVisibleRowIndex < 0 || lastVisibleRowIndex == self.logEntries.count - 1
}
_ = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
frameChangedNotificationToken = NotificationCenter.default.observe(name: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
if self.isInScrolledToEndMode {
DispatchQueue.main.async {