NotificationCenter addObserver(observer:selector:name:object) -- what is object? - ios

I am having trouble understanding what the object parameter is in NotificationCenter.default.addObserver(observer:selector:name:object)
If I understand it correctly, it acts as a kind of filter; only notifications posted from this object will be observed. But I can't seem to actually figure out how to use it.
I created a class and made a global instance of it
class FooClass {
func postNotification() {
NotificationCenter.default.post(name: NSNotification.Name("TestNotification"), object: self)
}
}
let globalFoo = FooClass()
Then in my first ViewController I press a button which calls globalFoo.postNotification()
Then in my second ViewController I registered like so:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived), name: NSNotification.Name("TestNotification"), object: globalFoo)
}
func notificationReceived() {
print("notification received")
}
}
It works fine when I don't specify object (i.e. nil), so clearly I'm misunderstanding what it is.

The object parameter used when posting a notification is to indicate what object is actually posting the notification.
When adding an observer, you can leave object nil and you will get all of the named notifications regardless of which object actually sent the notification. Or you can specify a specific object when adding an observer and you will then only be notified when that specific object posts the named notification.

Some notifications use this parameter to provide more appropriate information to the observer.
For example, notifications like NSManagedObjectContextObjectsDidChange optionally accepts NSManagedObjectContext object so that it can notify changes only from that context.

Related

What exactly is the default NotificationCenter property?

I want to create an additional NotificationCenter instance to separate my internal notifications from system notifications. However, when I looked at the class implementation, I did not understand what I was looking at.
open class NotificationCenter : NSObject {
open class var default: NotificationCenter { get }
}
Where is the getter? What does default exactly get? This is what I was expecting to see:
extension NotificationCenter {
static let default = NotificationCenter()
}
Is the default property a static singleton that we just don't see and the getter is just returning it?
And to implement my own custom center, could I just declare it the following way?
extension NotificationCenter {
static let custom = NotificationCenter()
}
NotificationCenter.default gets the default NotificationCenter instance for your app, which is a singleton.
The iOS frameworks are not open source, so when you look at the definition of NotificationCenter.default you see the interface it exposes, not the implementation, which would probably look very much like the code you expected to see.
You can, indeed, create your own NotificationCenter instances using NotificationCenter()

Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization?

Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!

Does the target of #selector() depend on the context of `addObserver` (class vs object)?

The full test case below is supposed to demonstrate: a selector, even though it is specified identically in two places, is performed differently: either it is performed on the class, or on the object. (I understand that a static method and an object method can share the same name, but there is only one below.) Whether the receiver is class or object seems to depend on where the “same” selector is made known to NSNotificationCenter, either in class context or in method context:
a static method has the call to addObserver, or
an object method has the call addObserver
while the calls are otherwise identical.
If the identical call occurs in a static method, then when the notification is processed later, the system tries to invoke the selector on the class, not the object. The class does not have it. The code compiles fine with the new (in 2.2) syntax. Is this result to be expected?
import XCTest
import class Foundation.NSNotificationCenter // for emphasis
class SelectorTests: XCTestCase {
static let NotificationName = "OneTwoThreeNotification"
override func setUp() {
super.setUp()
}
override func tearDown() {
NSNotificationCenter.defaultCenter().removeObserver(self)
super.tearDown()
}
func addObserverForTestNormal() { // <- HERE
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(SelectorTests.myMethod(_:)), // <- HERE
name: SelectorTests.NotificationName,
object: nil)
}
func testNormal() {
self.addObserverForTestNormal()
NSNotificationCenter.defaultCenter().postNotificationName(
SelectorTests.NotificationName,
object: self)
}
static func addObserverForTestStatic() { // <- HERE
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(SelectorTests.myMethod(_:)), // <- HERE
name: SelectorTests.NotificationName,
object: nil)
}
func testStatic() {
SelectorTests.addObserverForTestStatic()
NSNotificationCenter.defaultCenter().postNotificationName(
SelectorTests.NotificationName,
object: self)
}
func myMethod(x : Int) {
XCTAssert(true)
}
}
One test succeeds, the other fails. The gist of the stack trace and message is
"+[KuckuckTests.SelectorTests myMethod:]: unrecognized selector sent to class
Is this schism, i.e. class or object “inferred” from addObserver-context, so obvious to old Objective-C hands that it isn't worth mentioning with #selector? In this case, could you point out some documentation?
Edit: just noticed that self in the static function's invocation
of addObserver is perhaps referring to the class, not to some object. That makes the effect somewhat plausible, and suggests that programmers should know what overloaded names stand for…
Nothing about a #selector expression has any connection to the use site of that selector. A selector names a message, and says nothing about the receiver of that message. You can use a #selector expression to create a Selector value for a method on one object, then pass that Selector value to an API (like NSNotificationCenter.addObserver or UIControl.sendAction or NSTimer.init) that'll result in sending a message with that selector to some completely different object.
This loose binding is an intentional part of the dynamic nature of the Objective-C runtime Cocoa uses for passing these messages (regardless of whether the functions referenced by your selectors are build in ObjC or Swift). The #selector expression, and the Swift function-reference syntax it depends on, give you a way to "sorta" strongly type your use of selectors, but only on one end — they let you verify that the Selector value you're constructing refers to a specific method. (But once you have a Selector value, how it gets used is out of Swift's control.)
Your error message (emphasis added):
unrecognized selector sent to class
...indicates that the failure is because the message is being sent to the SelectorTests class object (aka the metaclass object). That is, by scheduling a notification to be sent to self in a static method, you're asking for a call to class func myMethod, not to func myMethod.
The self keyword always refers to the instance responsible for the code that's executing: inside an instance method, self refers to the current instance. Inside a class method, self refers to the (only instance of) the class object.

Use of string literal for Objective-C selectors is deprecated, use '#selector' instead

I have the following code:
func setupShortcutItems(launchOptions: [NSObject: AnyObject]?) -> Bool {
var shouldPerformAdditionalDelegateHandling: Bool = false
if (UIApplicationShortcutItem.respondsToSelector("new")) {
self.configDynamicShortcutItems()
// If a shortcut was launched, display its information and take the appropriate action
if let shortcutItem: UIApplicationShortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
// When the app launched at the first time, this block can not called.
self.handleShortCutItem(shortcutItem)
// This will block "performActionForShortcutItem:completionHandler" from being called.
shouldPerformAdditionalDelegateHandling = false
} else {
// normal app launch process without quick action
self.launchWithoutQuickAction()
}
} else {
// Less than iOS9 or later
self.launchWithoutQuickAction()
}
return shouldPerformAdditionalDelegateHandling
}
I get the following "warning" on UIApplicationShortcutItem.respondsToSelector("new"), which says:
Use of string literal for Objective-c selectors is deprecated, use '#selector' instead
The warning replaces the code automatically with:
UIApplicationShortcutItem.respondsToSelector(#selector(FBSDKAccessToken.new))
However this doesn't compile because new() is unavailabe.
What am I supposed to use in this case?
Xcode 7.3 using swift for iOS9.3/watchOS2.2/...
If you previously used this line of code:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateResult:", name: "updateResult", object: nil)
you should now use this line of code:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(InterfaceController.updateResult(_:)), name: "updateResult", object: nil)
at least this is what Xcode offered me after I changed around a few characters in the code. Seems like it doesn't always offer the correct solution when you are presented with this error.
Create a protocol whose only reason for existing is to allow you to construct the appropriate selector. In this case:
#objc protocol NewMenuItemHandling {
func new()
}
You are taking the informal protocol (an object that responds to the new selector) and making it into a formal protocol.
Then where you want to use the selector you can add the expression:
#selector(NewMenuItemHandling.new)
In this special respondsToSelector situation, where you have no existing method with which to associate a function reference, write this:
UIApplicationShortcutItem.respondsToSelector(Selector("new"))
You'll still get a warning (you shouldn't, and I've filed a bug against it), but it will be a different warning and you can ignore it.
In summary, every "selector: function or object" is now "selector: #selector(class.funtion(_:))" wherever used.

Automatically update an UILabel at var update in swift

At some point of my project I got an UILabel displaying the number of element in an array.
I want to update automatically the label text when I modifying the array.
I can make my UILabel global and access it with a "didSet" on the array, but I don't want to make all my UILabel global (I got 8 UILabel for 8 different evolutive var).
Here is a way to update the UILabel content like a pointer or a reference on a specific variable ?
Edit
Some code example :
I got a global which is a dictionary,
let eg:[string : [SomeClass]]!
In a ViewController I got a UILabel, with
label.text = eg["key"].count
I want to automatically update the value display if I do something like that
eg["key"].append(something)
Since you are using global data, one approach you might try is using NSNotificationCenter. If you were to encapsulate your global data inside an object, the object could post a notification every time the data is updated.
This approach will allow multiple observers the opportunity to act on changes to global state "automatically". It will also add the benefit of keeping your UIKit elements from being exposed.
Inside the userInfo property of the posted notification, you would place the key and associated count value.
Make your view controller an observer of this notification. When it receives the notification, it can update its UILabels itself, using the data received inside userInfo.
A simple implementation might look like this:
class SomeClass {
// ...
}
class MyGlobalObject {
var eg:[String : [SomeClass]]!
static let sharedInstance = MyGlobalObject()
private init() {
eg = ["someKey":[], "someOtherKey":[]]
}
func appendTo(key:String, value:SomeClass) {
eg[key]?.append(value)
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name:"ValueChanged", object: nil, userInfo: ["Key":key, "Count" : (eg[key]?.count)!]))
}
}
class ViewController: UIViewController {
// Define labels, etc....
override func viewDidLoad() {
super.viewDidLoad()
// Put this wherever makes the most sense. viewWillAppear() works too. Don't forget to remove yourself as an observer when you are done.
NSNotificationCenter.defaultCenter().addObserver(self, selector:"didUpdate:", name: "ValueChanged", object: nil)
}
func didUpdate(notif:NSNotification) {
print("Received Notification!")
let userInfo = notif.userInfo
// Update appropriate label with the data
}
}
Then anywhere in your app, you could do this:
MyGlobalObject.sharedInstance.appendTo("someKey", value: SomeClass())

Resources