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


Answers:


32

您也可以为此使用协议

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)

这样,通知名称将与基金会分离Notification.Name。而且您只需要修改协议,以防Notification.Name更改实现。


这正是我最初认为应该起作用的方式-通知应该是枚举。谢谢你的把戏!
hexdreamer

没问题!我编辑了代码,以包括扩展名的构造,NotificationName因此该name属性仅添加到符合协议的枚举中。
halil_g

严格等效但更符合逻辑的IMO,您可以像下面这样在NotificationName上定义扩展名(而不是RawRepresentable):extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj

386

有一种更清洁的方法(我认为)可以实现

extension Notification.Name {

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

然后可以像这样使用它

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

2
我正在使用上面的代码。这是一个静态属性。
Cesar Varela

3
非常干净,我非常喜欢
Tom Wolters

10
extension NSNotification.Name 代替extension Notification.Name 。否则,Swift 3会投诉'Notification' is ambiguous for type lookup in this context
lluisgh '16

9
您为在字符串中输入错字并因此证明键入的通知名称的值而感到鼓舞:P
Dorian Roy

10
可能值得注意的是,这是Apple在WWDC 2016 Session 207中建议的方法developer.apple.com/videos/play/wwdc2016/207
Leon

36

Notification.post定义为:

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

在Objective-C中,通知名称是纯NSString。在Swift中,它定义为NSNotification.Name。

NSNotification.Name定义为:

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

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

Notification for NSNotification.Name中有一个类型别名:

public typealias Name = NSNotification.Name

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

因此,为了定义您自己的自定义通知,请执行以下操作:

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

然后调用它:

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

3
好答案。一些评论:这有点奇怪,因为我希望它是一个枚举 -枚举是一个封闭的集合。如果Notification.Name是枚举,则没人可以定义新的通知。我们将结构用于其他枚举类型,这些类型需要允许添加新成员。(请参阅快速发展提案。)
rickster

2
令人困惑的部分是,通知和NSNotification都在Swift中同时存在 -这Notification是一种值类型(结构),因此它可以从Swift的语义中受益,以实现价值(im)可变性。通常,基金会类型会在Swift 3中删除其“ NS”,但是如果存在一种新的基金会值类型来代替它,则旧引用类型会保留(保留“ NS”名称),以便在以下情况下仍可以使用它您需要引用语义或对其进行子类化。见提案
rickster

让我澄清一下:我希望通知名称像枚举错误一样是枚举。您可以定义自己的Error枚举,并使它们符合ErrorType。
hexdreamer,2016年

1
正确– Apple至少在理论上可以使NotoficationName(或类似的协议)成为一种协议,您可以在其中创建符合条件的类型。我不知道,但是他们可能没有这么做的原因……可能与ObjC桥接有关吗?如果您找到了更好的解决方案,请提交一个错误(对于开源,Foundation Swift已公开)。
rickster

2
您可能是正确的,因为它应该以小写字母开头。
hexdreamer


11

您可以将自定义初始化程序添加到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)

1
Swift 3.0.2的小写字母“枚举类型”和“ init(_ type:type)”
Jalakoo

@Jalakoo case枚举中的s应该小写,而不是枚举本身。类型名称是大写的,枚举是类型。
正常2017年

9

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

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

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

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

希望这会帮助你。


4

我做了自己的实现,将各处的内容混合在一起,发现这是最方便的。分享给任何可能感兴趣的人:

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)
    }
}


2

这只是参考

// 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)

1

使用枚举的优点是我们让编译器检查名称是否正确。减少潜在的问题并简化重构。

对于那些喜欢使用枚举而不是用引号引起来的通知名称的人,此代码可以解决问题:

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)

尽管与问题无关,但可以使用情节提要segue进行相同操作,以避免键入带引号的字符串:

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

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

然后,在您的视图控制器上,像这样调用它:

perform(segue: .unwindToX)

> NotificationCenter.default.post(.somethingHappened)这会引发错误;您在扩展程序中添加的方法接受更多参数。

0

如果您使用仅字符串的自定义通知,则没有理由扩展任何类,但 String

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

0

@CesarVarela的答案很好,但是为了使代码更清晰,您可以执行以下操作:

extension Notification.Name {
    typealias Name = Notification.Name

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

0

如果您希望它在同时使用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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.