如何使用 Selector 和 NotificationCenter 调用可选函数

How to call an optional function using Selector and NotificationCenter

语言:Swift 3

IDE: XCode 8.3.2 (8E2002)

我有一个带有可选功能的协议foo

@objc protocol SomeProtocol {
    @objc optional func foo(_ notification: Notification)
}

extension SomeProtocol {
    func listenToFoo() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.foo(_:)), name: NSNotification.Name(rawValue: "UltimateNotificationKeyLOL"), object: nil)
    }
}

如果我将此代码扩展为 class,比如 UIViewController

class CrashingViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.listenToFoo()
    }
}

extension CrashingViewController: SomeProtocol { } 

现在问题来了,因为 foo 是一个可选函数,如果任何人发送一个 NotificationNSNotification.Name(rawValue: "UltimateNotificationKeyLOL") 应用程序将崩溃,因为我没有实现foo还没有。所以在这种情况下,上面的代码会导致崩溃。

但是如果我这样做

class GodzillaViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.listenToFoo()
    }
}

extension GodzillaViewController: SomeProtocol {
    func foo(_ notification: Notification) {
        print("lol")
    }
} 

没有崩溃,因为 foo(_:) 不再是可选的。

:此代码不可用#selector(self.foo?(_:))

问题:是否可以让选择器调用可选函数而不使应用程序崩溃?

如果我在你那里,我会像这样制定一个完整的swift协议:

// Protocol declaration
protocol SomeProtocol {
    func foo(_ notification: Notification)
}

// Provide default implementation for optional methods of SomeProtocol
extension SomeProtocol {
    func foo(_ notification: Notification) {}
}

// Extend SomeProtocol with additional methods
extension SomeProtocol {
    func listenToFoo() {
        NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "UltimateNotificationKeyLOL"), object: nil, queue: nil) { (notification) in
            self.foo(notification)
        }
    }
}

如您所见,这样做有很多好处:

  1. 您只有 Swift 代码(没有 @objc)
  2. 通过添加 foo 的默认实现,您将函数设为可选。
  3. 您的通知仍然可以调用 foo 而不会崩溃,因为它会在必要时转到默认实现
  4. 如果您仍然想做一些事情,您甚至可以在默认方法中添加一些代码!

更新

你可以在 listenToFoo() 函数中看到我使用了一个不同的 addObserver 函数,它使用闭包来代替,原因是 #selector 仍然需要将函数暴露给 @objc 和闭包没有:

func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol