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)
        })
    }
}
Cristik
  • 30,989
  • 25
  • 91
  • 127
Cody Winton
  • 2,989
  • 5
  • 25
  • 48

3 Answers3

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 
})
Borzh
  • 5,069
  • 2
  • 48
  • 64
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • 2
    But this is different. `dispatch_after` works with dispatch queues. `performSelector:afterDelay:` and `NSTimer` work on run loops. – user102008 Aug 27 '14 at 19:26
  • And your point is? GCD is perfectly suitable here... – brandonscript Aug 27 '14 at 19:30
  • 4
    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 })` – Vladimirs Matusevics Dec 19 '14 at 17:15
  • so if I want 3 seconds, I do 3.0 * Double(NSEC_PER_SEC)? – Van Du Tran Feb 16 '15 at 18:14
  • 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. – mobibob Oct 04 '15 at 19:19
  • @mobibob you can just call your function with the userInfo as parameter within the dispatch block – Daniel May 17 '16 at 07:58
  • 9
    In Swift 3 you do it like this... DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // Stuff } – 365SplendidSuns Nov 18 '16 at 17:13
  • 5
    for the latest swift this is the correct way to create dispatchTime: let dispatchTime = DispatchTime.now() + 0.1 – Tung Fam Jan 11 '17 at 22:39
  • 1
    Here is what I use: `DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // }` – Thomas Jeans Nov 05 '17 at 19:40
  • 1
    nice, but `cancelPreviousPerformRequestsWithTarget` doesn't work anymore. Is there a way to cancel a scheduled dispatch before it runs? – progrmr Nov 14 '17 at 18:50
  • @progrmr open a new question for that – brandonscript Nov 14 '17 at 19:38
  • "perform selector" had a possibility to cancel this scheduled task – Vyachaslav Gerchicov Jan 31 '19 at 12:19
  • Would `perform(#selector(myFunctionName), with: nil, afterDelay: 2.1)` be an option, with `@objc func myFunctionName() {...}` ? – hotdogsoup.nl Dec 27 '21 at 19:36
  • your example doesn't allow to cancel requests but "perform selector" has such ability – Gargo Jun 09 '23 at 07:13
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
}
Noor Ali Butt
  • 798
  • 8
  • 24
StevenOjo
  • 2,498
  • 1
  • 16
  • 21
  • 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. – Jason Crump Jul 31 '14 at 14:50
  • @JasonCrump: It will work as long as the method is marked `@objc` or `dynamic` – user102008 Aug 27 '14 at 19:25
  • 1
    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. – user102008 Aug 27 '14 at 19:46
  • Also, note that this method will not work on threads without a run loop-- i.e. GCD background threads. – Sean Dec 31 '14 at 06:06
  • 1
    hello I used your code in my project, thought you might be interested: https://github.com/goktugyil/CozyLoadingActivity – Esqarrouth Jun 04 '15 at 15:26
  • 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() } – Sakthimuthiah Dec 01 '15 at 14:40
  • When I used this method App crashes saying unidentified selector. It worked proper when I called method directly(self.) – sschunara Mar 17 '16 at 04:50
  • 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 all – Gargo Jun 09 '23 at 07:18
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.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • 1
    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. – Léo Natan Jun 11 '14 at 18:50
  • 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 `performSelector`s. – Tommy Jun 11 '14 at 19:00
  • 1
    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. – Léo Natan Jun 11 '14 at 19:06
  • 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. – Tommy Jun 11 '14 at 19:13
  • 1
    Endorsement and necessity are two different things. – Léo Natan Jun 11 '14 at 19:16
  • +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. – Tommy Jun 11 '14 at 19:32
  • @Tommy: what's "something Apple wants rid of"? performSelector? or runloops? – newacct Jun 12 '14 at 23:51
  • 1
    @newacct 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. – Tommy Jun 13 '14 at 01:35
  • As of Xcode 7, the full family of performSelector methods are available in Swift, including `performSelectorOnMainThread()` and `performSelectorInBackground()`. Enjoy! – FizzBuzz Jul 21 '15 at 19:50
  • @FizzBuzz 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:" – Tommy Jul 23 '15 at 13:33