I want to change a value in an actor when the application enters the background. Normally, I'd do this:
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidEnterBackground), // error 1
name: UIApplication.didEnterBackgroundNotification, // error 2
object: nil
)
#objc private func applicationDidEnterBackground() { // error 3
foo = true
}
However, I can't do this because it gives me the errors:
Argument of '#selector' refers to instance method 'applicationDidEnterBackground()' that is not exposed to Objective-C
Main actor-isolated class property 'didEnterBackgroundNotification' can not be referenced on a non-isolated actor instance
Actor-isolated instance method 'applicationDidEnterBackground()' cannot be #objc
How can I do this within my actor? Or will I need to have a wrapper to my actor that observers didEnterBackgroundNotification and calls a method on my actor?
Related
Question Summary:
If you have a Swift class that takes a selector as an argument in its initializer, how do you manually "fire/call" that selector?
Full Question:
Consider the following attempt at making a custom timer in Swift:
let TIME_INTERVAL = 0.1
class ValueAnimator : NSObject {
private var timer = Timer()
private let maxRep: Int
private var currentRepIndex: Int = 0
private var selector: Selector
init(durationInSeconds: Int, selector: Selector) {
print("VALUEANIMATOR INIT")
self.maxRep = Int(Double(durationInSeconds) / TIME_INTERVAL)
self.selector = selector
}
func start() {
timer = Timer.scheduledTimer(timeInterval: TIME_INTERVAL, target: self, selector: (#selector(timerCallback)), userInfo: nil, repeats: true)
}
#objc func timerCallback() {
currentRepIndex += 1
perform(selector) // <-------- this line causes crash, "unrecognized selector sent to instance 0x600001740030"
print ("VA timer called!, rep: \(currentRepIndex)")
if currentRepIndex == maxRep {
timer.invalidate()
print("VA timer invalidated")
}
}
}
The usage of this "ValueAnimator" would be similar to a normal Timer/NSTimer, in that you pass a "selector" as an argument and that selector is called each time the ValueAnimator fires:
[In Parent Class]:
// { ...
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp))
valueAnimatorTest.start()
}
#objc func temp() {
print("temp VA callback works!") // this doesn't happen :(
}
I'm trying to implement the same thing and as I understand, the line:
perform(selector)
should fire the selector in the parent class, but instead I get the error: "unrecognized selector sent to instance 0x600001740030"
I'm in a bit over my head here. I have tried googling the error, but everyone seems to be talking about how to use a selector from the parent-side (how to use Timer.scheduledTimer(), etc.) but I already know how to do that successfully.
I've also tried various tweaks to the code (changing public/private, scope of variables, and different forms of the performSelector() function)... but can't figure out the proper way to make the selector fire... or the unrelated mistake I've made if there is one.
Thanks for any help.
By calling perform(selector) it's like you're calling self.perform(selector) (self is implied), and by doing so the current instance of the ValueAnimator class is the object that actually performs the selector. When that happens, it tries to call a method called temp() of the ValueAnimator class, but as it doesn't exist the app is crashing.
You can verify that if you add a temp() method in the ValueAnimator:
#objc func temp() {
print("Wrong method!!!")
}
If you run now you'll have no crash and the "Wrong Selector!!!" message will appear on the console.
The solution to your problem is to pass the object that should run the selector method along with the selector to the initialisation of the ValueAnimator object.
In the ValueAnimator class declare the following property:
private var target: AnyObject
Update the init method so it can get the target as an argument:
init(durationInSeconds: Int, selector: Selector, target: AnyObject) {
...
self.target = target
}
Also update the timerCallback():
#objc func timerCallback() {
...
_ = target.perform(selector)
...
}
Finally, when you initialise a ValueAnimator instance pass the object that the selector belongs to as well:
let valueAnimatorTest = ValueAnimator(durationInSeconds: 10, selector: #selector(self.temp), target: self)
Run again and the proper temp() method will be executed this time.
I hope it helps.
You are calling perform on the wrong object: its an instance method of NSObject, so you are trying to call perform on ValueAnimator and ValueAnimator does not respond to "temp". You must pass in both the object and the selector you want to perform, then you call perform on that object with the selector. Notice that this is exactly what Timer does: you have to pass in self as the object and the timer call the selector you specify on self.
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.
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.
I have a simple NSNotification set up on my Swift project.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "serviceAccessChanged", name:"LocationAccessChangedNotification", object: nil)
I've also tried...
NSNotificationCenter.defaultCenter().addObserver(self, selector: "serviceAccessChanged:", name:"LocationAccessChangedNotification", object: nil)
The method called looks like so.
private func serviceAccessChanged() {
println("serviceAccessChanged")
}
When the notification is made I receive the following error.
-[CoolApp.HomeViewController serviceAccessChanged]: unrecognized selector sent to instance 0x7fc91324bba0
What's wrong and how do I fix this?
Private functions are not exposed to Objective C, and this is why you get this exception. Make this method accessible, and use the serviceAccessChanged selector.
Just mark your private method with #objc
#objc private func serviceAccessChanged() {
println("serviceAccessChanged")
}
I am trying to spawn a thread in swift.
So I have this line:
.
.
.
let thread = NSThread(target: self, selector: doSomething(), object: nil)
.
.
.
doSomething is a function within the scope of the class.
That line gives this error:
"could not find an overload for init() that accepts the supplied arguments"
What am I missing here? Ho can I create a new thread in swift?
As of Xcode 7.3 and Swift 2.2, you can use the special form #selector(...) where Objective-C would use #selector(...):
let thread = NSThread(target:self, selector:#selector(doSomething), object:nil)
NSThread takes a selector as it's second parameter. You can describe Objective-C selectors as Strings in Swift like this:
let thread = NSThread(target: myObj, selector: "mySelector", object: nil)
Swift functions aren't equivalent objective-c methods though. If you have a method in a swift class, you can use it as a selector if you use the #objc attribute on the class:
#objc class myClass{
func myFunc(){
}
}
var myObj = myClass()
let thread = NSThread(target: myObj, selector: "myFunc", object: nil)
When we call a selector on a target, we should check if the target exists and it responds to the selector. Only class that extend from NSObject or it's subclasses can use respondsToSelector:
You can use a NSDictionary to store parameters if you have more than 2 parameters.
Ex:
//this code is in Objective-C but the same code should exist in Swift
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:object1, #"key1", object2, #"key2",nil];
then pass 'params' to parameter of selector: