Alternate way (using Selector) to set up notification observer - swift

I have successfully setup notification observer using the code below:
func setupNotification {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "action:", name: notificationString, object: nil)
}
func action(notification: NSNotification) {
// Do something when receiving notification
}
However, I am not interested in the coding style above since there might be a chance that I could type or copy/paste wrong method name action:.
So I tried to addObserver in a different way: NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector(/* What to pass in here??? */), name: notificationString, object: nil), I don't know what to pass in selector: Selector(...).
XCode hints me: Selector(action(notification: NSNotification), but this is illegal.
In Objective C, I can easily pick up a method at this phase but I don't know how in Swift.
Have you tried this syntax? Let me know.
Thanks,

The syntax for a Selector is Selector("action:")

Not the direct answer for your question but I have an idea.
In swift instance methods are curried functions that take instance as first argument. Suppose you have a class like this.
class Foo: NSObject {
func bar(notification: NSNotification) {
// do something with notification
}
}
And somewhere in code you have an instance of Foo.
let foo = Foo()
Now you can get bar method as a variable like this
let barFunc = Foo.bar(foo)
Now imagine in the future NSNotificationCenter has an api that lets you assign functions to it instead of selectors.
NSNotificationCenter.defaultCenter().addObserverFunction(barFunc, name: "notificationName", object: nil)
Though I don't like to use NSNotificationCenter but It would be nice to see this kind of api in the future.

Related

SwiftUI - KV Observe completion from Combine does not get triggered

I am trying to build a VOIP app using lib called VailerSIPLib. As the library was built using Obj-C and heavily using NotificationCenter to to publish the changes the active states all over the place.
I currently at the CallView part of the project, I can manage to start, end, reject calls. However, I need to implement connectionStatus in the view which will give information about the call like duration, "connecting..", "disconnected", "ringing" etc.
The below code is all in CallViewModel: ObservableObject;
Variables:
var activeCall: VSLCall!
#Published var connectionStatus: String = ""
Initializer:
override init(){
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.listen(_:)), name: Notification.Name.VSLCallStateChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateInboundCallAccepted, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.buildCallView(_:)), name: Notification.Name.CallKitProviderDelegateOutboundCallStarted, object: nil)
}
Methods:
func setCall(_ call: VSLCall) {
self.activeCall = call
self.activeCall.observe(\.callStateText) { (asd, change) in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
#objc func listen(_ notification: Notification) {
if let _ = self.activeCall {
print(self.activeCall.callStateText)
}
}
#objc func buildCallView(_ notification: Notification) {
print("inbound call")
self.isOnCall = true
}
Problem:
It prints out every thing except the completionBlock in setCall(_:). listen(_:) function validates that the state of the activeCall is changing and I would want to use that directly, however it does not work correct all the time. It should be triggered when the call is answered with callState value of .confirmed but sometime it does. This how I will know that it is time start the timer.
Other point is, in the example project of the VialerSIPLib they used self.activeCall.addObserver(_:) and it works fine. The problem for that is it throws a runtime error at the method something like didObservedValueChange(_:) and logs An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)' is unused
which I could not find anything related to it.
Finally there is yellow warning at the activeCall.observe(_:) says
Result of call to 'observe(_:options:changeHandler:)'
This is telling you what the problem is. The observe(_:options:changeHandler:) method is only incompletely documented. It returns an object of type NSKeyValueObservation which represents your registration as a key-value observer. You need to save this object, because when the NSKeyValueObservation is destroyed, it unregisters you. So you need to add a property to CallViewModel to store it:
class CallViewModel: ObservableObject {
private var callStateTextObservation: NSKeyValueObservation?
...
And then you need to store the observation:
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextObservation = activeCall.observe(\.callStateText) { _, change in
print("observing")
print("\(String(describing: change.oldValue)) to \(String(describing: change.newValue)) for \(call.callId)")
}
}
You could choose to use the Combine API for KVO instead, although it is even less documented than the Foundation API. You get a Publisher whose output is each new value of the observed property. It works like this:
class CallViewModel: ObservableObject {
private var callStateTextTicket: AnyCancellable?
...
func setCall(_ call: VSLCall) {
activeCall = call
callStateTextTicket = self.activeCall.publisher(for: \.callStateText, options: [])
.sink { print("callId: \(call.callId), callStateText: \($0)") }
}
There's no specific reason to use the Combine API in your sample code, but in general a Publisher is more flexible than an NSKeyValueObservation because Combine provides so many ways to operate on Publishers.
Your error with addObserver(_:forKeyPath:options:context:) happens because that is a much older API. It was added to NSObject long before Swift was invented. In fact, it was added before Objective-C even had blocks (closures). When you use that method, all notifications are sent to the observeValue(forKeyPath:of:change:context:) method of the observer. If you don't implement the observeValue method, the default implementation in NSObject receives the notification and raises an exception.

Ambiguous reference when using selectors

Ready to use example to test in Playgrounds:
import Foundation
class Demo {
let selector = #selector(someFunc(_:))
func someFunc(_ word: String) {
// Do something
}
func someFunc(_ number: Int) {
// Do something
}
}
I'm getting an Ambiguous use of 'someFunc' error.
I have tried this answer and getting something like:
let selector = #selector(someFunc(_:) as (String) -> Void)
But still getting the same result.
Any hints on how to solve this?
Short answer: it can't be done.
Long explanation: when you want to use a #selector, the methods need to be exposed to Objective-C, so your code becomes:
#objc func someFunc(_ word: String) {
// Do something
}
#objc func someFunc(_ number: Int) {
// Do something
}
Thanks to the #objc annotation, they are now exposed to Objective-C and an compatible thunk is generated. Objective-C can use it to call the Swift methods.
In Objective-C we don't call a method directly but instead we try to send a message using objc_msgSend: the compiler is not able to understand that those method are different, since the generated signature is the same, so it won't compile. You will face the error:
Method 'someFunc' with Objective-C selector 'someFunc:' conflicts with previous declaration with the same Objective-C selector.
The only way to fix it is to have different signatures, changing one or both of them.
The selector is obviously ambiguous, neither methods have external parameter names. Remove the empty external parameters to solve this:
#objc func someFunc(word: String) {
// Do something
}
#objc func someFunc(number: Int) {
// Do something
}
After that, you should specify a selector with the parameter name:
#selector(someFunc(word:))
or
#selector(someFunc(number:))

Syntax "Selector" Swift 2.2

I'm trying to fix my NSNotificationCenter and its not working
the message :
'Use of string literal for Objective-C selectors is deprecated; use '#selector' instead'.
the line :
NSNotificationCenter.defaultCenter().addObserver(self, Selector :#selector(GameViewController.goBack)(GameViewController.goBack), object: nil)
self.dismissViewControllerAnimated(true, completion: {
});
}
#Eendje's answer is incorrect by its first comment.
I think it is better answer.
NSNotificationCenter.defaultCenter().addObserver(self, #selector(self.goBack), name: "your notification name", object: nil)
If some actions has target, it should be presented like #selector(target.method) or #selector(target.method(_:))
Here is another example
UIGestureRecognizer(target: target action:#selector(target.handleGesture(_:))
The code you pasted doesn't make any sense:
Selector :#selector(GameViewController.goBack)(GameViewController.goBack) // ???
It should be:
NSNotificationCenter.defaultCenter().addObserver(self, #selector(goBack), name: "your notification name", object: nil)
You have to look at this: https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md
The #selector proposal was made in conjunction with another proposal, specifying swift functions by their argument labels. So if I have a struct:
struct Thing
func doThis(this: Int, withOtherThing otherThing: Int) {
}
}
I would reference that function like:
let thing = Thing()
thing.doThis(_:withOtherThing:)
Remember here I'm referencing the function itself, not calling it.
You would use that with #selector:
#selector(self.doThis(_:withOtherThing:)
Function with no arguments:
#selector(self.myFunction)
Function with one implicit argument:
#selector(self.myOtherFunction(_:))
Yes, in Swift 2.2 the string literals for selectors is deprecated, and instead there this new operator #selector that you need to be using.
Refer this proposal of #selector with good examples here:
https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md
Add #objc to your selector method:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "YOUR_SELECTOR_METHOD:", name: "your notification name", object: nil)
#objc func YOUR_SELECTOR_METHOD(notification: NSNotification) {
//your code
}

How do I resolve "ambiguous use of" compile error with Swift #selector syntax?

[NOTE This question was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
Let's say I have these two methods in my class:
#objc func test() {}
#objc func test(_ sender:AnyObject?) {}
Now I want to use Swift 2.2's new #selector syntax to make a selector corresponding to the first of these methods, func test(). How do I do it? When I try this:
let selector = #selector(test) // error
... I get an error, "Ambiguous use of test()." But if I say this:
let selector = #selector(test(_:)) // ok, but...
... the error goes away, but I'm now referring to the wrong method, the one with a parameter. I want to refer to the one without any parameter. How do I do it?
[Note: the example is not artificial. NSObject has both Objective-C copy and copy: instance methods, Swift copy() and copy(sender:AnyObject?); so the problem can easily arise in real life.]
[NOTE This answer was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]
You can work around this problem by casting your function reference to the correct method signature:
let selector = #selector(test as () -> Void)
(However, in my opinion, you should not have to do this. I regard this situation as a bug, revealing that Swift's syntax for referring to functions is inadequate. I filed a bug report, but to no avail.)
Just to summarize the new #selector syntax:
The purpose of this syntax is to prevent the all-too-common runtime crashes (typically "unrecognized selector") that can arise when supplying a selector as a literal string. #selector() takes a function reference, and the compiler will check that the function really exists and will resolve the reference to an Objective-C selector for you. Thus, you can't readily make any mistake.
(EDIT: Okay, yes you can. You can be a complete lunkhead and set the target to an instance that doesn't implement the action message specified by the #selector. The compiler won't stop you and you'll crash just like in the good old days. Sigh...)
A function reference can appear in any of three forms:
The bare name of the function. This is sufficient if the function is unambiguous. Thus, for example:
#objc func test(_ sender:AnyObject?) {}
func makeSelector() {
let selector = #selector(test)
}
There is only one test method, so this #selector refers to it even though it takes a parameter and the #selector doesn't mention the parameter. The resolved Objective-C selector, behind the scenes, will still correctly be "test:" (with the colon, indicating a parameter).
The name of the function along with the rest of its signature. For example:
func test() {}
func test(_ sender:AnyObject?) {}
func makeSelector() {
let selector = #selector(test(_:))
}
We have two test methods, so we need to differentiate; the notation test(_:) resolves to the second one, the one with a parameter.
The name of the function with or without the rest of its signature, plus a cast to show the types of the parameters. Thus:
#objc func test(_ integer:Int) {}
#nonobjc func test(_ string:String) {}
func makeSelector() {
let selector1 = #selector(test as (Int) -> Void)
// or:
let selector2 = #selector(test(_:) as (Int) -> Void)
}
Here, we have overloaded test(_:). The overloading cannot be exposed to Objective-C, because Objective-C doesn't permit overloading, so only one of them is exposed, and we can form a selector only for the one that is exposed, because selectors are an Objective-C feature. But we must still disambiguate as far as Swift is concerned, and the cast does that.
(It is this linguistic feature that is used — misused, in my opinion — as the basis of the answer above.)
Also, you might have to help Swift resolve the function reference by telling it what class the function is in:
If the class is the same as this one, or up the superclass chain from this one, no further resolution is usually needed (as shown in the examples above); optionally, you can say self, with dot-notation (e.g. #selector(self.test), and in some situations you might have to do so.
Otherwise, you use either a reference to an instance for which the method is implemented, with dot-notation, as in this real-life example (self.mp is an MPMusicPlayerController):
let pause = UIBarButtonItem(barButtonSystemItem: .pause,
target: self.mp, action: #selector(self.mp.pause))
...or you can use the name of the class, with dot-notation:
class ClassA : NSObject {
#objc func test() {}
}
class ClassB {
func makeSelector() {
let selector = #selector(ClassA.test)
}
}
(This seems a curious notation, because it looks like you're saying test is a class method rather than an instance method, but it will be correctly resolved to a selector nonetheless, which is all that matters.)
I want to add a missing disambiguation: accessing an instance method from outside the class.
class Foo {
#objc func test() {}
#objc func test(_ sender: AnyObject?) {}
}
From the class' perspective the full signature of the test() method is (Foo) -> () -> Void, which you will need to specify in order to get the Selector.
#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))
Alternatively you can refer to an instance's Selectors as shown in the original answer.
let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))
In my case (Xcode 11.3.1) the error was only when using lldb while debugging. When running it works properly.

Swift: Creating a function and calling it multiple times?

I am very new to Swift and I can't find an answer to my qustion.
I am working on an app that will do a lot of the same functions but on separate button pushes. I could reduce my code and time updating greatly if I am able to write a function or action and just call it by name on a button push.
Does Swift have a way to do something like this:
Func doMath {
var answer = 1+1
var answer2 = 2+2
}
Func buttonPush {
call doMath
}
What you need is an IBAction. Using the Assistant view hold the control key and drag it into your code. Then change the connection type to Action and give it a useful name.
From inside your IBAction you can do any maths or call any maths function you need. Using functions to minimise code duplication is a good idea.
Here's an example of an Action that starts a timer.
#IBAction func play(sender: AnyObject) {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}
I sugguest you do some examples that use IBAction.
I liked the Ray Wenderlich ones myself.
Here is some code for creating a function that takes two integer parameters. It's taken from the Apple developer documentation.
apple docs hopefully that's what you need.
func halfOpenRangeLength(start: Int, end: Int) -> Int {
return end - start
}