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.
I want to schedule a function call in the future. I'm using Swift.
I want to callback a method that is private and returns a Promise (from PromiseKit)
All the example I've seen use
NSTimer.scheduledTimerWithTimeInterval(ti: NSTimeInterval, target: AnyObject, selector: Selector, userInfo: AnyObject?, repeats: Bool)
Fine. I've tried
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "connect", userInfo: nil, repeats: false)
That fails with No method declared with Objective-C selector 'connect'.
What is Objective-C doing here??
Anyway it's suggested that I add #objc in front of my method connect. Fine. Well I can't because apparently Method cannot be marked #objc because its result type cannot be represented in Objective-C
If I wanted to use objective-C I'd not be writing Swift...
There's another scheduledTimerWithTimeInterval that is
NSTimer.scheduledTimerWithTimeInterval(ti: NSTimeInterval, invocation: NSInvocation, repeats: Bool)
But from what I've read NSInvocation is not a Swift thing...
So I ended up creating a wrapper that does nothing other than calling connect and returning Void that Objective C can understand. It works but it feels very stupid. Is there a better Swift way?
Bonus: why can javascript do that as simply as setTimeout(this.connect, 1) and Swift has no built in way I can find?
Beginning with iOS 10 and Swift 3, it is possible to use (NS)Timer with a block closure and thus avoid Objective-C selector invocation when the timer fires:
if #available(iOS 10.0, *) {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { (Timer) in
self.connect() // per the OP's example
})
}
In addition to avoiding the #objc decorator, using this technique allows you to invoke methods that include non-Objective-C-compatible parameter types such as enums and optionals.
Re: setTimeout(this.connect, 1) from the Javascript, if you don't need to cancel it, a more direct analogy in Swift 3 might be:
DispatchQueue.Main.asyncAfter(deadline: .now() + 1.0, execute { self.connect() })
Which is pretty darn close given that you actually have a choice of which thread to run on ;-)
What is Objective-C doing here??
The reason for the need of Objective-C is that it dynamically binds the "call" (it is no call in Objective-C) at runtime, while Swift cannot do this. Swift is not able to have code in the timer class that "calls" a "function" not known at the compile time of NSTimer.
BTW: NSTimer uses NSInvocation (or something similar unswifty technology) to do the the "call". Therefore the usage of NSTimer is not more swifty, but the need for late binding is more obfuscated to make Swift developers feel better.
If I wanted to use objective-C I'd not be writing Swift...
Even your code is completely written in Swift, it takes masses of benefits from Objective-C's late binding. Many of the core techniques of Cocoa are impossible to write in Swift, including responder chain, undo manager, Core Data, animations … (On the other hand you are able to define a 💩 operator, what is a big progress in software engineering and describes the whole story.)
There are 2 ways you can invoke a timer:
// Specify the selector name as String
// Swift will give you a warning but this is handy if you have the function's name stored
// as a variable or need some dynamism
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("timerFired"), userInfo: nil, repeats: false)
// The recommended way since Swift 2.2 / 2.3 (can't remeber exactly)
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(MyClass.timerFired), userInfo: nil, repeats: false)
And both assumes you have a function like this:
func timerFired() {
print("Hello world")
}
Keep in mind Swift 2.2 / Xcode 7.3 has a new way to use selector: Selector("funcName") was changed to #selector(ClassName.funcName)
You should use #selector:
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(YourClass.connect), userInfo: nil, repeats: false)
OR Selector("connect"), however keep in mind that you'll receive a warning:
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("connect"), userInfo: nil, repeats: false)
Also look at this in order to know how to use Selector().
More info Referencing the Objective-C selector of a method
here.
Callback for NSTimer needs to be accessible to Objective-C, even if you're using swift. In my case it meant two things:
decorate the private method with #objc since by default private methods are not accessible to Objective-C from Swift classes.
wrap my method in a method that calls it and returns nothing, so that the callback returns void. This was necessary since Objective-C didn't know how to handle the Promise type.
So at the end it looked like:
import PromiseKit
class bla : NSObject {
private func myCallback() -> Promise<Void> {
return Promise { fullfill, reject in
// ...
}
}
#objc private func myCallbackWrap -> Void {
myCallback()
}
func startTimeout() -> Void {
NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: #selector(myCallbackWrap), userInfo: nil, repeats: false)
}
}
I'm trying to create an NSTimer in Swift but I'm having some trouble.
NSTimer(timeInterval: 1, target: self, selector: test(), userInfo: nil, repeats: true)
test() is a function in the same class.
I get an error in the editor:
Could not find an overload for 'init' that accepts the supplied
arguments
When I change selector: test() to selector: nil the error disappears.
I've tried:
selector: test()
selector: test
selector: Selector(test())
But nothing works and I can't find a solution in the references.
Swift itself doesn't use selectors — several design patterns that in Objective-C make use of selectors work differently in Swift. (For example, use optional chaining on protocol types or is/as tests instead of respondsToSelector:, and use closures wherever you can instead of performSelector: for better type/memory safety.)
But there are still a number of important ObjC-based APIs that use selectors, including timers and the target/action pattern. Swift provides the Selector type for working with these. (Swift automatically uses this in place of ObjC's SEL type.)
In Swift 2.2 (Xcode 7.3) and later (including Swift 3 / Xcode 8 and Swift 4 / Xcode 9):
You can construct a Selector from a Swift function type using the #selector expression.
let timer = Timer(timeInterval: 1, target: object,
selector: #selector(MyClass.test),
userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
with: button, with: otherButton)
The great thing about this approach? A function reference is checked by the Swift compiler, so you can use the #selector expression only with class/method pairs that actually exist and are eligible for use as selectors (see "Selector availability" below). You're also free to make your function reference only as specific as you need, as per the Swift 2.2+ rules for function-type naming.
(This is actually an improvement over ObjC's #selector() directive, because the compiler's -Wundeclared-selector check verifies only that the named selector exists. The Swift function reference you pass to #selector checks existence, membership in a class, and type signature.)
There are a couple of extra caveats for the function references you pass to the #selector expression:
Multiple functions with the same base name can be differentiated by their parameter labels using the aforementioned syntax for function references (e.g. insertSubview(_:at:) vs insertSubview(_:aboveSubview:)). But if a function has no parameters, the only way to disambiguate it is to use an as cast with the function's type signature (e.g. foo as () -> () vs foo(_:)).
There's a special syntax for property getter/setter pairs in Swift 3.0+. For example, given a var foo: Int, you can use #selector(getter: MyClass.foo) or #selector(setter: MyClass.foo).
General notes:
Cases where #selector doesn't work, and naming: Sometimes you don't have a function reference to make a selector with (for example, with methods dynamically registered in the ObjC runtime). In that case, you can construct a Selector from a string: e.g. Selector("dynamicMethod:") — though you lose the compiler's validity checking. When you do that, you need to follow ObjC naming rules, including colons (:) for each parameter.
Selector availability: The method referenced by the selector must be exposed to the ObjC runtime. In Swift 4, every method exposed to ObjC must have its declaration prefaced with the #objc attribute. (In previous versions you got that attribute for free in some cases, but now you have to explicitly declare it.)
Remember that private symbols aren't exposed to the runtime, too — your method needs to have at least internal visibility.
Key paths: These are related to but not quite the same as selectors. There's a special syntax for these in Swift 3, too: e.g. chris.valueForKeyPath(#keyPath(Person.friends.firstName)). See SE-0062 for details. And even more KeyPath stuff in Swift 4, so make sure you're using the right KeyPath-based API instead of selectors if appropriate.
You can read more about selectors under Interacting with Objective-C APIs in Using Swift with Cocoa and Objective-C.
Note: Before Swift 2.2, Selector conformed to StringLiteralConvertible, so you might find old code where bare strings are passed to APIs that take selectors. You'll want to run "Convert to Current Swift Syntax" in Xcode to get those using #selector.
Here's a quick example on how to use the Selector class on Swift:
override func viewDidLoad() {
super.viewDidLoad()
var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))
self.navigationItem.rightBarButtonItem = rightButton
}
func method() {
// Something cool here
}
Note that if the method passed as a string doesn't work, it will fail at runtime, not compile time, and crash your app. Be careful
Also, if your (Swift) class does not descend from an Objective-C class, then you must have a colon at the end of the target method name string and you must use the #objc property with your target method e.g.
var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))
#objc func method() {
// Something cool here
}
otherwise you will get a "Unrecognised Selector" error at runtime.
Swift 2.2+ and Swift 3 Update
Use the new #selector expression, which eliminates the need to use string literals making usage less error-prone. For reference:
Selector("keyboardDidHide:")
becomes
#selector(keyboardDidHide(_:))
See also: Swift Evolution Proposal
Note (Swift 4.0):
If using #selectoryou would need to mark the function as #objc
Example:
#objc func something(_ sender: UIButton)
Swift 4.0
you create the Selector like below.
1.add the event to a button like:
button.addTarget(self, action: #selector(clickedButton(sender:)), for: UIControlEvents.touchUpInside)
and the function will be like below:
#objc func clickedButton(sender: AnyObject) {
}
For future readers, I found that I experienced a problem and was getting an unrecognised selector sent to instance error that was caused by marking the target func as private.
The func MUST be publicly visible to be called by an object with a reference to a selector.
Just in case somebody else have the same problem I had with NSTimer where none of the other answers fixed the issue, is really important to mention that, if you are using a class that do not inherits from NSObject either directly or deep in the hierarchy(e.g. manually created swift files), none of the other answers will work even when is specified as follows:
let timer = NSTimer(timeInterval: 1, target: self, selector: "test",
userInfo: nil, repeats: false)
func test () {}
Without changing anything else other than just making the class inherit from NSObject I stopped getting the "Unrecognized selector" Error and got my logic working as expected.
If you want to pass a parameter to the function from the NSTimer then here is your solution:
var somethingToPass = "It worked"
let timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "tester:", userInfo: somethingToPass, repeats: false)
func tester(timer: NSTimer)
{
let theStringToPrint = timer.userInfo as String
println(theStringToPrint)
}
Include the colon in the selector text (tester:), and your parameter(s) go in userInfo.
Your function should take NSTimer as a parameter. Then just extract userInfo to get the parameter that passed.
Selectors are an internal representation of a method name in Objective-C. In Objective-C "#selector(methodName)" would convert a source-code method into a data type of SEL. Since you can't use the #selector syntax in Swift (rickster is on point there), you have to manually specify the method name as a String object directly, or by passing a String object to the Selector type. Here is an example:
var rightBarButton = UIBarButtonItem(
title: "Logout",
style: UIBarButtonItemStyle.Plain,
target: self,
action:"logout"
)
or
var rightBarButton = UIBarButtonItem(
title: "Logout",
style: UIBarButtonItemStyle.Plain,
target: self,
action:Selector("logout")
)
Swift 4.1
With sample of tap gesture
let gestureRecognizer = UITapGestureRecognizer()
self.view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.addTarget(self, action: #selector(self.dismiss(completion:)))
// Use destination 'Class Name' directly, if you selector (function) is not in same class.
//gestureRecognizer.addTarget(self, action: #selector(DestinationClass.dismiss(completion:)))
#objc func dismiss(completion: (() -> Void)?) {
self.dismiss(animated: true, completion: completion)
}
See Apple's document for more details about: Selector Expression
// for swift 2.2
// version 1
buttton.addTarget(self, action: #selector(ViewController.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(ViewController.tappedButton2(_:)), forControlEvents: .TouchUpInside)
// version 2
buttton.addTarget(self, action: #selector(self.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(self.tappedButton2(_:)), forControlEvents: .TouchUpInside)
// version 3
buttton.addTarget(self, action: #selector(tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(tappedButton2(_:)), forControlEvents: .TouchUpInside)
func tappedButton() {
print("tapped")
}
func tappedButton2(sender: UIButton) {
print("tapped 2")
}
// swift 3.x
button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)
func tappedButton(_ sender: UIButton) {
// tapped
}
button.addTarget(self, action: #selector(tappedButton(_:_:)), for: .touchUpInside)
func tappedButton(_ sender: UIButton, _ event: UIEvent) {
// tapped
}
Objective-C Selector
Selector identifies a method.
//Compile time
SEL selector = #selector(foo);
//Runtime
SEL selector = NSSelectorFromString(#"foo");
For example
[object sayHello:#"Hello World"];
//sayHello: is a selector
selector is a word from Objective-C world and you are able to use it from Swift to have a possibility to call Objective-C from Swift It allows you to execute some code at runtime
Before Swift 2.2 the syntax is:
Selector("foo:")
Since a function name is passed into Selector as a String parameter("foo") it is not possible to check a name in compile time. As a result you can get a runtime error:
unrecognized selector sent to instance
After Swift 2.2+ the syntax is:
#selector(foo(_:))
Xcode's autocomplete help you to call a right method
Create Refresh control using Selector method.
var refreshCntrl : UIRefreshControl!
refreshCntrl = UIRefreshControl()
refreshCntrl.tintColor = UIColor.whiteColor()
refreshCntrl.attributedTitle = NSAttributedString(string: "Please Wait...")
refreshCntrl.addTarget(self, action:"refreshControlValueChanged", forControlEvents: UIControlEvents.ValueChanged)
atableView.addSubview(refreshCntrl)
//Refresh Control Method
func refreshControlValueChanged(){
atableView.reloadData()
refreshCntrl.endRefreshing()
}
Since Swift 3.0 is published, it is even a little bit more subtle to declare a targetAction appropriate
class MyCustomView : UIView {
func addTapGestureRecognizer() {
// the "_" is important
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyCustomView.handleTapGesture(_:)))
tapGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(tapGestureRecognizer)
}
// since Swift 3.0 this "_" in the method implementation is very important to
// let the selector understand the targetAction
func handleTapGesture(_ tapGesture : UITapGestureRecognizer) {
if tapGesture.state == .ended {
print("TapGesture detected")
}
}
}
When using performSelector()
/addtarget()/NStimer.scheduledTimerWithInterval() methods your method (matching the selector) should be marked as
#objc
For Swift 2.0:
{
//...
self.performSelector(“performMethod”, withObject: nil , afterDelay: 0.5)
//...
//...
btnHome.addTarget(self, action: “buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
//...
//...
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector : “timerMethod”, userInfo: nil, repeats: false)
//...
}
#objc private func performMethod() {
…
}
#objc private func buttonPressed(sender:UIButton){
….
}
#objc private func timerMethod () {
….
}
For Swift 2.2,
you need to write '#selector()' instead of string and selector name so the possibilities of spelling error and crash due to that will not be there anymore. Below is example
self.performSelector(#selector(MyClass.performMethod), withObject: nil , afterDelay: 0.5)
It may be useful to note where you setup the control that triggers the action matters.
For example, I have found that when setting up a UIBarButtonItem, I had to create the button within viewDidLoad or else I would get an unrecognized selector exception.
override func viewDidLoad() {
super.viewDidLoad()
// add button
let addButton = UIBarButtonItem(image: UIImage(named: "746-plus-circle.png"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("addAction:"))
self.navigationItem.rightBarButtonItem = addButton
}
func addAction(send: AnyObject?) {
NSLog("addAction")
}
you create the Selector like below.
1.
UIBarButtonItem(
title: "Some Title",
style: UIBarButtonItemStyle.Done,
target: self,
action: "flatButtonPressed"
)
2.
flatButton.addTarget(self, action: "flatButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
Take note that the #selector syntax is gone and replaced with a simple String naming the method to call. There’s one area where we can all agree the verbosity got in the way. Of course, if we declared that there is a target method called flatButtonPressed: we better write one:
func flatButtonPressed(sender: AnyObject) {
NSLog("flatButtonPressed")
}
set the timer:
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("flatButtonPressed"),
userInfo: userInfo,
repeats: true)
let mainLoop = NSRunLoop.mainRunLoop() //1
mainLoop.addTimer(timer, forMode: NSDefaultRunLoopMode) //2 this two line is optinal
In order to be complete, here’s the flatButtonPressed
func flatButtonPressed(timer: NSTimer) {
}
I found many of these answers to be helpful but it wasn't clear how to do this with something that wasn't a button. I was adding a gesture recognizer to a UILabel in swift and struggled so here's what I found worked for me after reading everything above:
let tapRecognizer = UITapGestureRecognizer(
target: self,
action: "labelTapped:")
Where the "Selector" was declared as:
func labelTapped(sender: UILabel) { }
Note that it is public and that I am not using the Selector() syntax but it is possible to do this as well.
let tapRecognizer = UITapGestureRecognizer(
target: self,
action: Selector("labelTapped:"))
Using #selector will check your code at compile time to make sure the method you want to call actually exists. Even better, if the method doesn’t exist, you’ll get a compile error: Xcode will refuse to build your app, thus banishing to oblivion another possible source of bugs.
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem =
UIBarButtonItem(barButtonSystemItem: .Add, target: self,
action: #selector(addNewFireflyRefernce))
}
func addNewFireflyReference() {
gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
}
As many have stated selectors are an objective c way of dynamically calling methods that has been carried over to Swift, it some case we are still stuck with it, like UIKit, probable because they where working on SwiftUI to replace it but some api have more swift like version like Swift Timer, for example you can use
class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer
Instead, you can then call it like
Timer.scheduledTimer(withTimeInterval: 1,
repeats: true ) {
... your test code here
}
or
Timer.scheduledTimer(withTimeInterval: 1,
repeats: true,
block: test)
where the method test takes a Timer argument, or if you want test to take an named argument
Timer.scheduledTimer(withTimeInterval: 1,
repeats: true,
block: test(timer:))
you should also be using Timer not NSTimer as NSTimer is the old objective-c name
Change as a simple string naming in the method calling for selector syntax
var timer1 : NSTimer? = nil
timer1= NSTimer(timeInterval: 0.1, target: self, selector: Selector("test"), userInfo: nil, repeats: true)
After that, type func test().
For Swift 3
//Sample code to create timer
Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)
WHERE
timeInterval:- Interval in which timer should fire like 1s, 10s, 100s etc. [Its value is in secs]
target:- function which pointed to class. So here I am pointing to current class.
selector:- function that will execute when timer fires.
func updateTimer(){
//Implemetation
}
repeats:- true/false specifies that timer should call again n again.
Selector in Swift 4:
button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
For swift 3
let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.test), userInfo: nil, repeats: true)
Function declaration In same class:
#objc func test()
{
// my function
}