如何在 Swift 3中创建自定义通知?

在 Objective-C 中,自定义通知只是一个普通的 NSString,但是在 Swift 3的 WWDC 版本中,通知应该是什么样子并不明显。

54629 次浏览

Notification.post 的定义是:

public func post(name aName: NSNotification.Name, object anObject: AnyObject?)

在 Objective-C 中,通知名称是一个普通的 NSString。

名称定义为:

public struct Name : RawRepresentable, Equatable, Hashable, Comparable {
public init(_ rawValue: String)
public init(rawValue: String)
}

这有点奇怪,因为我希望它是一个 Enum,而不是一些似乎没有更多好处的自定义结构。

在 Notification for NSNotification 中有一个类型化名称:

public typealias Name = NSNotification.Name

令人困惑的是,在 Swift 中同时存在通知和 NSNotification

因此,为了定义自己的自定义通知,可以这样做:

public class MyClass {
static let myNotification = Notification.Name("myNotification")
}

然后称之为:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)

我认为有一个更干净的方法来实现它

extension Notification.Name {


static let onSelectedSkin = Notification.Name("on-selected-skin")
}

然后你可以像这样使用它

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)

这只是参考

// Add observer:
NotificationCenter.default.addObserver(self,
selector: #selector(notificationCallback),
name: MyClass.myNotification,
object: nil)


// Post notification:
let userInfo = ["foo": 1, "bar": "baz"] as [String: Any]
NotificationCenter.default.post(name: MyClass.myNotification,
object: nil,
userInfo: userInfo)

可以向 NSNotification.Name 添加自定义初始值设定项

extension NSNotification.Name {
enum Notifications: String {
case foo, bar
}
init(_ value: Notifications) {
self = NSNotification.Name(value.rawValue)
}
}

用法:

NotificationCenter.default.post(name: Notification.Name(.foo), object: nil)

更简单的方法:

let name:NSNotification.Name = NSNotification.Name("notificationName")
NotificationCenter.default.post(name: name, object: nil)

你也可以用一个协议来解决这个问题

protocol NotificationName {
var name: Notification.Name { get }
}


extension RawRepresentable where RawValue == String, Self: NotificationName {
var name: Notification.Name {
get {
return Notification.Name(self.rawValue)
}
}
}

然后在任何地方将通知名称定义为 enum。例如:

class MyClass {
enum Notifications: String, NotificationName {
case myNotification
}
}

然后把它当成

NotificationCenter.default.post(name: Notifications.myNotification.name, object: nil)

这样,通知名称将与 FoundationNotification.Name解耦。您只需要修改您的协议,以防 Notification.Name的实现发生变化。

NSNotification.Name(rawValue: "myNotificationName")

我自己的实现混合了这里和那里的东西,发现这是最方便的。为感兴趣的人分享:

public extension Notification {
public class MyApp {
public static let Something = Notification.Name("Notification.MyApp.Something")
}
}


class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(self.onSomethingChange(notification:)),
name: Notification.MyApp.Something,
object: nil)
}


deinit {
NotificationCenter.default.removeObserver(self)
}


@IBAction func btnTapped(_ sender: UIButton) {
NotificationCenter.default.post(name: Notification.MyApp.Something,
object: self,
userInfo: [Notification.MyApp.Something:"foo"])
}


func onSomethingChange(notification:NSNotification) {
print("notification received")
let userInfo = notification.userInfo!
let key = Notification.MyApp.Something
let something = userInfo[key]! as! String //Yes, this works :)
print(something)
}
}

使用枚举的优点是我们可以让编译器检查名称是否正确。减少潜在的问题,使重构更容易。

对于那些喜欢使用枚举而不是引号字符串作为通知名称的人来说,这段代码很有用:

enum MyNotification: String {
case somethingHappened
case somethingElseHappened
case anotherNotification
case oneMore
}


extension NotificationCenter {
func add(observer: Any, selector: Selector,
notification: MyNotification, object: Any? = nil) {
addObserver(observer, selector: selector,
name: Notification.Name(notification.rawValue),
object: object)
}
func post(notification: MyNotification,
object: Any? = nil, userInfo: [AnyHashable: Any]? = nil) {
post(name: NSNotification.Name(rawValue: notification.rawValue),
object: object, userInfo: userInfo)
}
}

然后你可以这样使用它:

NotificationCenter.default.post(.somethingHappened)

尽管与这个问题无关,但为了避免输入引用的字符串,故事板接续部分也可以这样做:

enum StoryboardSegue: String {
case toHere
case toThere
case unwindToX
}


extension UIViewController {
func perform(segue: StoryboardSegue) {
performSegue(withIdentifier: segue.rawValue, sender: self)
}
}

然后,在视图控制器上,调用如下:

perform(segue: .unwindToX)

如果使用只有字符串的自定义通知,则没有理由扩展除 String以外的任何类

    extension String {
var notificationName : Notification.Name{
return Notification.Name.init(self)
}
}

@ CesarVarela 的回答很好,但是为了让代码更简洁一些,你可以这样做:

extension Notification.Name {
typealias Name = Notification.Name


static let onSelectedSkin = Name("on-selected-skin")
static let onFoo = Name("on-foo")
}

我可能会建议另一个类似于@CesarVarela 建议的选择。

extension Notification.Name {
static var notificationName: Notification.Name {
return .init("notificationName")
}
}

这将使您可以轻松地发布和订阅通知。

NotificationCenter.default.post(Notification(name: .notificationName))

希望这个能帮到你。

如果您希望在同时使用 Objective-C 和 Swift 的项目中使用这种方法,那么我发现在 Objective-C 中创建通知会更容易。

创建一个. m/. h 文件:

//CustomNotifications.h
#import <Foundation/Foundation.h>


// Add all notifications here
extern const NSNotificationName yourNotificationName;
//CustomNotifications.m
#import "CustomNotifications.h"


// Add their string values here
const NSNotificationName yourNotificationName = @"your_notification_as_string";

在您的 MyProject-Bridging-Header.h(以您的项目命名)向 Swift 公开它们。

#import "CustomNotifications.h"

像这样使用 Objective-C 中的通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(yourMethod:) name:yourNotificationName:nil];

在 Swift (5)中是这样的:

NotificationCenter.default.addObserver(self, selector: #selector(yourMethod(sender:)), name: .yourNotificationName, object: nil)