dispatch_after - GCD у Swift?


558

Я пережив iBook від Apple, і не зміг знайти жодного визначення цього:

Чи може хтось пояснити структуру dispatch_after?

dispatch_after(<#when: dispatch_time_t#>, <#queue: dispatch_queue_t?#>, <#block: dispatch_block_t?#>)

1
Apple опублікувала цю книгу в 2018 році. Останній архів, який я міг знайти, - із грудня 2017 року . Старі посилання на iBook тепер просто переспрямовуються на developer.apple.com/documentation/swift .
Cœur

Відповіді:


742

Більш чітке уявлення про структуру:

dispatch_after(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t?)

dispatch_time_tє a UInt64. dispatch_queue_tФактично тип поєднаним Ань NSObject, але ви повинні просто використовувати ваші знайомі методи НСД , щоб отримати черзі. Блок - це закриття Swift. Зокрема, dispatch_block_tвизначається як () -> Void, що еквівалентно () -> ().

Приклад використання:

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
    print("test")
}

Редагувати:

Я рекомендую використовувати дійсно гарну delayфункцію @ matt .

EDIT 2:

У Swift 3 з'являться нові обгортки для GCD. Дивіться тут: https://github.com/apple/swift-evolution/blob/master/proposals/0088-libdispatch-for-swift3.md

Оригінальний приклад був би записаний у Swift 3:

let deadlineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
    print("test")
}

Зауважте, що ви можете написати deadlineTimeдекларацію як DispatchTime.now() + 1.0і отримати той самий результат, оскільки +оператор переосмислюється наступним чином (аналогічно для -):

  • func +(time: DispatchTime, seconds: Double) -> DispatchTime
  • func +(time: DispatchWalltime, interval: DispatchTimeInterval) -> DispatchWalltime

Це означає, що якщо ви не використовуєте DispatchTimeInterval enumта просто записуєте число, передбачається, що ви використовуєте секунди.


17
Порада: Оскільки блок є кінцевим параметром функції, ви можете використати синтаксис "Закінчення закриття" Свіфта для додаткової читабельності:dispatch_after(1, dispatch_get_main_queue()) { println("test") }
Білл

8
Я думаю, що використання числа 1в цьому dispatch_after(1, ...може викликати велику плутанину. Люди подумають, що це число секунд, коли воно насправді наносекунд . Я пропоную переглянути відповідь @brindy про те, як правильно створити це число.
Hlung

3
Будь ласка , змініть 1до , dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))тому що це призводить до плутанини. Люди могли подумати, що вам не потрібно створювати dispatch_time_t у Swift
OemerA

4
Версія Swift 3, здається, не працює. Він скаржиться, що Binary operator '+' cannot be applied to operands of type DispatchTime and '_'на лініїlet delayTime = DispatchTime.now() + .seconds(1.0)
Енді Ібанез

9
Переписати це так, як DispatchTime.now() + 1.0здається, єдиний спосіб змусити його працювати (не потрібно .seconds)
Енді Ібанез

1092

Я використовую dispatch_afterтак часто, що я написав функцію утиліти верхнього рівня, щоб спростити синтаксис:

func delay(delay:Double, closure:()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

А тепер можна говорити так:

delay(0.4) {
    // do stuff
}

Нічого собі, мова, де можна вдосконалити мову. Що може бути краще?


Оновлення для Swift 3, Xcode 8 Seed 6

Мабуть, майже не варто турбуватися, коли вони покращили синтаксис виклику:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

2
Мені просто знадобився ярлик для розрахунку затримки, який закінчився:func delayInSec(delay: Double) -> dispatch_time_t { return dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) }
Aviel Gross

4
@ agf119105 Якщо у вас є лише один рядок коду у закритті, додайте інший рядок коду (наприклад return).
мат

2
@GastonM Нерелевантно. Передача функції сама по собі не має проблем із управлінням пам'яттю.
мат

7
Msgstr "Мова, де можна вдосконалити мову". Я не розумію, як визначити глобальну функцію вдосконалення мови, або чому це неможливо зробити навіть на C. Можливо, якщо ви перевантажите оператора;)1.0 ~~ { code...}
Yerk

8
Не сумніваючись у правильності вашої відповіді, але чи не "я використовую dispatch_after так часто" запах коду, з яким найкраще боротися, не надаючи функції зручності?
Микола Рухе

128

Швидкий 3+

Це дуже просто та елегантно в Swift 3+:

DispatchQueue.main.asyncAfter(deadline: .now() + 4.5) {
    // ...
}

Старіша відповідь:

Щоб розширити відповідь Цезарі, яка виконається через 1 наносекунд, мені довелося виконати наступне, щоб виконати через 4 з половиною секунди.

let delay = 4.5 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), block)

Редагувати: я виявив, що мій початковий код був трохи невірним. Неявне введення тексту спричиняє помилку компіляції, якщо ви не передаєте NSEC_PER_SEC до Double.

Якщо хтось може запропонувати більш оптимальне рішення, я б хотів його почути.


Я отримую помилку компілятора для застарілого API з dispatch_get_current_queue(). Я використовував dispatch_get_main_queue()замість цього.
Девід Л

@DavidL - дякую, dispatch_get_main_queue()безумовно, те, що ви повинні використовувати. Буде оновлено.
brindy

я спробував це на ігровому майданчику зі швидким 3, і це не спрацює
μολὼν.λαβέ

@GAlexander працює для мене. Чи дозволяєте ви майданчик виконувати безстроково?
бринді

ум, ну ні, я даю бігати пару годин, і все ще нічого не друкується. ось що я використав. "import Dispatch import Darwin import CoreGraphics 'DispatchQueue.main.asyncAfter (крайній термін: .now () + 4.5) {print (" got here ")}"
µολὼν.λαβέ

83

синтаксис matt дуже приємний, і якщо вам потрібно скасувати блок, ви можете скористатися цим:

typealias dispatch_cancelable_closure = (cancel : Bool) -> Void

func delay(time:NSTimeInterval, closure:()->Void) ->  dispatch_cancelable_closure? {

    func dispatch_later(clsr:()->Void) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(time * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), clsr)
    }

    var closure:dispatch_block_t? = closure
    var cancelableClosure:dispatch_cancelable_closure?

    let delayedClosure:dispatch_cancelable_closure = { cancel in
        if closure != nil {
            if (cancel == false) {
                dispatch_async(dispatch_get_main_queue(), closure!);
            }
        }
        closure = nil
        cancelableClosure = nil
    }

    cancelableClosure = delayedClosure

    dispatch_later {
        if let delayedClosure = cancelableClosure {
            delayedClosure(cancel: false)
        }
    }

    return cancelableClosure;
}

func cancel_delay(closure:dispatch_cancelable_closure?) {

    if closure != nil {
        closure!(cancel: true)
    }
}

Використовуйте наступним чином

let retVal = delay(2.0) {
    println("Later")
}
delay(1.0) {
    cancel_delay(retVal)
}

кредити

Посилання вище, здається, не працює. Оригінальний код Objc від Github


1
Єдиною функцією продуктивності, яка має performSelector: afterDelay - це можливість її скасувати. Тільки це рішення охоплює проблему. Спасибі
HotJard

@HotJard Зауважте, що performSelector:afterDelay:тепер доступний у Swift 2, тому ви можете скасувати його.
мат

@matt, але він доступний лише для NSObject, чи не так?
HotJard

@HotJard Звичайно, але це краще, ніж його взагалі немає. Я не бачу жодної проблеми там. Однак, так само, як і в цій відповіді, я вже компенсував її втрату, написавши таймер dispatch_source_t, що відміняється на основі GCD (використовуючи , оскільки це можна скасувати).
мат

2
Велике спасибі, я використовував це до Swift 2.3. Компілятор Swift 3.0 скаржиться зараз, було б чудово, якби ви оновили свою відповідь!
нетоматичний

27

Найпростіше рішення в Swift 3.0 & Swift 4.0 & Swift 5.0

func delayWithSeconds(_ seconds: Double, completion: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { 
        completion()
    }
}

Використання

delayWithSeconds(1) {
   //Do something
}

22

Apple має фрагмент dispatch_after для Objective-C :

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    <#code to be executed after a specified delay#>
});

Ось той самий фрагмент, який перенесли на Swift 3:

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + <#delayInSeconds#>) {
  <#code to be executed after a specified delay#>
}

14

Ще один спосіб розширити Double так:

extension Double {
   var dispatchTime: dispatch_time_t {
       get {
           return dispatch_time(DISPATCH_TIME_NOW,Int64(self * Double(NSEC_PER_SEC)))
       }
   }
}

Тоді ви можете використовувати його так:

dispatch_after(Double(2.0).dispatchTime, dispatch_get_main_queue(), { () -> Void in
            self.dismissViewControllerAnimated(true, completion: nil)
    })

Мені подобається функція затримки мата, але просто з переваги, я краще обмежую пропускні закриття навколо.


8

У Swift 3.0

Черги відправлення

  DispatchQueue(label: "test").async {
        //long running Background Task
        for obj in 0...1000 {
            print("async \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.async(execute: { 
            print("UI update on main queue")
        })

    }

    DispatchQueue(label: "m").sync {
        //long running Background Task
        for obj in 0...1000 {
            print("sync \(obj)")
        }

        // UI update in main queue
        DispatchQueue.main.sync(execute: {
            print("UI update on main queue")
        })
    }

Відправити через 5 секунд

    DispatchQueue.main.after(when: DispatchTime.now() + 5) {
        print("Dispatch after 5 sec")
    }

4

Версія Swift 3.0

Після функції закриття виконайте деяку задачу після затримки на основному потоці.

func performAfterDelay(delay : Double, onCompletion: @escaping() -> Void){

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: {
       onCompletion()
    })
}

Назвіть цю функцію так:

performAfterDelay(delay: 4.0) {
  print("test")
}

4

1) Додайте цей метод як частину розширення UIViewController.

extension UIViewController{
func runAfterDelay(delay: NSTimeInterval, block: dispatch_block_t) {
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
        dispatch_after(time, dispatch_get_main_queue(), block)
    }
}

Викличте цей метод на VC:

    self.runAfterDelay(5.0, block: {
     //Add code to this block
        print("run After Delay Success")
    })

2)

performSelector("yourMethod Name", withObject: nil, afterDelay: 1)

3)

override func viewWillAppear(animated: Bool) {

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue(), { () -> () in
    //Code Here
})

// Компактна форма

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2), dispatch_get_main_queue()) {
    //Code here
 }
}

3

Хоча ОР не є оригінальним запитанням, деякі NSTimerвідповідні питання були позначені як дублікати цього питання, тому тут варто включити NSTimerвідповідь.

NSTimer проти dispatch_after

  • NSTimerє більш високим рівнем, а dispatch_afterбільш низьким.
  • NSTimerпростіше скасувати. Для скасування dispatch_afterпотрібно писати більше коду .

Затримка завдання з NSTimer

Створіть NSTimerекземпляр.

var timer = NSTimer()

Запустіть таймер із необхідною затримкою.

// invalidate the timer if there is any chance that it could have been called before
timer.invalidate()
// delay of 2 seconds
timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 

Додайте функцію, яку потрібно викликати після затримки (використовуючи те ім’я, яке ви використовували для selectorвищевказаного параметра).

func delayedAction() {
    print("Delayed action has now started."
}

Примітки

  • Якщо вам потрібно скасувати дію, перш ніж вона станеться, просто зателефонуйте timer.invalidate().
  • Для повторних дій використовуйте repeats: true.
  • Якщо у вас є разова подія, без якої потрібно скасовувати, то не потрібно створювати timerзмінну екземпляра. Досить буде наступного:

    NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false) 
  • Дивіться мою більш повну відповідь тут .


3

Для декількох функцій використовуйте це. Це дуже корисно для використання анімації або завантажувача активності для статичних функцій або будь-якого оновлення інтерфейсу користувача.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
            // Call your function 1
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                // Call your function 2
            }
        }

Наприклад - Використовуйте анімацію перед повторним завантаженням tableView. Або будь-яке інше оновлення інтерфейсу користувача після анімації.

*// Start your amination* 
self.startAnimation()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
                *// The animation will execute depending on the delay time*
                self.stopAnimation()
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    *// Now update your view*
                     self.fetchData()
                     self.updateUI()
                }
            }

2

Це працювало для мене.

Швидкий 3:

let time1 = 8.23
let time2 = 3.42

// Delay 2 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Sum of times: \(time1 + time2)")
}

Завдання-C:

CGFloat time1 = 3.49;
CGFloat time2 = 8.13;

// Delay 2 seconds

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    CGFloat newTime = time1 + time2;
    NSLog(@"New time: %f", newTime);
});

2

Swift 3 і 4:

Ви можете створити розширення на DispatchQueue і додати функцію затримки, яка використовує функцію DispatchQueue asyncAfter внутрішньо

extension DispatchQueue {
    static func delay(_ delay: DispatchTimeInterval, closure: @escaping () -> ()) {
        let timeInterval = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: timeInterval, execute: closure)
    }
}

використання:

DispatchQueue.delay(.seconds(1)) {
    print("This is after delay")
}

1

Інший помічник для затримки вашого коду, який на 100% є швидким у використанні, і, можливо, дозволяє вибрати інший потік для запуску затримки коду з:

public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: @escaping () -> Void) {
    let dispatchTime = DispatchTime.now() + seconds
    dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}

public enum DispatchLevel {
    case main, userInteractive, userInitiated, utility, background
    var dispatchQueue: DispatchQueue {
        switch self {
        case .main:                 return DispatchQueue.main
        case .userInteractive:      return DispatchQueue.global(qos: .userInteractive)
        case .userInitiated:        return DispatchQueue.global(qos: .userInitiated)
        case .utility:              return DispatchQueue.global(qos: .utility)
        case .background:           return DispatchQueue.global(qos: .background)
        }
    }
}

Тепер ви просто затримаєте свій код на головній темі так:

delay(bySeconds: 1.5) { 
    // delayed code
}

Якщо ви хочете відкласти свій код до іншої нитки :

delay(bySeconds: 1.5, dispatchLevel: .background) { 
    // delayed code that will run on background thread
}

Якщо ви віддаєте перевагу Framework, який також має деякі зручніші функції, то замовіть HandySwift . Ви можете додати його до свого проекту через Carthage, а потім використовувати його точно так, як у наведених вище прикладах, наприклад:

import HandySwift    

delay(bySeconds: 1.5) { 
    // delayed code
}

1

Я завжди вважаю за краще використовувати розширення замість вільних функцій.

Швидкий 4

public extension DispatchQueue {

  private class func delay(delay: TimeInterval, closure: @escaping () -> Void) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
  }

  class func performAction(after seconds: TimeInterval, callBack: @escaping (() -> Void) ) {
    DispatchQueue.delay(delay: seconds) {
      callBack()
    }
  }

}

Використовуйте наступним чином.

DispatchQueue.performAction(after: 0.3) {
  // Code Here
}

1

Затримка виклику GCD за допомогою asyncAfter швидко

let delayQueue = DispatchQueue(label: "com.theappmaker.in", qos: .userInitiated)
let additionalTime: DispatchTimeInterval = .seconds(2)

Ми можемо затримати як ** мікросекунди , мілісекунди , наносекунди

delayQueue.asyncAfter(deadline: .now() + 0.60) {
    print(Date())
}

delayQueue.asyncAfter(deadline: .now() + additionalTime) {
    print(Date())
}

1

У Swift 4

Скористайтеся цим фрагментом:

    let delayInSec = 1.0
    DispatchQueue.main.asyncAfter(deadline: .now() + delayInSec) {
       // code here
       print("It works")
    }

Це вже в інших відповідях (наприклад, бринді, або Рахулі) ... той самий синтаксис ...
Ерік Айя

1
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // ...
});

dispatch_after(_:_:_:)Функція приймає три параметри:

затримка
черги відправки
блоку або закриття

dispatch_after(_:_:_:)Функція викликає блок або замикання на черзі відправки, яка передається функції після заданої затримки. Зауважте, що затримка створюється за допомогою dispatch_time(_:_:)функції. Пам'ятайте про це, оскільки ми також використовуємо цю функцію в Swift.

Рекомендую пройти навчальний посібник Raywenderlich Dispatch


1

У Swift 5 використовуйте наведене нижче:

 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: closure) 

// time gap, specify unit is second
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
            Singleton.shared().printDate()
        }
// default time gap is second, you can reduce it
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
          // just do it!
    }

0

використовуйте цей код, щоб виконати якесь завдання, пов’язане з інтерфейсом, через 2,0 секунди

            let delay = 2.0
            let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
            let mainQueue = dispatch_get_main_queue()

            dispatch_after(delayInNanoSeconds, mainQueue, {

                print("Some UI related task after delay")
            })

Версія Swift 3.0

Після функції закриття виконайте деяку задачу після затримки на основному потоці.

func performAfterDelay(delay : Double, onCompletion: @escaping() -> Void){

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: {
       onCompletion()
    })
}

Назвіть цю функцію так:

performAfterDelay(delay: 4.0) {
  print("test")
}

1
Це майже ідентично попереднім відповідям
Даніель Галаско

Схоже, що ця відповідь була зроблена на початку 2016 року і є старшою, щонайменше, з інших 6 відповідей ..
eharo2

0

Тепер більше, ніж синтаксичний цукор для асинхронних відправлень у Grand Central Dispatch (GCD) у Свіфті.

додати Podfile

pod 'AsyncSwift'

Тоді ви можете використовувати його так.

let seconds = 3.0
Async.main(after: seconds) {
print("Is called after 3 seconds")
}.background(after: 6.0) {
print("At least 3.0 seconds after previous block, and 6.0 after Async code is called")
}

Apple дала нам усі необхідні для використання GCD в декількох рядках. Навіщо турбуватися про стручки, робочу область тощо? Просто читайте документи про @escaping та захоплення. цього достатньо.
ingconti

0

Swift 4 має досить короткий спосіб зробити це:

Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
    // Your stuff here
    print("hello")
}

0

Ось синхронна версія asyncAfter у Swift:

let deadline = DispatchTime.now() + .seconds(3)
let semaphore = DispatchSemaphore.init(value: 0)
DispatchQueue.global().asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
    semaphore.signal()
}

semaphore.wait()

Поряд з асинхронним:

let deadline = DispatchTime.now() + .seconds(3)
DispatchQueue.main.asyncAfter(deadline: deadline) {
    dispatchPrecondition(condition: .onQueue(DispatchQueue.global()))
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.