I am trying to write an extension for NotificationCenter
I find the syntax a little meaty and "boilerplatey" and would like to provide a simple extension that simplifies posting and observing.
I can dispatch an event like so
NotificationCenter.dispatch(key: <#T##String#>, payload: <#T##[String : String]#>)
However I would like to observe an event in a similar fashion.
I am trying to create something like
NotificationCenter.observe(key: <#T##String#>, handler: <#T##() -> Void#>)
However this is not correct. I am unsure how I can handler passing in my selector function that should be triggered on an observation?
This is my attempt so far.
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: ()->Void) {
NotificationCenter.default.addObserver(
self, selector: handler, name: NSNotification.Name(rawValue: key), object: nil
)
}
}
It sounds like you need something like
extension NotificationCenter {
static func dispatch(key: String, payload: [String: String] = [:]) {
self.default.post(name: NSNotification.Name(rawValue: key), object: nil, userInfo: payload)
}
static func observe(key: String, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: NSNotification.Name(rawValue: key), object: nil, queue: .main, using: handler)
}
}
You can use something like this:
extension NotificationCenter {
class func observe(name: NSNotification.Name, handler: #escaping (Notification) -> Void) {
self.default.addObserver(forName: name, object: nil, queue: .main, using: handler)
}
}
Then you can use it like this:
NotificationCenter.observe(name: UIResponder.keyboardDidShowNotification) { notification in
// do something
}
You should definitely check out https://developer.apple.com/documentation/foundation/notificationcenter/1411723-addobserver regarding unregistering observations though.
NotificationCenter - a useful but bulky looking interface, feels a bit bloating in code, as someone coming from UNIX-like thread synchronization semantics... So, these extensions are based on stuff on S.O. & the 'Net, with a couple of twists.
extension Notification.Name {
static let patientlyWaiting = Notification.Name("patientlyWaiting")
// .
// . list your arbitrary waiter names here...
// .
}
extension NotificationCenter {
static func wait(_ name : Notification.Name) async {
for await _ in NotificationCenter.default.notifications(named: name) {
break;
}
}
static func post(_ name : Notification.Name) {
NotificationCenter.default.post(name: name, object: nil)
}
static func postProcessing(_ name: Notification.Name, using block: #escaping (Notification) -> Void) {
NotificationCenter.default.addObserver(forName: name, object: nil, queue: OperationQueue.main, using: block)
}
}
To use it, something like this:
Waiting for notification:
#IBAction func doSomethingAsychronouslyButtonPushed(_ sender: Any) {
doSomethingAsynchronously()
NotificationCenter.postProcessing(.patientlyWaiting, using: { _ in
print("doSomethingAsynchronouslyButton completion handler called")
})
}
Notifying:
func doSomethingAsynchronously() {
for n in ["Time", " ", "consuming", " - ", "whatever"] {
print("\(n)", terminator: "")
}
print("\n")
NotificationCenter.post(.patientlyWaiting)
}
Note: You could avoid using a closure and do an inline wait using Swift language's recently-added async/await support (see the 'wait()function in the extensions shown above), but I didn't provide an example of using that because I haven't been able to invokeasyncfunctions from selectors, such as from aUIButtonpress, directly or indirectly; because, any function that usesawaitneeds to be declaredasync. Specifically, #selector(afunc(_:)) doesn't match functions declared async, and I'm unaware of a function signature syntax that accounts for an asyncin function declaration (to pass to#selector()`). If someone knows, let me know in the comments and I'll update the answer.
Related
Can we customize SwiftEventBus Library to only trigger in the current active ViewController.
I'm trying to trigger an action when ever a notification occurs, so i'm using swift event bus to trigger when ever a push notification comes but it is triggering in all the places it is registered. Can we make so that it will only trigger the action in the active view. If not is there any other library I can use?
Wouldn't it be enough to deregister inactive ViewControllers as mentioned in the SwiftEventBus readme?
//Perhaps on viewDidDisappear depending on your needs
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
SwiftEventBus.unregister(self)
}
Modify library(or subclass SwiftEventBus) like below:
#discardableResult
open class func on(_ target: AnyObject, name: String, sender: Any? = nil, queue: OperationQueue?, handler: #escaping ((Notification?) -> Void)) -> NSObjectProtocol {
let id = UInt(bitPattern: ObjectIdentifier(target))
//modification start
let handlerIner:((Notification?) -> Void) = { [weak target] n in
if let vc = target as? UIViewController, vc.view?.window != nil {
handler(n)
}
}
let observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: name), object: sender, queue: queue, using: handlerIner)
// modification end
let namedObserver = NamedObserver(observer: observer, name: name)
Static.queue.sync {
if let namedObservers = Static.instance.cache[id] {
Static.instance.cache[id] = namedObservers + [namedObserver]
} else {
Static.instance.cache[id] = [namedObserver]
}
}
return observer
}
So I have the following partial function implementation:
func addObserver(with notificationType: String) -> (#escaping (Notification) -> (Void)) -> NSObjectProtocol
{
return { (function: #escaping (Notification) -> (Void)) in
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: notificationType), object: nil, queue: nil, using: function)
}
}
Which basically would be used as such to create a full function such as:
let aoSelectCountry = addObserver(with: selectCountryNotification)
Which would then be used as:
self.selectCountryObserver = aoSelectCountry{ (notification) in
if (notification.object != nil) {
//do things
}
}
This is effectively the same as writing:
self.selectCountryObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: selectCountryNotification), object: nil, queue: nil) { (notification) in
if (notification.object != nil) {
//do things
}
Now all of this worked in XCode 8. But when I try to compile in XCode 9, I get the following error:
Cannot convert value of type '(Notification) -> (Void)' to expected argument type '(Notification) -> Void'
Removing the brackets around the (Void) part of the initial function just changes the error message to:
Cannot convert value of type '(Notification) -> Void' to expected argument type '(Notification) -> Void'
I have a strong feeling that this is a compiler bug, but I do desperately need a work around. I appreciate anyone's efforts. Thanks!
In the server side I use socket.io library to send some JS object data type through, upon users typing in their devices:
var typingUsers = {};
clientSocket.on("startType", function(clientNickname){
typingUsers[clientNickname] = 1;
io.emit("userTypingUpdate", typingUsers);
});
when the app receives data, it will post notification:
private func listenForOtherMessages() {
socket.on("userTypingUpdate") { (dataArray, socketAck) -> Void in
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "userTypingNotification"), object: dataArray[0] as! [String: AnyObject])
}
}
And then the notification is added to a view controller as an observer:
NotificationCenter.default.addObserver(self, selector: Selector(("handleDisconnectedUserUpdateNotification:")), name: NSNotification.Name(rawValue: "userWasDisconnectedNotification"), object: nil)
This implementation always throw an error "unrecognised selector send to ..."
But the following implementation without a selector works ok:
NotificationCenter.default.addObserver(forName:NSNotification.Name(rawValue: "userTypingNotification"), object: nil, queue: nil){ object in
print(object)
}
It seems like I cannot add an selector to the observer, I think the problems might lie in the object data type, but I cannot really figure out why..
found the answer:
NotificationCenter.default.addObserver(self, selector: #selector(self.userTypingNotification), name: NSNotification.Name(rawValue: "userWasDisconnectedNotification"), object: nil)
older post
I have a strange issue. I register and unregister my Notification like so:
func doRegisterNotificationListener() {
NotificationCenter.default.addObserver(forName: Notification.Name(rawValue: "RateAlertRated"), object: nil, queue: nil, using: rateDidRate)
}
func doUnregisterNotificationListener() {
NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "RateAlertRated"), object: nil)
}
func rateDidRate(notification: Notification) {
let rating = notification.userInfo?["score"] as? Int
let message = notification.userInfo?["message"] as? String
let response = Response(rating: rating, message: message)
output.presentRated(response)
}
This view controller is in a UITabBarController. doRegisterNotificationListener is called in viewDidAppear and doUnregisterNotificationListener is called in viewDidDisappear. When I switch between tabs the register and unregister methods are being called correctly (I tested using a print statement). However if I fire a notification it will still be received even though doUnregisterNotificationListener was called last. Any ideas what I might be doing wrong here?
Quick note:
Also tried:
NotificationCenter.default.removeObserver(self)
This also doesn't work.
I have tested your code and once i register observer with this type it is not called when you doUnregister it. please try this.
NotificationCenter.default.addObserver(self, selector: #selector(rateDidRate(notification:)), name: Notification.Name(rawValue: "RateAlertRated"), object: nil)
If you are working with addObserver(forName:object:queue:using:) you should remove it in this way:
Create:
let center = NSNotificationCenter.defaultCenter()
let mainQueue = NSOperationQueue.mainQueue()
self.localeChangeObserver = center.addObserverForName(NSCurrentLocaleDidChangeNotification, object: nil, queue: mainQueue) { (note) in
print("The user's locale changed to: \(NSLocale.currentLocale().localeIdentifier)")
}
Remove:
center.removeObserver(self.localeChangeObserver)
This approach is taken from the documentation.
I would like to access the Notification object that is sent from the method below.
var currentTrack:MPMediaItem? {
get{
return playlist?.items[index]
}
set{
print(newValue?.title!)
//self.index = locateIndex(track: newValue!)
let notif = Notification.init(name: Playlist.SongChangedName, object:self)
NotificationCenter.default.post(notif)
}
}
The Notifications name is defined as:
static let SongChangedName = Notification.Name("SongChangedNotification")
Here is the observer:
override init() {
super.init()
NotificationCenter.default.addObserver(self,
selector: #selector(testSelector),
name: Playlist.SongChangedName, //Notification.Name("songChanged"),
object: nil)
}
Here is the Method it calls:
func testSelector(notification:Notification){
queueNextTrack()
}
How do I pass testSelector a notification object? I know it has something to do with the object parameter of the addObserver method.
Thank you.
You can now get rid of your problem entirely by not using selectors in your notifications, timers, etc. There are new block based API to replace target-selector such as
NotificationCenter.default.addObserver(forName: Playlist.SongChangedName, object: nil, queue: nil, using: { notification in
self.testSelector(testSelector)
})
For the most part you won't need access to the notifications in your blocks so you could do this too
func testSelector(){
queueNextTrack()
}
NotificationCenter.default.addObserver(forName: Playlist.SongChangedName, object: nil, queue: nil) { _ in
self.testSelector()
}
or most my preferred in most scenarios:
override init() {
super.init()
let testBlock: (Notification) -> Void = {
self.queueNextTrack()
}
NotificationCenter.default.addObserver(forName: Playlist.SongChangedName, object: nil, queue: nil, using: testBlock)
}
EDIT I'd also suggest you take a look at the sample code in the description for this API