Как вы создаете собственные уведомления в Swift 3?

Ответы:

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)

Таким образом, имена уведомлений будут отделены от Foundation Notification.Name. И вам останется только изменить свой протокол в случае реализации Notification.Nameизменений.

halil_g
источник
Именно так я изначально думал, что это должно работать - уведомления должны быть перечислениями. Спасибо за фокус!
hexdreamer
Нет проблем! Я редактировал код включить конформации расширения для NotificationNameпоэтому nameсвойство добавляется только к перечислениям , которые соответствуют протоколу.
halil_g
Строго эквивалентный, но более логичный IMO, вы можете определить расширение для NotificationName (вместо RawRepresentable) следующим образом:extension NotificationName where Self: RawRepresentable, Self.RawValue == String {
jlj
388

Есть более чистый (я думаю) способ добиться этого

extension Notification.Name {

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

И тогда вы можете использовать это так

NotificationCenter.default.post(name: .onSelectedSkin, object: selectedSkin)
Сезар Варела
источник
2
Я использую приведенный выше код. Это статическое свойство.
Сезар Варела
3
Очень чисто, мне это очень нравится
Том Уолтерс
10
extension NSNotification.Name вместо extension Notification.Name . В противном случае жалобы на Swift 3 с'Notification' is ambiguous for type lookup in this context
lluisgh
9
Вы получили мой голос за опечатку в строке и, таким образом, демонстрацию значения напечатанных имен уведомлений: P
Дориан Рой,
10
Возможно, стоит отметить, что это метод, предложенный Apple на WWDC 2016 Session 207 developer.apple.com/videos/play/wwdc2016/207
Леон,
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 есть typealias:

public typealias Name = NSNotification.Name

Непонятная часть заключается в том, что в Swift существуют как Notification, так и NSNotification.

Итак, чтобы определить собственное уведомление, сделайте что-нибудь вроде:

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

Затем назвать это:

NotificationCenter.default().post(name: MyClass.myNotification, object: self)
мечтатель
источник
3
Хороший ответ. Некоторые комментарии: Это немного странно, поскольку я ожидал, что это будет Enum - Enum - это закрытый набор. Если бы это Notification.Nameбыло перечисление, никто бы не смог определять новые уведомления. Мы используем структуры для типов, подобных перечислению, которые должны разрешать добавление новых членов. (См. Предложение о быстрой эволюции .)
Рикстер
2
Запутанная часть является то , что как уведомление и NSNotification существует в Swift - Notificationэто тип значения (а структура), так что он может извлечь выгоду из семантики Свифта для значения (им) изменчивости. Как правило, типы Foundation удаляют свои "NS" в Swift 3, но там, где существует один из новых типов значений Foundation для его замены, старый ссылочный тип остается (сохраняя имя "NS"), так что вы все еще можете использовать его, когда вам нужна эталонная семантика или ее подкласс. Смотрите предложение .
rickster
Позвольте мне уточнить: я ожидаю, что имена уведомлений будут перечислениями, как и ошибки. Вы можете определить свои собственные перечисления ошибок и сделать их соответствующими ErrorType.
hexdreamer
1
Верно - Apple, по крайней мере, теоретически могла бы сделать NotoficationName (или что-то подобное) протоколом, для которого вы создаете соответствующие типы. Я не знаю, но, вероятно, они этого не сделали ... Возможно, что-то связано с мостом через ObjC? Сообщите об ошибке (для открытого исходного кода , Foundation Swift находится в открытом доступе), если у вас есть лучшее решение.
rickster
2
Вероятно, вы правы в том, что он должен начинаться со строчной буквы.
hexdreamer
13

Более простой способ:

let name:NSNotification.Name = NSNotification.Name("notificationName")
NotificationCenter.default.post(name: name, object: nil)
Золтан Варади
источник
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
Строчные буквы 'enum type' и 'init (_ type: type)' для Swift 3.0.2
Jalakoo
@Jalakoo В caseнижнем регистре должны быть только s в перечислении, а не само перечисление. Имена типов пишутся в верхнем регистре, а перечисления - это типы.
manmal
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)
    }
}
inigo333
источник
3
NSNotification.Name(rawValue: "myNotificationName")
Ли Проберт
источник
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)
пользователь6943269
источник
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)

Хотя это не связано с вопросом, то же самое можно сделать с сегментами раскадровки, чтобы не вводить строки в кавычках:

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")
}
ThomasW
источник
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)
nickdnk
источник