Swift에서 딜레이 후 코드 호출하기

Swift // 2024년 07월 19일 작성

이 글에서는 Swift에서 UI를 멈추지 않게 하면서 특정 시간 동안 기다린 후 비동기로 코드를 실행시키는 방법 일부를 정리한다. 모든 방법이 아닌 개인적인 선호로 골랐다는 점에 주의하자.

타이머를 사용해 딜레이 후 호출하기

어쩌면 가장 직관적인 방법으로 Timer를 이용하는 방법이 있다. Objective-C에서는 NSTimer라 불리는 클래스와 동일하다.

Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
    print("3 seconds have passed")
}

타이머를 생성해서 돌리는 방법은 여러가지가 있지만 위의 방식이 가장 편한 것 같았다. 필요하다면 반복(repeats)도 지정할 수 있으니 딜레이 말고도 여러 용도로 쓸 수도 있다.

GCD를 이용해 딜레이 후 호출하기

GCD(Grand Central Dispatch)의 디스패치 큐에는 asyncAfter라는 용도에 딱 맞는 메서드가 제공된다.

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    print("3 seconds have passed")
}

asyncAfter의 경우 딜레이 시간을 정의할 때 약간 난해할 수도 있는데 DispatchTime.now()를 알고 있다면 크게 어려울 것 없이 쓸 수 있다.

다만 위 코드는 메인 스레드에서 동작하는 만큼 동작하는 코드에 따라 UI에 영향을 줄 수도 있으니 아래와 같이 글로벌 큐를 이용하는 방식이 적절하지 않을까 생각된다.

DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
    DispatchQueue.main.async {
        print("3 seconds have passed")
    }
}

이렇게 하면 기다리는 동안 다른 일을 더 해도 UI에 큰 무리를 안 주면서 원하는 대로 동작한다. 다만 UI에 영향을 끼치는 코드는 메인 스레드에서 돌아가도록 주의해야 하는 점은 잊지 말자.

물론 위의 방식들이 정석에 가깝지만, 좀 더 단순하게 하려면 아래와 같이 그냥 쉬었다가(sleep) 하는 방법도 있다.

DispatchQueue.global().async {
    Thread.sleep(forTimeInterval: 3)
    DispatchQueue.main.async {
        print("3 seconds have passed")
    }
}

글로벌 디스패치큐는 백그라운드 스레드에서 돌아가기 때문에 사용할 수 있는 단순한 코드다. 사람에 따라 '무식한' 방법이라고 평가할 수도 있겠지만 어떻게 보면 특징이나 용도를 잘 활용한 기능으로 볼 수도 있어서 '단순한' 방법이라 표현하는 게 맞을 것 같다. 어쨌든 잘 동작하고 별 문제도 없으니 말이다.

Task

Swift Concurrency 지원으로 추가된 비동기 루틴을 지원하는 Task를 활용하면 역시 쉬었다가 처리하는 방식의 '단순한' 방법을 쓸 수도 있다.

Task {
    try await Task.sleep(nanoseconds: 3000000000)
    print("3 seconds have passed")
}

Task 기반은 취소가 가능하다는 장점이 있다. 태스크 취소를 하려면 아래와 같이 해당 태스크 인스턴스에서 cancel() 메서드를 호출해 주면 된다. 다만 이 글의 주제에서 벗어나기 때문에 더 자세한 소개는 생략한다.

let delayedTask = Task {
    try await Task.sleep(nanoseconds: 300000000000)
    print("300 seconds have passed")
}

...

// 해당 태스크를 취소해야 할 필요가 있을 때
delayedTask.cancel()

태스크 내에서의 딜레이(sleep) 명령으로 Thread.sleep()을 쓸 수 있는지는 잘 모르겠지만, 취소를 위해서는 반드시 Task.sleep()을 써야 한다는 것 정도만 알아두자.

사족

GCD를 이용할 수 있으니 당연히 Thread나 OperationQueue를 이용할 수도 있지 않겠나라고 할 수도 있는데 맞다. 사실 이 둘도 위의 예와 거의 동일한 동작을 만들 수 있다. 하지만 가장 심플한 예는 아무래도 위에서 거론한 방식들 같다. 즉 이 외에도 여러 방법이 있을 수도 있으니 상황에 맞는 특수한 방법은 직접 찾아보자.