Como envio dispatch_sync, dispatch_async, dispatch_after etc. no Swift 3, Swift 4 e além?

243

Eu tenho muito código nos projetos Swift 2.x (ou até 1.x) que se parecem com isso:

// 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
    }
}

Ou coisas assim para atrasar a execução:

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

Ou qualquer outro tipo de uso da API Grand Central Dispatch ...

Agora que abri meu projeto no Xcode 8 (beta) para o Swift 3, recebo todos os tipos de erros. Alguns deles oferecem a correção do meu código, mas nem todas as correções produzem código funcional. O que eu faço sobre isso?

rickster
fonte

Respostas:

343

Desde o início, o Swift forneceu algumas facilidades para tornar ObjC e C mais Swifty, adicionando mais a cada versão. Agora, no Swift 3, o novo recurso "importar como membro" permite estruturas com certos estilos de API C - onde você tem um tipo de dados que funciona como uma classe e várias funções globais para trabalhar com ele - agem mais como APIs nativas do Swift. Os tipos de dados são importados como classes Swift, suas funções globais relacionadas são importadas como métodos e propriedades nessas classes, e algumas coisas relacionadas, como conjuntos de constantes, podem se tornar subtipos, quando apropriado.

No Xcode 8 / Swift 3 beta, a Apple aplicou esse recurso (junto com alguns outros) para tornar a estrutura do Dispatch muito mais Swifty. (E gráficos básicos também.) Se você tem acompanhado os esforços de código aberto Swift, isso não é novidade , mas agora é a primeira vez que faz parte do Xcode.

Seu primeiro passo na transferência de qualquer projeto para o Swift 3 deve ser aberto no Xcode 8 e escolha Editar> Converter> Para a sintaxe Swift atual ... no menu. Isso aplicará (com sua revisão e aprovação) todas as alterações necessárias de uma só vez para todas as APIs renomeadas e outras alterações. (Geralmente, uma linha de código é afetada por mais de uma dessas alterações ao mesmo tempo, portanto, responder à correção de erros - sua individualmente pode não lidar com tudo certo.)

O resultado é que o padrão comum para devolver o trabalho para segundo plano e para trás agora se parece com o seguinte:

// 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
    }
}

Observe que estamos usando, em .userInitiatedvez de uma das DISPATCH_QUEUE_PRIORITYconstantes antigas . Os especificadores de Qualidade de Serviço (QoS) foram introduzidos no OS X 10.10 / iOS 8.0, fornecendo uma maneira mais clara para o sistema priorizar o trabalho e descontinuar os antigos especificadores de prioridade. Consulte os documentos da Apple sobre trabalho em segundo plano e eficiência energética para obter detalhes.

A propósito, se você está mantendo suas próprias filas para organizar o trabalho, a maneira de obter uma agora se parece com isso (observe que DispatchQueueAttributesé um OptionSet, então você usa literais no estilo de coleção para combinar opções):

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

Usando dispatch_afterpara fazer o trabalho mais tarde? Também é um método nas filas e é necessário um DispatchTime, que possui operadores para vários tipos numéricos, para que você possa adicionar segundos inteiros ou fracionários:

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

Você pode encontrar o caminho da nova API do Dispatch abrindo sua interface no Xcode 8 - use o Open Quickly para encontrar o módulo Dispatch ou coloque um símbolo (como DispatchQueue) no seu projeto / playground Swift e clique com o comando e, em seguida, clique o módulo de lá. (Você pode encontrar a API do Swift Dispatch no novo site de referência da API da Apple e no visualizador de documentos no Xcode, mas parece que o conteúdo do documento da versão C ainda não foi movido.)

Consulte o Guia de migração para obter mais dicas.

rickster
fonte
3
Quanto Xcode 8 Beta 6, o atributo .serial se foi e o comportamento padrão - forums.developer.apple.com/message/159457#159457
hyouuu
6
Isso precisa de uma atualização desde o XCode 8.1. O rótulo de atributos desapareceu e, em seu lugar, podemos usar 'DispatchQueue.global (qos: .background) .async'
Mike M
2
Resposta maravilhosa. Realmente me ajudou a entender isso.
Mohsin Khubaib Ahmed
Eu tive que usar em qos:vez deattributes:
Islam Q.
Isso não deveria estar myQueue.async {no class Fooexemplo?
vacawama
142

No Xcode 8 beta 4 não funciona ...

Usar:

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

para assíncrono de duas maneiras:

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

DispatchQueue.main.async( execute: {
    print("Async2")
})
ingconti
fonte
Portanto, não bloqueia a interface do usuário?
USER25
72

Este é bom exemplo para Swift 4cerca de async:

DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}
S. Matsepura
fonte
oi DispatchQueue.main.async {// Run UI Atualizações} está em execução antes da discussão de fundo
Uma Achanta
semelhante com as corotinas de Kotlin
user25
40

no Xcode 8 use:

DispatchQueue.global(qos: .userInitiated).async { }
Marco
fonte
26

Swift 5.2, 4 e posterior

Filas principais e em segundo plano

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

Trabalhando com threads assíncronos e sincronizados !

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

Threads assíncronos funcionarão junto com o thread principal.

Threads de sincronização bloquearão o thread principal durante a execução.

Saranjith
fonte
1
E como você usaria threads de sincronização sem bloquear o thread principal (UI)? Eu gostaria de executar uma linha de coisas em segundo plano - mas essas coisas devem ser executadas uma após a outra de maneira sincronizada. Durante esse período, a interface do usuário deve permanecer responsiva .... Como você faria isso?
IKK
Use NSOperationQueue. Qual cada tarefa que representa uma NSOperation. referem stackoverflow.com/a/19746890/5215474
Saranjith
12

Swift 4.1 e 5. Usamos filas em muitos lugares em nosso código. Então, eu criei a classe Threads com todas as filas. Se você não quiser usar a classe Threads, poderá copiar o código da fila desejado dos métodos da classe.

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()
    }
  }
}

Exemplo mostrando o uso da fila principal.

override func viewDidLoad() {
    super.viewDidLoad()
     Threads.performTaskInMainQueue {
        //Update UI
    }
}
Gurjinder Singh
fonte