@selector() in Swift?
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:)
vsinsertSubview(_:aboveSubview:)
). But if a function has no parameters, the only way to disambiguate it is to use anas
cast with the function's type signature (e.g.foo as () -> ()
vsfoo(_:)
). - 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
.
Selector in swift3
Selector("tap:")
should now be written as #selector(tap(gestureReconizer:))
Also, you should declare tap as func tap(_ gestureRecognizer: UITapGestureRecognizer)
as per the new Swift API Guidelines in which case your selector would then become #selector(tap(_:))
.
Swift 3 selector usage
Try with this:
outletScoresDetailsNav.leftBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.ownCatalogBackButton));
Swift 3 Method from Class not working with #selector syntax
The problem is that printMe
is a temporary, local variable:
let printMe = printThings() // local variable
button.addTarget(printMe, action: #selector(printMe.printMe(_:)), for: .touchUpInside)
// ... and then viewDidLoad ends, and `printMe` vanishes
So by the time you push the button, printMe
has vanished; there is no one to send the button message to.
If you want this to work, you need to make printMe
persist:
class ViewController : UIViewController {
let printMe = printThings() // now it's a _property_ and will persist
override func viewDidLoad(){
super.viewDidLoad()
//button setup here
button.addTarget(self.printMe, action: #selector(self.printMe.printMe(_:)), for: .touchUpInside)
//add button to subview
}
}
Add Observer and Selector in Swift 3
Remember to make the method accepting the notifications public (if its on a different controller).
And you should also add the processor tag objc
so objective-c methods can call it.
Assign Observers:
nc.addObserver(
self,
selector: #selector(received(notification:)),
name: LocationNotification.kLocationUpdated, object: nil
)
Handle notifications:
@objc public func locationUpdated(notification:Notification) {
//Do something
}
Hope this helps! :-)
Proper way to use selectors in Swift
- why can't I no longer pass simply the function name String to the action?
Using strings for selectors has been deprecated, and you should now write #selector(methodName)
instead of "methodName"
. If the methodName() method doesn't exist, you'll get a compile error – another whole class of bugs eliminated at compile time. This was not possible with strings.
- how is the proper way to implement this following the Swift Way? Using the Selector class?
You did it the right way:
button.addTarget(self, action: #selector(ClassName.methodName(_:)), forControlEvents: UIControlEvents.TouchUpInside)
- why do we need to pass the @objc keyword and how it affects the function?
In Swift the normal approach is to bind method's calls and method's bodies at compile time (like C and C++ do). Objective C does it at run time. So in Objective C you can do some things that are not possible in Swift - for example it is possible to exchange method's implementation at run time (it is called method swizzling). Cocoa was designed to work with Objective C approach and this is why you have to inform the compiler that your Swift method should be compiled in Objective-C-like style. If your class inherits NSObject it will be compiled ObjC-like style even without @objc keyword.
Using selector in Swift 3 NotificationCenter observer
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.update), name: NSNotification.Name(rawValue: "uploaded"), object: nil)
func update() {
// do what you want
}
please note that "ViewController" is the class name where your function is
iOS Swift 3 performSelector onMainThread
is not in withObject
it is in with
try this
performSelector(onMainThread: #selector(importComplete), with: nil, waitUntilDone: false)
and called the method as
func importComplete() {
//
print("Hello World")
}
output
Related Topics
Get Average Color of Uiimage in Swift
Is There a Practical Way to Compress Nsdata
How to Check If the iOS Device Is Locked/Unlocked Using Swift
How to Set Selected Segment Index in Uisegmentedcontrol
Alamofire No Such Module (Cocoapods)
How to Disable Arc for a Single File in Xcode 5
Object-C/iOS :How to Use Asynchronous to Get a Data from Url
Automating Xcode' Debug > Simulate Location Command
Continue Uploading Process in Background iOS
Combining Text and Images in 'Uitextview'
iOS Crash When Testing on Device - Debug Logs
Swrevealviewcontroller - Manually Switch to Another View