Jak mam dispatch_sync, dispatch_async, dispatch_after itp. W Swift 3, Swift 4 i późniejszych wersjach?

243

Mam dużo kodu w projektach Swift 2.x (lub nawet 1.x), które wyglądają tak:

// Move to a background thread to do some long running work
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    dispatch_async(dispatch_get_main_queue()) {
        self.imageView.image = image
    }
}

Lub coś takiego, aby opóźnić wykonanie:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
    print("test")
}

Lub wszelkie inne zastosowania API Grand Central Dispatch ...

Teraz, gdy otworzyłem swój projekt w Xcode 8 (beta) dla Swift 3, otrzymuję wszelkiego rodzaju błędy. Niektóre z nich oferują naprawę mojego kodu, ale nie wszystkie poprawki produkują działający kod. Co mam z tym zrobić?

Rickster
źródło

Odpowiedzi:

343

Od samego początku Swift zapewnił pewne możliwości zwiększania Swifty w ObjC i C, dodając więcej z każdą wersją. Teraz, w Swift 3, nowa funkcja „importuj jako członka” pozwala frameworkom z niektórymi stylami C API - gdzie masz typ danych, który działa trochę jak klasa, i kilka globalnych funkcji do pracy z nim - działają bardziej jak natywne interfejsy API Swift. Typy danych są importowane jako klasy Swift, powiązane z nimi funkcje globalne są importowane jako metody i właściwości tych klas, a niektóre powiązane rzeczy, takie jak zestawy stałych, mogą w stosownych przypadkach stać się podtypami.

W Xcode 8 / Swift 3 beta Apple zastosował tę funkcję (wraz z kilkoma innymi), aby uczynić środowisko Dispatch znacznie bardziej Swifty. (A także Core Graphics .) Jeśli śledziłeś działania Swift typu open source, to nie jest nowość , ale teraz jest to pierwsza część Xcode.

Pierwszym krokiem na przeniesienie dowolnego projektu Swift 3 powinno być, aby otworzyć go w Xcode 8 i wybierz Edycja> Przekształć> do aktualnych Swift Składnia ... w menu. Dotyczy to (po sprawdzeniu i zatwierdzeniu) wszystkich zmian na raz wymaganych dla wszystkich przemianowanych interfejsów API i innych zmian. (Często na wiersz kodu wpływa więcej niż jedna z tych zmian naraz, więc reagowanie na naprawę błędu - jego osobno może nie wszystko poprawnie obsłużyć).

W rezultacie wspólny wzór odskakiwania do pracy w tle i wstecz wygląda teraz następująco:

// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

Uwaga: używamy .userInitiatedzamiast jednej ze starych DISPATCH_QUEUE_PRIORITYstałych. Specyfikatory jakości usług (QoS) zostały wprowadzone w OS X 10.10 / iOS 8.0, zapewniając systemowi wyraźniejszy sposób ustalania priorytetów pracy i nieaktualne stare specyfikatory priorytetów. Szczegółowe informacje można znaleźć w dokumentach Apple na temat pracy w tle i efektywności energetycznej .

Nawiasem mówiąc, jeśli trzymasz własne kolejki do organizowania pracy, sposób na uzyskanie kolejki wygląda teraz tak (zauważ, że DispatchQueueAttributesto OptionSet, więc używasz literałów w stylu kolekcji do łączenia opcji):

class Foo { 
    let queue = DispatchQueue(label: "com.example.my-serial-queue",
                           attributes: [.serial, .qosUtility])
    func doStuff() {
        queue.async {
            print("Hello World")
        }
    }
}

Używasz dispatch_afterdo pracy później? To także metoda kolejek, która wymaga DispatchTimeoperatora, który ma operatory dla różnych typów liczbowych, dzięki czemu można po prostu dodać całe lub ułamkowe sekundy:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // in half a second...
    print("Are we there yet?")
}

Możesz obejść nowy interfejs API Dispatch, otwierając jego interfejs w Xcode 8 - użyj Otwórz szybko, aby znaleźć moduł Dispatch lub umieść symbol (podobny DispatchQueue) w swoim projekcie / placu zabaw Swift i kliknij go, a następnie przeciągnij się moduł stamtąd. ( Interfejs API Swift Dispatch można znaleźć w nowej, zwięzłej witrynie Apple Reference API i przeglądarce dokumentów w Xcode, ale wygląda na to, że zawartość dokumentów z wersji C jeszcze się do niej nie przeniesiła.)

Więcej porad znajdziesz w Przewodniku migracji .

Rickster
źródło
3
Jeśli chodzi o Xcode 8 Beta 6, atrybut .serial zniknął, a domyślne zachowanie - forums.developer.apple.com/message/159457#159457
hyouuu
6
Wymaga to aktualizacji od XCode 8.1 .. etykieta atrybutów zniknęła, a zamiast niej możemy użyć „DispatchQueue.global (qos: .background) .async”
Mike M
2
Cudowna odpowiedź. Naprawdę pomógł mi to obejść.
Mohsin Khubaib Ahmed
Musiałem użyć qos:zamiastattributes:
Islam Q.
Czy nie powinno tak być myQueue.async {w class Fooprzykładzie?
vacawama,
142

W Xcode 8 beta 4 nie działa ...

Posługiwać się:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    print("Are we there yet?")
}

dla asynchronizacji na dwa sposoby:

DispatchQueue.main.async {
    print("Async1")
}

DispatchQueue.main.async( execute: {
    print("Async2")
})
ingconti
źródło
Więc to nie blokuje interfejsu użytkownika?
user25
72

Ten jest dobrym przykładem na Swift 4około async:

DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}
S. Matsepura
źródło
cześć DispatchQueue.main.async {// Uruchom aktualizacje interfejsu użytkownika} wykonuje się przed wątkiem w tle
Uma Achanta
podobnie z koroutynami Kotlina
25
40

w Xcode 8 użyj:

DispatchQueue.global(qos: .userInitiated).async { }
Marco
źródło
26

Swift 5.2, 4 i późniejsze

Główne i tło kolejki

let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread") 

Praca z wątkami asynchronicznymi i synchronizującymi !

 background.async { //async tasks here } 
 background.sync { //sync tasks here } 

Wątki asynchroniczne będą działać razem z głównym wątkiem.

Synchronizacja wątków zablokuje główny wątek podczas wykonywania.

Saranjith
źródło
1
A jak używałbyś wątków synchronizacji bez blokowania głównego wątku (UI)? Chciałbym wykonać szereg rzeczy w tle - ale te rzeczy muszą być wykonywane jeden po drugim w sposób zsynchronizowany. W tym czasie interfejs powinien pozostać responsywny ... Jak byś to zrobił?
iKK
Użyj NSOperationQueue. Które twoje każde zadanie reprezentuje NSOperation. patrz stackoverflow.com/a/19746890/5215474
Saranjith,
12

Swift 4.1 i 5. Używamy kolejek w wielu miejscach w naszym kodzie. Tak więc utworzyłem klasę wątków ze wszystkimi kolejkami. Jeśli nie chcesz używać klasy Threads, możesz skopiować żądany kod kolejki z metod klas.

class Threads {

  static let concurrentQueue = DispatchQueue(label: "AppNameConcurrentQueue", attributes: .concurrent)
  static let serialQueue = DispatchQueue(label: "AppNameSerialQueue")

  // Main Queue
  class func performTaskInMainQueue(task: @escaping ()->()) {
    DispatchQueue.main.async {
      task()
    }
  }

  // Background Queue
  class func performTaskInBackground(task:@escaping () throws -> ()) {
    DispatchQueue.global(qos: .background).async {
      do {
        try task()
      } catch let error as NSError {
        print("error in background thread:\(error.localizedDescription)")
      }
    }
  }

  // Concurrent Queue
  class func perfromTaskInConcurrentQueue(task:@escaping () throws -> ()) {
    concurrentQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Concurrent Queue:\(error.localizedDescription)")
      }
    }
  }

  // Serial Queue
  class func perfromTaskInSerialQueue(task:@escaping () throws -> ()) {
    serialQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Serial Queue:\(error.localizedDescription)")
      }
    }
  }

  // Perform task afterDelay
  class func performTaskAfterDealy(_ timeInteval: TimeInterval, _ task:@escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: (.now() + timeInteval)) {
      task()
    }
  }
}

Przykład pokazujący użycie kolejki głównej.

override func viewDidLoad() {
    super.viewDidLoad()
     Threads.performTaskInMainQueue {
        //Update UI
    }
}
Gurjinder Singh
źródło