Sobes.tech
Middle

Por que não é recomendado sobrecarregar a thread principal no iOS, e quais são as maneiras de evitar isso?

sobes.tech IA

Resposta da IA

O thread principal é responsável por atualizar a interface de utilizador e tratar eventos de UI. Quaisquer operações longas (requisições de rede, cálculos pesados, trabalho com ficheiros) no thread principal irão bloqueá-lo, levando a uma UI "congelada", baixa resposta da aplicação e até ao seu crash (timeout do watchdog).

Formas de evitar a carga no thread principal:

  1. Grand Central Dispatch (GCD): Permite executar tarefas de forma assíncrona em filas de fundo.

    • DispatchQueue.global(): Obter filas globais de fundo com diferentes prioridades.
    • DispatchQueue.main: Fila principal para atualizar a UI.
    • async { ... }: Executar um bloco de código de forma assíncrona.
    // executar tarefa pesada em segundo plano
    DispatchQueue.global(qos: .userInitiated).async {
        // cálculos pesados ou requisição de rede
    
        // atualizar a UI no thread principal
        DispatchQueue.main.async {
            // atualizar a interface de utilizador
        }
    }
    
  2. Queues de Operações (Operation & OperationQueue): Uma abstração de nível superior sobre as filas GCD, suportando dependências entre operações, cancelamento e suspensão.

    • OperationQueue(): Criar uma fila de operações.
    • Operation: Classe abstrata para tarefas que podem ser adicionadas à fila.
    • addOperation(_:): Adicionar uma operação à fila.
    let downloadOperation = BlockOperation {
        // descarregar dados
    }
    
    let parseOperation = BlockOperation {
        // analisar dados
    }
    
    // dependência: a análise começa após o download
    parseOperation.addDependency(downloadOperation)
    
    let queue = OperationQueue()
    queue.addOperation(downloadOperation)
    queue.addOperation(parseOperation)
    
  3. Concorrência (async/await): Abordagem moderna em Swift para programação assíncrona.

    • async: Marca uma função como assíncrona.
    • await: Aguarda a conclusão de uma operação assíncrona.
    • Task: Criar uma nova tarefa concorrente.
    func fetchData() async throws -> Data {
        let url = URL(string: "https://example.com/data")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    
    func updateUI() {
        Task {
            do {
                let data = try await fetchData()
                // processar dados
                // atualizar a UI no thread principal automaticamente
                // (se a tarefa for criada num Actor principal ou DispatchQueue.main)
            } catch {
                // tratar erro
            }
        }
    }
    
  4. Atores: Impedem condições de corrida ao aceder de diferentes contextos concorrentes, garantindo acesso exclusivo ao seu estado.

    actor DataStore {
        private var data: [String] = []
    
        func addData(_ item: String) {
            data.append(item)
        }
    
        func getData() -> [String] {
            return data
        }
    }
    
    let store = DataStore()
    
    Task {
        await store.addData("item1")
    }
    

A escolha entre GCD, Queues de Operações e Concorrência depende da complexidade da tarefa, do nível de controlo necessário e dos requisitos de estruturação do código. A concorrência é a abordagem preferida em aplicações modernas devido à sua legibilidade e segurança.