NSNotificationCenter as the main events dispatcher across the app - ios

Is it correct to use NSNotificationCenter as the only handler for all the events inside the app?
Is it fine if I put a list of all possible events like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loginUser:", name: "userWillLogin", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showError:", name: "userLoginError", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadMainScreen:", name: "userDidLogin", object: nil)
// Is it ok if I put 10 or 20 more event listeners here?
}
Or is the intention of this functionality different? I find it appealing to use event listeners and handlers in this manner, but not sure if it's the recommended way to pass events and data across the app regarding performance and best practices.

When using only notifications you'll find yourself in troubles to debug what happened that something is broken. Notifications make tracking down bugs hard, because not always it's obvious what was the flow. There are situations when you should use them, but don't do it always via notifications - it's less readable.
The best way would be to use delegate pattern / blocks for 1:1 relationships and NSNotificationCenter / KVO for 1:n relationships.
Check out this link: http://nshipster.com/nsnotification-and-nsnotificationcenter/

Related

Observe com.apple.configuration.managed only, and not all Defaults?

I need to observe any changes to the "com.apple.configuration.managed" plist on iOS.
To do that, I now do this:
NotificationCenter.default.addObserver(self, selector: #selector(........), name: UserDefaults.didChangeNotification, object: nil)
this works and I get notified when any defaults changes. So then when I get that notification I filter for the "com.apple.configuration.managed" key on the notification object and then react as needed.
Is there a way to only observe changes to the "com.apple.configuration.managed" plist and not the entire App Defaults?

How to observe 'boldTextStatusDidChangeNotification' using default notification center in Swift 5

I'm trying to have observe running constantly in the app background which will trigger custom action if Accessibility Bold Text feature has been enabled via Mobile Device Settings.
My understanding is I need to add observe to the default notification center and the name of the notification is 'boldTextStatusDidChangeNotification'.
Can someone advice on the code sample for this?
You can check the state of many accessibility options thanks to a bunch of events provided by the system.
Add an observer as follows:
NotificationCenter.default.addObserver(self,
selector: #selector(methodToBeCalled(notification:)),
name: UIAccessibility.boldTextStatusDidChangeNotification,
object: nil)
... and create the method to be fired when the appropriate event occurs:
#objc private func methodToBeCalled(notification: Notification) {
//Create your actions here.
}
If you want further information or check a complete list of these events with code snippets, I suggest to take a look at this site. 👍

iOS/Swift - Lifecycle methods causing network request collisions

In my application, I have a home screen. Anytime this screen is loaded, it needs to make a network request (currently using Alamofire 5.2) to fetch the latest data needed to display. I am running into this issue that I believe has to do with my implementation of view lifecycles, but I am not sure how to get around it to achieve my desired effect.
Scenario 1
override func viewDidLoad() {
NotificationCenter.default.addObserver(
self,
selector: #selector(wokeUp),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
#objc func wokeUp() {
pageLoaded()
}
func pageLoaded(){
// network requests made here
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}
Here I am registering the observer within viewDidLoad. The reason I need this is many of our users will not close the application. We've found on more than a few occasions that they will let the phone sleep while the application is open, so we need to make this request when the phone is woken up and immediately back at this screen. the didBecomeActiveNotification seems to be what takes care of that.
Scenario 2
override func viewDidAppear(_ animated: Bool) {
pageLoaded() // same network request as example above
}
We also need to call this request within viewDidAppear, as there are quite a few flows where the user is brought back to this home page from another view in the application, and it's important that the request is made here as well (what the user does in the other flows has an impact of what shows up here, so we have to make sure it's updated).
The problem is that what I am finding is these two scenario will occasionally clash - our server essentially gets the same request twice, which is not ideal and causing issues. I've noticed the majority (if not all) of the problems occur when opening the application when it's no longer in memory (viewDidLoad gets called); the case of bringing the app from the background to foreground while it's still in memory is working as expected, but I have no idea what other implementation I could take to cover all of my bases here. Any insight would be appreciated.
Why not just add a simple boolean flag to your networking logic to make sure only 1 request gets fired. e.g.
class SomeViewController {
private var isFetching = false
...
func pageLoaded() {
guard isFetching == false else {
return
}
isFetching = true
// do some networking
// ....
// inside the callback / error cases
isFetching = false
}
}
depending on how big your app is, if you have many requests and/or the same request being fired on many screens. Move all your networking to another class and have this logic inside the network service rather than the viewController

How can I setup a listener for SBMediaVolumeChangedNotification?

I have an app that uses the darwin notify center.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
displayStatusChanged, // callback
CFSTR("com.apple.iokit.hid.displayStatus"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
I would really like to capture volume button presses in the background. I found these notifications listed
http://iphonedevwiki.net/index.php/SpringBoard.app/Notifications
Is there a way to trigger methods on SBMediaVolumeChangedNotification?
The SBMediaVolumeChangedNotification is not a Darwin notification, it is a default local notification. To capture it, you would need a common notification observer, like this one (in Swift):
NSNotificationCenter.defaultCenter().addObserver(self, selector: "volumeChanged:", name: "SBMediaVolumeChangedNotification", object: nil)
However, the documentation states that:
The object is an SBMediaController instance.
So I think we would need to pass a SBMediaController instance to the observer, as the object, to make it work. Since the SBMediaController class seems to be inaccessible from the SDK, I don't think there is a way to use that specific notification. I have tried without the object, but it didn't work for me.

mergeChangesFromContextDidSaveNotification Consumes Memory

I have two NSManagedObjectContext, one for ui and one for background tasks. I'm trying to merge changes to UIcontext whenever the background one changed. But whenever I call
mergeChangesFromContextDidSaveNotification:notification
It just start eating memory (will goes up to 1GB on simulator) and cause a crash.
of course I setup a notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(coreUpdateFromApp:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
and also tried doing the merge in main thread, etc. No luck!
I found out that URIRepresentation is causing the issue. For some reason it's keep being called. (by apple's code not mine)
Note that I let it run for under a sec and it uses 64.95MB it will grow pretty fast with same call tree. If I keep it running it would crash the osx as well!
The problem is object:nil. You are listening to an endless echo of notifications.
You need to specify a specific context object to listen to notifications from.
The Problem here is, Since Google Analytics also using core-data we are intercepting the endless notifications fired by Google Analytics as well.
Setting object to non-nil value didn't work for me. Found another way and it worked for me as magic.
Inside your observer I have selector method as below
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: nil)
and,
func managedObjectContextDidSave(notification: NSNotification) {
if notification.object?.persistentStoreCoordinator != self.persistentStoreCoordinator {
return
}
//do remaining task here.
}

Resources