Swift performSelector:withObject:afterDelay: is unavailable [duplicate]
Asked Answered
O

3

144

I have an app in Objective C that I'm transitioning to Swift. In Objective C, I have this method:

[self.view performSelector:@selector(someSelector) withObject:self afterDelay:0.1f];

I'm working with Swift and I can't figure out how to do this. I've tried:

self.view.performSelector(Selector("someSelector"), withObject: self, afterDelay: 0.1)

Here's the error that I get: 'performSelector' is unavailable: 'performSelector' methods are unavailable

What call would I use to call a method afterDelay?

UPDATE

Here's what I ended up with:

extension NSObject {

    func callSelectorAsync(selector: Selector, object: AnyObject?, delay: NSTimeInterval) -> NSTimer {

        let timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: selector, userInfo: object, repeats: false)
        return timer
    }

    func callSelector(selector: Selector, object: AnyObject?, delay: NSTimeInterval) {

        let delay = delay * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
        dispatch_after(time, dispatch_get_main_queue(), {
            NSThread.detachNewThreadSelector(selector, toTarget:self, withObject: object)
        })
    }
}
Ops answered 11/6, 2014 at 18:43 Comment(5)
I would disagree. I'm not asking how to perform a Selector. I'm asking how to perform a Selector afterDelayOps
The same question is answered there. Use GCD and dispatch_after or dispatch_async.Gunner
@David: It's not answered there. dispatch_after uses dispatch queues; whereas performSelector:afterDelay: uses the current run loop. They are different.Justification
hello I used your code in my project, thought you might be interested: github.com/goktugyil/CozyLoadingActivityOntogeny
self.perform(#selector(self.someSelector), with: self, afterDelay: 0.1)Jessi
G
166

Swift 4

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    // your function here
}

Swift 3

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(0.1)) {
    // your function here
}

Swift 2

let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))) 
dispatch_after(dispatchTime, dispatch_get_main_queue(), { 
    // your function here 
})
Gormand answered 11/6, 2014 at 19:11 Comment(14)
But this is different. dispatch_after works with dispatch queues. performSelector:afterDelay: and NSTimer work on run loops.Justification
And your point is? GCD is perfectly suitable here...Gormand
You should pass dispatch_time_t as first argument var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))) dispatch_after(dispatchTime, dispatch_get_main_queue(), { // your function here })Bedell
so if I want 3 seconds, I do 3.0 * Double(NSEC_PER_SEC)?Agave
why is this dispatch_after better than NSTimer.scheduledTimerWithTimeInterval? Aside from the type-safe issue of NSTimer's methods, dispatch_after is less intuitive and I don't know how to handle the "userInfo" scope within the dispatch_after as it does not take a data argument.Unpretentious
@Unpretentious you can just call your function with the userInfo as parameter within the dispatch blockUndine
In Swift 3 you do it like this... DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // Stuff }Kudu
for the latest swift this is the correct way to create dispatchTime: let dispatchTime = DispatchTime.now() + 0.1Wendelin
Here is what I use: DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // }Bucovina
nice, but cancelPreviousPerformRequestsWithTarget doesn't work anymore. Is there a way to cancel a scheduled dispatch before it runs?Improvisatory
@Improvisatory open a new question for thatGormand
"perform selector" had a possibility to cancel this scheduled taskGravitate
Would perform(#selector(myFunctionName), with: nil, afterDelay: 2.1) be an option, with @objc func myFunctionName() {...} ?Flammable
your example doesn't allow to cancel requests but "perform selector" has such abilitySuicidal
L
112

You could do this:

var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)

func someSelector() {
    // Something after a delay
}

SWIFT 3

let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(someSelector), userInfo: nil, repeats: false)

func someSelector() {
    // Something after a delay
}
Lurlenelurline answered 11/6, 2014 at 19:13 Comment(8)
Just a note that this method only works if your class inherits from an NSObject or derivative class. It won't work in a pure swift class.Timikatiming
@JasonCrump: It will work as long as the method is marked @objc or dynamicJustification
Note that this only directly translates for methods with no arguments. If they take one argument, they will be passed the NSTimer in this case, not the given object as in performSelector:afterDelay:. Also, the method cannot return anything.Justification
Also, note that this method will not work on threads without a run loop-- i.e. GCD background threads.Veil
hello I used your code in my project, thought you might be interested: github.com/goktugyil/CozyLoadingActivityOntogeny
To cancel operation of timer thread, i tried as "timer.invalidate()" in swift2. Here "timer" is a variable. Try as follows func stopBackgroundTimer() { guard let timerIsAlive = timer else { return } timerIsAlive.invalidate() return } func applicationDidEnterBackground(application: UIApplication) { stopBackgroundTimer() }Alp
When I used this method App crashes saying unidentified selector. It worked proper when I called method directly(self.<methodname>)Miley
1)you can use Timer instead of NSTimer; 2)if you call scheduledTimer... then you can use block and don't need to use "selector" and separate functions at allSuicidal
C
10

Swift is statically typed so the performSelector: methods are to fall by the wayside.

Instead, use GCD to dispatch a suitable block to the relevant queue — in this case it'll presumably be the main queue since it looks like you're doing UIKit work.

EDIT: the relevant performSelector: is also notably missing from the Swift version of the NSRunLoop documentation ("1 Objective-C symbol hidden") so you can't jump straight in with that. With that and its absence from the Swiftified NSObject I'd argue it's pretty clear what Apple is thinking here.

Cthrine answered 11/6, 2014 at 18:46 Comment(10)
That's a problem. dispatch_get_current_queue is deprecated, and obtaining a queue to run on the current thread (w/ runloop) is not as easy. The performSelector: methods have their place and they are not going anywhere.Chessman
It doesn't look like a problem here — he wants the selector to perform on self.view. So it's probably UIKit work. So it goes on the main queue. Otherwise I guess you'd need to throw in the @objc attribute to force a selector despite Swift, and then probably you'd be smart to use NSRunLoop directly but you could inherit from NSObject to get the old performSelectors.Cthrine
My comment was more on your comment, not the question. If after all, performSelector: is artificially disabled, a simple Objective C category wrapper will do.Chessman
Having followed-up on that, it's notably omitted from the Swift versions of the documentation NSRunLoop and from NSObject. I really think Apple wants this feature gone. So, yes, the workaround is fairly trivial for now but based on Apple's usual behaviour I'd argue it's not a pattern to endorse.Cthrine
Endorsement and necessity are two different things.Chessman
+1 from me for that, but with the caveat that relying on something Apple wants rid of is likely to buy you quite a bit more necessary work in the future when the feature is eventually pulled away. If even Adobe and Microsoft can have Carbon pulled out from under them (rumours were that a 64-bit version was ready but Apple simply decided not to ship it after years of warnings) then us hoi polloi shouldn't adopt the logic that obviously Objective-C will always be underneath.Cthrine
@Tommy: what's "something Apple wants rid of"? performSelector? or runloops?Gramme
@Gramme I'm speculating, I have no inside knowledge, but for now it's probably only safe to say performSelector; runloops are tied up with event scheduling, and especially network access. But you can easily imagine that they'll become a purely internal thing. And I give it five years before we'll be saying goodbye to Objective-C entirely. The Java bridge didn't last that long and if the hype is right about Swift being suitable for systems programming we'll increasingly be talking about the bridge that allows Objective-C to access the system. Though, again, all speculation, not knowledge.Cthrine
As of Xcode 7, the full family of performSelector methods are available in Swift, including performSelectorOnMainThread() and performSelectorInBackground(). Enjoy!Talent
@Talent with the caveat that they work only on Objective-C classes (i.e. ones from genuine Objective-C and Swift classes that inherit by any route from NSObject) — otherwise you'll get a "class XXX does not implement methodSignatureForSelector:"Cthrine

© 2022 - 2024 — McMap. All rights reserved.