Swift selector function with optional closure? - swift

What am I doing wrong with the #selector assignment when I add the notification observer?
NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func reloadData(completionHandler: ((Bool) -> Void)? = nil ) {
mainCollectionView.reloadData()
completionHandler?(true)
}
The app crashes whenever I post the notification:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
I have already tried things like #selector(reloadData(completionHandler: nil)
EDIT:
I have tried the selector #selector(reloadData(completionHandler:)) but it still crashes n the line where I post a notification, with the error message:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x8)
When I change the code as follows everything works fine but it is not really nice to create a function just to call another function
NotificationCenter.default.addObserver(self, selector: #selector(testCall), name: NSNotification.Name(rawValue: "reloadCollectionData"), object: nil)
func testCall() {
self.reloadData()
}

A completion handler in a notification selector method is nonsensical (and illegal). What do you expect is the object which is supposed to be the receiver of the completion handler ... ?
The syntax of a notification selector is very similar to a target/action selector. The passed parameter must be the type of the affected object (here Notification).
func reloadData(_ notification : Notification) { ...
The alternative is the block based API of Notification which is able to capture values of the enclosing function / method.
The only way to pass custom data in a notification object with specified selector is the (optional) userInfo dictionary.

Related

Selector function not being entered when observer is added

In my first view controller, I post the notification with the following code:
NotificationCenter.default.post(name: Notification.Name("date"), object: formattedDate)
I then "receive" the notification in a second view controller with the following code:
func receiveNotification () {
NotificationCenter.default.addObserver(self, selector: #selector(self.didGetTheDate(_:)), name: NSNotification.Name("date"), object: nil)
}
#objc
func didGetTheDate(_ notification: Notification) {
print("In did get date")
date = notification.object as! String
}
However, the function "didGetTheDate" never gets called.
I have triple checked that the function "receiveNotification" gets called as I have added print statements to check this.
Can somebody please help me with this.
NSNotificacionCenter is a variation of the Observer Pattern, you can read about it here
This means that you will have to register an Observer before posting any notification. If you post anything before that, the NSNotificationCenter will look at the observer for name and will see that nobody is listening for it, thus nothing will happen.

Very strange bug in this 1 line function

So I stumbled onto something really weird.
Here is a 1 line function
#objc func foo(value: Int = 1) {
print("value is \(value)")
}
I'm gonna set up a tap gesture in viewDidLoad to call that function:
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
self.view.addGestureRecognizer(tap)
Here's the handleTap method. Note how I call foo without any arguments.
#objc func handleTap() {
foo()
}
As you'd expect, it prints out a 1 every time.
Enter The Dragon 🐉
Here's where it gets weird. Let's change handleTap to only post a notification. And we'll add an observer for that notification in viewDidLoad to call foo, like so.
The notification:
extension Notification.Name {
static let didSomething = Notification.Name("didSometing")
}
The new handleTap method:
#objc func handleTap() {
NotificationCenter.default.post(name: .didSomething, object: nil)
}
And our addition to viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
Now every time I tap, I get all kinds of values, here's what my output looks like:
value is 10748457248
value is 10748611248
value is 10748564928
value is 10748456000
value is 10748564256
value is 10748612688
value is 10748611824
value is 10748609616
value is 10748612496
value is 10748564592
Now if I change the notification observation to call a new method fooCaller (which is just a method that calls foo()), then I get all 1's.
So I figure it's something to do with obj-c, selectors and arguments, but thing is that my foo can be called without any arguments, like foo(), so I would reasonably expect #selector(foo) to have the same behaviour but it doesn't, and I don't know why.
In actuality, my function (which is represented here by foo) never took any arguments and was only called from notification observations. Then I later needed to call foo directly, passing something. So I gave it a default value so as not to disturb the current use of foo elsewhere, but it opened up the dark dimension instead.
There is sort of a bug, in the sense that, in my opinion, your code should not compile. And it does indeed, as you suspect, have to do with the tricky interface between Objective-C and Swift.
Let's look at the declaration of addObserver in Objective-C:
- (void)addObserver:(id)observer selector:(SEL)aSelector
name:(NSNotificationName)aName object:(id)anObject;
According to the docs, the selector must refer to a method that has
one and only one argument (an instance of NSNotification)
That's because the NSNotification can contain important information, such as its userInfo, that you might need to receive.
In other words, your foo, if it is to be the method called by the notification center to let you know of the notification, should have this signature:
#objc func foo(_ notification: Notification) {
And if it did, you'd be able to do stuff like this:
#objc func foo(_ notification: Notification) {
print(notification) // name = didSomething, object = nil, userInfo = nil
}
Great. But your foo does not have that signature; it types its parameter as Int instead. This is not identically what you have, but it might as well be:
#objc func foo(_ value: Int) {
Now, in my opinion, that should not be permitted. If you declare your foo that way, then when the time comes to use it as the selector...
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
...the compiler should complain: "No, you can't do that, foo has the wrong type as its parameter!" But it doesn't.
So the Notification object arrives and is interpreted as an Int in accordance with its memory location — and that is what you are printing.
Your console log print these numbers because of this line:
NotificationCenter.default.addObserver(self, selector: #selector(foo), name: .didSomething, object: nil)
This function is passing the Notification as a parameter which causes these logs. Modify your function to receive the notification and handle it.
#objc func foo(notification: Notification) {
print("received notification: \(notification)")
}
Try passing the foo function as parameter for selector in tapGestureRecognizer, like this:
let tap = UITapGestureRecognizer(target: self, action: #selector(foo))
view.addGestureRecognizer(tap)
You'll see a similar result it's because because here the UITapGestureRecognizer is being passed to the foo function.

Use value of superclass in instance in Swift

I am trying to write a chat application and I am using SignalR for this. I recently started to refactor the code to use a handler for the SignalR/SwiftR functions. I am creating an instance, when I call the SignalR handler from the chat viewController. Now I have a function, which is triggered inside the handler instance and from there I am trying to execute a function in the viewController. I tried to do this with an instance. But now I don't have any data in my arrayMessage because I am creating a new instance of the class. Is there a way to get the array of the normal class?
If not, what's the best way to execute this function?
This should execute the recieveMessage:
chatHub.on("CommunityMessage") { args in
if let m: AnyObject = args![0] as AnyObject!{
SignalRViewController.instance.recieveMessage(m: m)
}
}
recieveMessage function, where I don't have data in the arrayMessage:
func recieveMessage(m : AnyObject){
let message = m.object(forKey: "Message") as! String
let index = (self.arrayMessage.count - 1)
print(self.arrayMessage)
}
In your chathub-callback you can post a notification with the message attached
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ChatHubMessageReceived"), object: m)
In the ViewController you can then subscribe yourself to the message
NotificationCenter.default.addObserver(self,
selector: #selector(receiveMessage),
name: NSNotification.Name(rawValue: "ChatHubMessageReceived"),
object: nil)
You will have to adjust the method signature to one that takes a notification
func recieveMessage(notification: Notification) {
let message = notification.object
}

Observer never called

I have two functions
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserverForName("personalDataDidLoad", object: self, queue: NSOperationQueue.mainQueue()) {_ in
print("Received notification")
self.showPersonalData()
}
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
but for some reason this outputs
personal data loaded
instead of the expected
personal data loaded
Received notification
I'm probably missing something obvious, but I don't see it right now....
I also tried addObserver with selector: "showPersonalData:" but this throws an unrecognized selector exception..
The problem is with the 2nd parameter in postNotificationName and addObserverForName: object. When you add an observer, and pass a non-nil object value, this means that the observer block will run when a notification comes from that object, and from that object only.
However, when you fire the notification, you do object: nil. So your notification is ignored.
On the other hand, passing a nil value for object means, "I want to receive this notification regardless of who sends it".
So you need to make sure the object value is the same in both places: either self or nil.
Is there a reason you need to use addObserverForName(_:object:queue:usingBlock:)?
Try this instead:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, "personalDataDidLoadNotification:", name: "personalDataDidLoad" object: nil)
loadPersonalData()
}
func loadPersonalData() {
//load data
print("personal data loaded")
NSNotificationCenter.defaultCenter().postNotificationName("personalDataDidLoad", object: nil)
}
func personalDataDidLoadNotification(notification: NSNotification) {
print("notification recieved")
}
Another answer to the title question (but not this example) but will hopefully help others in the situation I have been in for the last 3 hours:
Make sure your notificationcenter observer is added inside a class that has a persisted instance. I created the observer inside a class that was called as e.g. MyClass().setupNotification inside a local method of another class.
This meant the observer was immediately deleted and didnt persist against any instance.
Schoolboy error - but hope this helps others searching for this.

Call function on app termination in Swift

How can I call a function that is within an SKScene class when my app is terminated by the user?
I need to modify a value and save it to NSUserDefauts when the app is terminated.
You can register to receive a notification when your app is about to terminate. To do this, add an observer to the default notification center by
Swift 5:
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: UIApplication.willTerminateNotification, object: nil)
Swift 3/4:
// Add this to didMoveToView in your SKScene subclass
NotificationCenter.default.addObserver(self, selector: #selector(saveData), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
Add the following method to your SKScene subclass. The method will be called before the app terminates. It must be "exposed" to Objective-C by adding #objc so the notifier can use #selector().
#objc func saveData(notification:NSNotification) {
// Save your data here
print("Saving data...")
}
In Swift 3 and 4 you have something like that:
in your viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(toDoSomething), name: NSNotification.Name.UIApplicationWillTerminate, object: nil)
and than you have that method to be called
func suspending () {
print("toDoSomething...")
}
There are a few methods in UIAppDelegate that will help you. Take a look at applicationWillTerminate(_:) and applicationWillResignActive(_:). From there you see what state your app is in and do perform the appropriate actions.