Entrevistas de iOS testam o sistema de tipos do Swift, como o ARC gerencia memória, e se você consegue raciocinar sobre o estado da UI — além de pelo menos uma pergunta em que um retain cycle está escondido bem à vista. Abaixo estão as perguntas mais feitas, cada uma com uma resposta modelo.
Cada pergunta vem com uma resposta modelo para você comparar com a sua.
1
Que problema os optionals resolvem, e como você faz o unwrap de um com segurança?
Resposta
Um optional torna "esse valor pode não existir" parte do tipo, então o compilador obriga você a tratar o caso vazio em vez de crashar em runtime como faria um ponteiro nulo. Fazer o unwrap com segurança significa usar if let ou guard let para um valor que você precisa no escopo, nil-coalescing (??) para um valor padrão, e optional chaining quando só importa o caminho de sucesso. O force unwrapping com ! é uma promessa ao compilador de que o valor existe — aceitável para outlets e erros de programação, nunca para dados vindos da rede.
2
Qual é a diferença entre struct e class em Swift? Qual você usa por padrão?
Resposta
Structs são tipos de valor: atribuir uma copia o conteúdo, elas não podem herdar, e são alocadas na stack sempre que possível. Classes são tipos de referência, com identidade, herança, deinit e o overhead do ARC. A convenção em Swift é começar com struct e só recorrer a class quando você precisa de semântica de referência — estado mutável compartilhado, um requisito de interoperabilidade com Objective-C, ou um ciclo de vida que você precisa observar.
3
Como o ARC funciona, e o que causa um retain cycle?
Resposta
O ARC conta quantas referências fortes apontam para um objeto e o desaloca quando essa contagem chega a zero — são chamadas de retain/release inseridas em tempo de compilação, não um garbage collector em runtime. Um retain cycle acontece quando dois objetos mantêm referências fortes um para o outro, e nenhuma das contagens chega a zero: o caso clássico é um view controller que possui uma closure que captura self fortemente. Você quebra isso com [weak self] ou [unowned self] na lista de captura.
4
Quando usar weak e quando usar unowned?
Resposta
Os dois evitam incrementar o retain count. weak torna a referência opcional e a define como nil quando o objeto é desalocado, então é seguro quando o outro objeto pode morrer primeiro — delegates são o exemplo clássico. unowned não é opcional e assume que o objeto referenciado vai sobreviver mais que você; se essa suposição falhar, o app crasha. Use unowned só quando a relação de tempo de vida é realmente garantida, e weak sempre que houver dúvida.
5
Qual é a diferença entre @State, @Binding, @StateObject e @ObservedObject?
Resposta
@State é um estado local de tipo valor que a view possui; o SwiftUI o guarda fora da struct para que sobreviva a re-renders. @Binding é uma referência de leitura e escrita a um estado que pertence a outra parte, e é assim que uma view filha altera o valor de uma view pai. @StateObject cria e possui um objeto observable de tipo referência, e é inicializado exatamente uma vez. @ObservedObject observa um objeto possuído em outro lugar — usá-lo onde você precisava de @StateObject recria o objeto a cada re-render e reseta seu estado silenciosamente.
6
Como o SwiftUI decide quando renderizar uma view de novo?
Resposta
Uma view é uma struct leve que descreve a UI; o SwiftUI reconstrói seu body sempre que uma dependência que ela leu muda, depois compara o resultado com a árvore anterior e aplica só as diferenças na renderização final. As dependências são os property wrappers que a view realmente lê, mais o Environment. Problemas de performance quase sempre vêm de estado colocado alto demais na árvore, de modo que uma mudança não relacionada invalida uma subárvore grande.
7
O que async/await muda em relação a completion handlers e GCD?
Resposta
async/await permite ler código assíncrono de cima para baixo, então o tratamento de erros usa try/catch normal e não existe mais aquela "pirâmide da perdição" nem um caminho de completion esquecido. O compilador garante que awaits só aconteçam em um contexto async, e a concorrência estruturada amarra o tempo de vida das tarefas filhas à tarefa pai, então o cancelamento se propaga automaticamente. GCD continua relevante para código legado e controle fino de filas, mas código novo deveria usar async/await por padrão, deixando o Combine reservado para streams de valores genuínos ao longo do tempo.
8
O que é uma escaping closure e por que essa palavra-chave existe?
Resposta
Uma closure é escaping quando é armazenada e chamada depois que a função já retornou — um completion handler de rede, por exemplo. O Swift exige a anotação @escaping porque uma closure non-escaping pode ser otimizada de forma mais agressiva e não pode capturar self fortemente por acidente. Essa anotação também é o sinal para pensar na semântica de captura: é nas closures escaping que os retain cycles nascem.
9
Explique o ciclo de vida do UIViewController. Onde você coloca o código de layout?
Resposta
viewDidLoad roda uma vez, depois que a hierarquia de views é carregada, e é onde você faz a configuração única. viewWillAppear e viewDidAppear rodam a cada apresentação da tela e servem para trabalho que precisa se repetir, como atualizar dados ou iniciar uma animação. Layout que depende do tamanho final do frame vai em viewDidLayoutSubviews, porque em viewDidLoad os bounds ainda não são definitivos — colocar cálculo de frame ali é um bug muito comum, que só aparece em outro tamanho de tela.
10
Como você decide entre Core Data, SwiftData e simplesmente gravar arquivos?
Resposta
Core Data vale a complexidade quando os dados são relacionais e você precisa de queries, faulting e rastreamento de mudanças em um grafo de objetos grande. SwiftData é o wrapper moderno e nativo do Swift sobre a mesma stack, e é a escolha padrão para apps novos nas versões recentes do sistema. Para um punhado de configurações, use UserDefaults; para um cache de modelos decodificados, arquivos simples com Codable ou um key-value store leve são muito mais fáceis de entender do que um persistent container completo.
11
Como você diagnostica um app que demora para abrir ou um vazamento de memória?
Resposta
Para o tempo de abertura, use o template App Launch do Instruments para ver o que roda antes do primeiro frame; os culpados de sempre são trabalho síncrono de disco ou rede no didFinishLaunching e grafos de dependência pesados montados de forma eager. Para vazamentos, o Memory Graph Debugger mostra objetos que deveriam ter sido desalocados e quem ainda aponta para eles, o que expõe retain cycles diretamente. O instrumento Leaks pega vazamentos clássicos, mas o graph é melhor para ciclos porque nomeia a referência que está segurando o objeto.
12
O que você testa no iOS, e o que deliberadamente não testa?
Resposta
Testes unitários cobrem lógica de negócio, view models e transformações puras — qualquer coisa com entrada e saída e sem dependência do UIKit. O XCUITest cobre um número pequeno de fluxos críticos ponta a ponta, como login e compra, porque testes de UI são lentos e frágeis em grande quantidade. O que geralmente não vale a pena testar é o comportamento do framework da Apple e o layout pixel a pixel; snapshot tests podem ajudar aí, mas quebram a cada atualização de versão do sistema, então é bom manter poucos e usá-los com intenção.