Swift
performSelector
withObject
afterDelay
unavailable

Swift performSelectorwithObjectafterDelay is unavailable

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

performSelector:withObject:afterDelay: belongs to Objective-C's selector-based runtime style. Swift does not encourage that pattern because it is not type-safe, and the idiomatic replacement is usually DispatchQueue.asyncAfter.

Why Swift Pushes You Away from Selectors Here

Swift prefers APIs that are:

  • checked at compile time
  • explicit about parameter types
  • harder to misuse with misspelled selector names

A selector string can fail at runtime if the method name or parameter shape is wrong. Closures are much safer and clearer in Swift, so delayed execution is usually expressed with a closure rather than a selector.

The Standard Replacement: DispatchQueue.asyncAfter

For one-shot delayed work on the main thread, use:

swift
1import Foundation
2
3DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
4    print("Ran after a delay")
5}

This is the normal replacement for "do this later on the UI thread."

Replacing the Classic Objective-C Pattern

Old Objective-C style:

objective-c
[self performSelector:@selector(doWork) withObject:nil afterDelay:1.0];

Swift style:

swift
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
    self?.doWork()
}

This is easier to read and safer because the compiler understands the target method directly.

Passing Values to the Delayed Code

Instead of passing an untyped object, capture the value in the closure:

swift
1func showMessage(_ text: String) {
2    print(text)
3}
4
5let message = "Hello"
6
7DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
8    showMessage(message)
9}

Closures make value passing straightforward and type-safe.

Cancelable Delayed Work

If you need to cancel a pending delayed action, use DispatchWorkItem:

swift
1import Foundation
2
3var pendingTask: DispatchWorkItem?
4
5func scheduleRefresh() {
6    pendingTask?.cancel()
7
8    let task = DispatchWorkItem {
9        print("Refreshing...")
10    }
11
12    pendingTask = task
13    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: task)
14}

This is a good fit for debouncing or replacing previously scheduled work.

When Timer Is Better

Use Timer when the task is naturally timer-shaped, especially if it repeats:

swift
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
    print("Timer fired")
}

For one-shot delayed execution, DispatchQueue.asyncAfter is usually simpler. For repeated events or run-loop-oriented behavior, Timer may be the better abstraction.

Swift Concurrency Option

If you are already inside async Swift code, Task.sleep can be a good fit:

swift
1func loadLater() {
2    Task {
3        try? await Task.sleep(for: .seconds(1))
4        print("Delayed in async context")
5    }
6}

This keeps the delay inside the structured-concurrency model instead of dropping to GCD directly.

Choosing the Right Tool

A practical rule is:

  • one delayed UI action: use DispatchQueue.asyncAfter
  • cancelable delayed work: use DispatchWorkItem
  • timer-oriented repeated behavior: use Timer
  • async workflow already using async and await: use Task.sleep

Thinking in those categories usually leads to cleaner code than trying to reproduce the old selector API directly.

Memory Management Notes

Delayed closures can outlive the object that scheduled them. In UI code, a weak capture of self is often the right choice:

swift
[weak self] in

That prevents the delayed block from keeping the owner alive unnecessarily.

Common Pitfalls

The biggest mistake is looking for a one-to-one Swift spelling of performSelector:withObject:afterDelay: instead of switching to DispatchQueue.asyncAfter.

Another mistake is capturing self strongly in a delayed closure when the owner should be allowed to deallocate.

People also use Timer when they only need one delayed block, which adds unnecessary complexity.

Finally, cancellation is not automatic. If the delayed action may become irrelevant, use DispatchWorkItem or another explicit cancellation strategy.

Summary

  • 'performSelector:withObject:afterDelay: is unavailable in Swift because selector-based delayed invocation is not the preferred Swift model.'
  • The usual replacement is DispatchQueue.asyncAfter.
  • Closures let you pass values safely without untyped selector arguments.
  • Use DispatchWorkItem when you need cancelable delayed work.
  • Use Timer or Task.sleep only when those abstractions better match the surrounding code.

Course illustration
Course illustration

All Rights Reserved.