why is performSelector not present in swift - ios

apparently the following is not available in swift anymore:
[self performSelector:#selector(onFlip) withObject:nil afterDelay:0.3];
why is this the case if the following is still present:
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: singleTapSelector, object: nil)
doesn't nsobject.cancel work with performSelector? why have the cancel functionality but not the perform functionality?

Use
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: "onFlip", userInfo: nil, repeats: false)
instead. No idea why the other one is not available.

We can easily unlock these methods in Swift 1.x! Yay!
These methods from the perfromSelector family actually exist in Swift but they were artificially hidden (by design, I think) prior Swift 2.0. They can be unveiled using swizzling and some Objective-C runtime hacks right in Swift.
Just add this extension file NSObject+PerformSelector.swift and prefix all calls with 🚀 symbol.
In your case the code:
[self performSelector:#selector(onFlip) withObject:nil afterDelay:0.3];
can be rewritten in Swift like this:
self.🚀performSelector("onFlip", withObject: nil, afterDelay: 0.3)
With using this 🚀magical category (class extension):
NSObject+PerformSelector.swift
import Foundation
private var dispatchOnceToken: dispatch_once_t = 0
private var selectors: [Selector] = [
"performSelector:",
"performSelector:withObject:",
"performSelector:withObject:withObject:",
"performSelector:withObject:afterDelay:inModes:",
"performSelector:withObject:afterDelay:",
]
private func swizzle() {
dispatch_once(&dispatchOnceToken) {
for selector: Selector in selectors {
let 🚀selector = Selector("🚀\(selector)")
let method = class_getInstanceMethod(NSObject.self, selector)
class_replaceMethod(
NSObject.self,
🚀selector,
method_getImplementation(method),
method_getTypeEncoding(method)
)
}
}
}
extension NSObject {
func 🚀performSelector(selector: Selector) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object)
}
func 🚀performSelector(selector: Selector, withObject object1: AnyObject?, withObject object2: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object1, withObject: object2)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval, inModes modes: [AnyObject?]?) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay, inModes: modes)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay)
}
}

func performFlipOnDelay() {
let delayInSeconds = 1.0
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
onFlip();
}
}
Another round about answer would be the above using GCD to add a delay time for an action to be performed

Taking the answer from Thomas Kilian a step further, primarily to enhance readability on calling clients, I've defined a swift utility, TimerUtil.swift, defining a facade on NSTimer for this particular usecase:
import Foundation
/**
* Convenience function to make a single timer scheduling more readable.
*/
public func invoke( selector:Selector, on target:AnyObject, afterDelay delay:NSTimeInterval ) {
NSTimer.scheduledTimerWithTimeInterval( delay, target: target, selector: selector, userInfo: nil, repeats: false )
}
Then your code can simply call:
invoke( "onFlip", on:self, afterDelay:0.3 )
which more or less resembles performSelector:withObject:afterDelay

In Swift 4
use This to Handle These Types of Warning!
perform(#selector(yourMethodName), with: nil, afterDelay: 0, inModes: [])

Related

Swift - Timer not able to access updated instance property

Im my react native project's native module I need to send some data periodically from Objective-C to Swift so I am using NSNotificationCenter. I receive the data successfully in my Swift class, inside the function attached to the observer, and I store it in a property.
If I access this property from any instance method call I can see that the value has updated.
However if I access the same property in the selector function attached to the Timer it appears as if the value has not been updated and I cannot figure out why? It seems as if the timer selector function does not have access to anything except the initial value of the property - I have also tried passing the property as part of userInfo to the Timer but the issue is the same.
[[NSNotificationCenter defaultCenter] postNotificationName:#"stateDidUpdate" object:nil userInfo:state];
class StateController {
var state: Dictionary<String, Any> = Dictionary()
var timer: Timer = Timer()
func subscribeToNotifications() {
NotificationCenter.default.addObserver(
self, selector: #selector(receivedStateUpdate),
name: NSNotification.Name.init(rawValue: "stateDidUpdate"), object: nil)
}
#objc func receivedStateUpdate(notification: NSNotification) {
if let state = notification.userInfo {
self.state = (state as? Dictionary<String, Any>)!
print("\(self.state)") // I can see that self.state has been updated here
}
}
func runTimer() {
self.timer = Timer(timeInterval: 0.1, target: self, selector: #selector(accessState(timer:)), userInfo: nil, repeats: true)
self.timer.fire()
RunLoop.current.add(self.timer, forMode: RunLoop.Mode.default)
RunLoop.current.run(until: Date(timeIntervalSinceNow: 2))
}
#objc func accessState(timer: Timer) {
print("\(self.state)") // state is an empty Dictionary when accessed here
}
func printState() {
"\(self.state)" // value printed is the updated value received from the notification
}
}
I figured out that multiple instances of my Swift class being created was causing the issue. I assumed that React Native would create a singleton when calling a native module but it appears multiple instances are created as I could see how many times the init method was called. Switching to a Singleton pattern resolved the issue for me following this excellent video and this excellent gist on how to create a singleton in a react native project
class StateController {
static let shared = StateController()
private override init() {
}
}

How to call a block of code in #selector? [duplicate]

I want to make a selector argument of my method refer to a closure property, both of them exist in the same scope. For example,
func backgroundChange() {
self.view.backgroundColor = UIColor.blackColor()
self.view.alpha = 0.55
let backToOriginalBackground = {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}
However, this shows an error: Argument of #selector cannot refer to a property.
Of course I can define a new, separate method and move the implementation of the closure to it, but I want to keep it frugal for such a small implementation.
Is it possible to set a closure to #selector argument?
Not directly, but some workarounds are possible. Take a look at the following example.
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: #escaping () -> ()) {
_action = action
super.init()
}
#objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
I tried this for UIBarButtonItem at least:
private var actionKey: Void?
extension UIBarButtonItem {
private var _action: () -> () {
get {
return objc_getAssociatedObject(self, &actionKey) as! () -> ()
}
set {
objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
convenience init(title: String?, style: UIBarButtonItemStyle, action: #escaping () -> ()) {
self.init(title: title, style: style, target: nil, action: #selector(pressed))
self.target = self
self._action = action
}
#objc private func pressed(sender: UIBarButtonItem) {
_action()
}
}
Then you can do this:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
As #gnasher729 notes, this is not possible because selectors are just names of methods, not methods themselves. In the general case, I'd use dispatch_after here, but in this particular case, the better tool IMO is UIView.animateWithDuration, because it's exactly what that function is for, and it's very easy to tweak the transition:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
It is now possible. I've created a gist for block-based selectors in Swift 4.
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
Usage:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)`
You can use ActionClosurable which support UIControl, UIButton, UIRefreshControl, UIGestureRecognizer and UIBarButtonItem.
https://github.com/takasek/ActionClosurable
Bellow show example of UIBarButtonItem
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
#werediver's answer is excellent. Here's an update that allows you to call it as a function.
import Foundation
public extension Selector {
/// Wraps a closure in a `Selector`.
/// - Note: Callable as a function.
final class Perform: NSObject {
public init(_ perform: #escaping () -> Void) {
self.perform = perform
super.init()
}
private let perform: () -> Void
}
}
//MARK: public
public extension Selector.Perform {
#objc func callAsFunction() { perform() }
var selector: Selector { #selector(callAsFunction) }
}
You need to manage strong references to Selector.Performs. One way to do that is to subclass UIKit classes that were designed to work with target-action:
/// A `UITapGestureRecognizer` that wraps a closure.
public final class TapGestureRecognizer: UITapGestureRecognizer {
public init(_ perform: #escaping () -> Void) {
self.perform = .init(perform)
super.init(target: self.perform, action: self.perform.selector)
}
public let perform: Selector.Perform
}
let tapRecognizer = TapGestureRecognizer { print("🍔🐈") }
tapRecognizer.perform() // "🍔🐈"
No, #selector refers to an Objective-C method.
You can do something much better though: Add an extension to NSTimer that lets you create a scheduled timer not with a target and selector, but with a closure.
If you change the scope of block to a class scope rather than function and hold a reference to closure there.
You could invoke that closure with a function. in the class. So that way you can invoke that closure as a selector.
Something like this:
class Test: NSObject {
let backToOriginalBackground = {
}
func backgroundChange() {
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
}
func test() {
self.backToOriginalBackground()
}
}
My solution was to create a class block variable like:
let completionBlock: () -> () = nil
Create a method which calls this completionBlock:
func completed(){
self.completionBlock!()
}
And inside where I want to put my selector like a block I did:
func myFunc(){
self.completionBlock = {//what I want to be done}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
So my answer to having a selector be assigned to a closure in a swift like manner is similar to some of the answers already, but I thought I would share a real life example of how I did it within a UIViewController extension.
fileprivate class BarButtonItem: UIBarButtonItem {
var actionCallback: ( () -> Void )?
func buttonAction() {
actionCallback?()
}
}
fileprivate extension Selector {
static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
}
extension UIViewController {
func createBarButtonItem(title: String, action: #escaping () -> Void ) -> UIBarButtonItem {
let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
button.actionCallback = action
button.action = .onBarButtonAction
return button
}
}
// Example where button is inside a method of a UIViewController
// and added to the navigationItem of the UINavigationController
let button = createBarButtonItem(title: "Done"){
print("Do something when done")
}
navigationItem.setLeftbarButtonItems([button], animated: false)
Swift 5.2.x
First of all, you need to declare an "easy to use" typealias for your block:
typealias Completion = () -> ()
Then, you must declare private var to use "as a gate" for your function:
private var action: Completion?
After that, you should create a function that can be called by your Selector (it accept only string format) and to call private completion:
#objc func didAction() {
self.action?()
}
Finally you can re-write your function (using the new swift syntax) like:
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(didAction), userInfo: nil, repeats: false)
self.action = backToOriginalBackground
P.S.: Remember that your variable (or parameter if you embed it to a function) must be of the same of type declared to your typeAlias so, in our case:
var backToOriginalBackground: () -> ()
or also:
var backToOriginalBackground: Completion
It has been several years since this question was asked, and it is worth noting that in those years, Apple has added variants of many selector-using methods that take closures instead.
The original question asks about NSTimer.scheduledTimerWithTimeInterval. That method is now spelled Timer.scheduledTimer and has a version that takes a closure. So the function in the original question can be rewritten thus:
extension UIViewController {
func changeBackground() {
self.view.backgroundColor = .black
self.view.alpha = 0.55
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
self.view.backgroundColor = .white
self.view.alpha = 1.0
}
}
}
Here are some other common cases where, as of May 2016, a selector was required, but which can now use a closure:
UIControl now has an addAction method that takes a UIAction, and UIAction takes a closure. Subclasses of UIControl include UIButton, UISwitch, and UITextField.
UIBarButtonItem has an initializer that takes a UIAction.
NotificationCenter now has an addObserver method that takes a closure. It also supports Combine (the publisher method) and async/await (the notifications method).
RunLoop now has a perform method that takes a closure.

NSTimer scheduledTimerWithTimeInterval - not calling funciton

I want to run a timer in the background. So I created a singleton.
The problem is that after the set 5.0 seconds, it does not call the function timeEnded(). Xcode proposes to add #Objc in front of the function (like this: #Objc func timeEnded() {...) to solve some problem (I don't get what, though). But it still doesn't call that function. Any ideas?
class TimerService {
static let instance = TimerService()
var internalTimer: NSTimer?
func startTimer() {
guard internalTimer != nil else {
return print("timer already started")
}
internalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(TimerService.timeEnded), userInfo: nil, repeats: false)
}
func timeEnded() {
//NSNotificationCenter.defaultCenter().postNotificationName("timerEnded", object: nil)
print("timer Ended")
}
}
You never actually start the timer because your startTimer() function will always return before reaching the line of code where you create the timer.
In your guard statement you only continue the execution of the function if internalTimer != nil but the only place where you set the timer is after that statement. Thus, your timer is never created and internalTimer will always be nil.
This should fix your problem:
func startTimer() {
guard internalTimer == nil else {
return print("timer already started")
}
internalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(TimerService.timeEnded), userInfo: nil, repeats: false)
}
Selectors are a feature of Objective-C and can only be used with methods that are exposed to the dynamic Obj-C runtime. You cannot have a selector to a pure Swift method.
If your class inherits from NSObject then its public methods are exposed to Obj-C automatically. Since your class does not inherit from NSObject you have to use the #objc attribute to indicate that you want this method exposed to Obj-C so that it may be called with an Obj-C selector.
#selector() is the new syntax in Swift 2.2. It allows the compiler to check that the selector you're trying to use actually exists. The old syntax is deprecated and will be removed in Swift 3.0.

Handle the selector parameter in swift

Let's say I declare a function in my CustomTimer class:
class CustomTimer {
class func scheduledTimerWithSelector(aSelector: Selector) -> CustomTimer {
// aSelector ??
}
}
How can I handle this aSelector parameter?
Like the NSTimer.scheduledTimerWithTimeInterval method, how dose it work?
You should check the Selector structure.
From the apple docs:
In Swift, Objective-C selectors are represented by the Selector
structure. You can construct a selector with a string literal, such as
let mySelector: Selector = "tappedButton:". Because string literals
can be automatically converted to selectors, you can pass a string
literal to any method that accepts a selector.
Selector function with Swift :
func selectorFunc(aSel:Selector){
if self.respondsToSelector(aSel){
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: aSel, userInfo: nil, repeats: false)
}
}
func gooleIt(){
println("Hello")
}
Function Call :
self.selectorFunc(Selector(gooleIt()))
Hope it help you.
You can declare a method with a selector like this and you can call it by creating a UIControl because performSelector methods are not available in Swift:
func methodWithSelector(sel:Selector) {
var control = UIControl()
control.sendAction(sel, to: self, forEvent: nil)
}
If you do not need to call it on the main thread another option is like this:
func methodWithSelector(sel:Selector) {
NSThread.detachNewThreadSelector(sel, toTarget: self, withObject: nil)
}
You will call it like this:
methodWithSelector(Selector("methodCall"))
or like this
methodWithSelector("methodCall")
then you must have a method with the name of the selector
func methodCall() {
println("methodCall")
}

How to implement callback/selector with performSelector in swift?

I am trying to create an equivalent of below method signature (Objective-C) in swift language. I couldn't get an answer on how to get the right equivalent for this. Any help is highly appreciated.
- (void)myMethod:(MyObject*)firstParam
setCallbackObject:(id)obj
withMySelector:(SEL)selector {
[obj performSelector:selector withObject:nil afterDelay:0]
}
First:
NOTE
The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
If you still want to implement it that way, read below.
You could use NSTimer:
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: self, selector: "selectorMethod", userInfo: nil, repeats: false)
String can be used where Selector is needed. It will automatically be converted (autoboxing).
The delay can be of course higher: 0.1 is then equal to 1 tenth of a second.
To call a method like:
func selectorMethod() {
...
}
We need to check before using the selector on the class. But the respondsToSelector: is in the NSObject protocol, so you have to derive at least from that (or one that subclasses from it).
To make it clear, here is the example.
Code:
class Test {
func myMethod(firstParam: String, setCallbackObject obj: AnyObject, withMySelector selector: Selector) {
if obj.respondsToSelector(selector) {
var myTimer: NSTimer = NSTimer.scheduledTimerWithTimeInterval(0.0, target: obj, selector: selector, userInfo: nil, repeats: false)
myTimer.fire()
} else {
println("Warning: does not respond to given selector")
}
}
}
class Test2: NSObject {
func selectorMethod() {
print("worked")
}
}
var test: Test = Test()
var callBackObj: Test2 = Test2()
test.myMethod("thisfirstis", setCallbackObject: callBackObj, withMySelector: Selector("selectorMethod"))
Output:
workedProgram ended with exit code: 0

Resources