Economizo 20 minutos para você caso não queira ler um post inteiro:
Copie isso em uma nota adesiva e deixe sob o seu monitor para a sua próxima entrevista.
Geralmente, eu amo o formato iOS Knowledge Interview™: 45 minutos para demonstrar meu conhecimento enciclopédico sobre o compilador Swift, dispatch de método, gerenciamento de memória ou binários Mach-O.
Agora, até recentemente. Eu fui perguntado para explicar a pipeline de renderização no iOS.
“Uh… então tem uma tela. Ela tem pixels. Ela detecta toques. Acredito que Core Animation provavelmente faz algo ali em cima...”
— Jacob Bartlett, 2025
Fácil de deixar isso passar. O sistema abstrai o trabalho pesado.
Mas esse trabalho pesado controla cada pixel único em cada quadro único de cada aplicativo que você constrói. Vale a pena gastar tempo para entender isso adequadamente.
A pipeline UI é uma fera complexa, orquestrando vários sistemas pouco documentados. Difícil encontrar um caminho que explique tudo. Felizmente, você está em boas mãos.
Nós estamos rastreando a jornada de um evento de toque: desde sensores até seu código e de volta aos pixels, através de cada sistema ao longo do caminho:
No final dos anos 90, Tio Sam financiou algumas pesquisas que levaram a avanços em telas multi-touch capacitivas. Em 2007, Papai Steve colocou isso na frente de uma confusão de tecnologia miniaturizada finalmente pronta para o grande público: microprocessadores modernos, rádio celular e baterias de lítio de alta capacidade. O resto é história.
Por baixo do vidro da tela do seu iPhone há uma grade de fios transparentes X e Y cruzados. Um microcontrolador aplica um campo elétrico ao longo de cada fio X, medindo sinais ao longo de cada fio Y.
Porque a carne humana é condutora eletricamente (não me pergunte como descobri isso), a presença de um dedo altera a capacitância elétrica medida nos pontos XY. O microcontrolador varre cada fio para criar uma mapa 2D em tempo real.
O kernel é o núcleo do sistema operacional. Ele cria e gerencia abstrações sobre hardware:
A outra responsabilidade principal do kernel é I/O: ou seja, falando com o hardware. Esta interface é feita via drivers, software especializado que traduz comandos de entrada e sinais de saída.
O kernel xnu no iOS interage com o hardware capacitivo via IOKit.
O hardware envia sinais que o SO decodifica em um IOHIDEvent (evento de dispositivo de interface humana IOKit) para representar eventos de toque como rotações, deslizamentos ou arrastos. Este evento é enviado ao backboardd.
O backboardd é um daemon, que você pode identificar pelo D solitário no final do seu nome.
Os demônios são processos de fundo executados pelo SO. Assim como seus nomes bíblicos, eles nunca dormem e sempre estão te observando.
O daemon backboardd gerencia tarefas do sistema como o embaçamento da tela, lê entradas de sensores de hardware como o acelerômetro e trata eventos de toque físico.
Esta seção é um pouco vaga porque não está documentada. A Apple não sente a necessidade de fazê-lo, pois toda essa complexidade é abstraída dos desenvolvedores.
Mesmo assim, minhas únicas fontes são antigas wikis, documentações não oficiais e dumps de engenharia reversa. Por favor, aguente um pouco até chegarmos ao loop principal.
BackBoardServices é um subsistema dentro deste daemon que realiza a ponte entre o I/O do dispositivo e os processos do usuário, incluindo seu aplicativo.
Ele gera um evento de GraphicsServices (GSEvent) para o toque e o envia ao processo de aplicativo relevante usando comunicação interprocesso (IPC), uma canalização segura fornecida pelo sistema operacional.
Para determinar o processo de aplicativo relevante, backboardd se comunica com FrontBoard (parte da SpringBoard). FrontBoard é o que alimenta o gerenciador de troca de aplicativos, gerenciando as cenas visíveis na tela para coisas como a tela inicial e cada aplicativo individual.
Essa cena, ou FBScene, está associada ao seu aplicativo’s CAContext, ou contexto Core Animation, que é o fundamento de toda sua interface do usuário. BackBoardServices pode ler o ID de contexto do CAContext para determinar o processo de aplicativo correto para o evento de toque e encaminhá-lo.
A um aplicativo iOS sempre começa da mesma maneira:
- O usuário toca em um ícone na SpringBoard, o aplicativo de tela inicial do usuário.
- O daemon de inicialização launchd executa syscalls para carregar o aplicativo na memória.
- iOS chama a função main() em seu executável carregado.
- Isso, por sua vez, chama o método embutido UIApplicationMain().
- Inicia um UIApplication, define AppDelegate e inicia o loop principal.
- O aplicativo começa a processar eventos.
- (repetir passo #6 até ficar entediado).
O loop principal do seu aplicativo se comporta como um loop while permanente, oferecendo dois estados básicos: aguardando algo acontecer; e fazendo algo.
while let await touchInputs() { handleTouchEvents() }O loop principal do seu aplicativo é acordado por temporizadores disparando, código enviado para a fila principal e sinais de backboardd como nosso evento de toque.
O loop de execução passa os eventos de toque para o objeto UIApplication, que os converte em UIEvents e os distribui via sendEvent(_:) para a janela UIWindow correspondente da cena ativa. A janela, por sua vez, distribui o evento para uma UIView, determinando o alvo via hit testing.
A mesma coisa acontece mais ou menos com as views SwiftUI, mas vou me concentrar no UIKit por enquanto porque é melhor documentado e tem menos ~mágica~. Se você quiser uma explicação muito vaga, o atributo gráfico do SwiftUI mapeia aproximadamente à árvore de CALayer de alguma forma.
A UIWindow encaminha o UIEvent para a view-alvo, acionando o método touchesBegan(_:with:), ou outro método apropriado UIResponder.
Agora que não estamos mais mexendo em detalhes internos do sistema, podemos demonstrar com um exemplo visual bonito para aproveitar ao máximo a luz do dia.
Vamos criar um botão 3D skeumórfico personalizado subclasseando UIView, completo com os métodos UIResponder mencionados anteriormente:
Quando o usuário toca nele, ele pressiona. Assim como um verdadeiro botão deveria fazer. Sem nonsense de vidro líquido aqui.
O pipeline inteiro acabou de passar diante dos seus olhos.
Vamos voltar atrás.
Para entender o que acontece após seu evento de toque ser registrado, devemos esclarecer que as UIViews no UIKit são finalmente apoiadas pela CALayers do Core Animation.
O Core Animation é o núcleo visual fundamental do iOS. A API principal usa layers, objetos leves com propriedades como frame, borda, filtros, sombra e opacidade.
Independente de você estar usando UIKit ou SwiftUI, a renderização em seu aplicativo é feita usando Core Animation: o conteúdo visual é arranjado em CALayers, em uma árvore hierárquica “layer tree” que pertence à UIWindow.
A principal vantagem do Core Animation é que ele pode animar todas essas alterações de propriedade super facilmente (comparado com os tempos antigos). Você define uma curva de tempo, como linear, mola, easeInOut (ou até mesmo múltiplas curvas com chaves), para descrever como as propriedades mudam ao longo do tempo. O Core Animation interpola entre valores inicial e final, ao longo dessas curvas, para criar o estado visual da animação em cada quadro. Ele aproveita a GPU para fazer isso de forma performática via render server.
Parte do nosso método setPressed() cria um CATransaction.
Uma transaction representa uma mudança visual na tela ou animação. Podemos empacotar nossas alterações visuais em uma única transação para eficiência, sincronização, compartilhamento de código de tempo ou acesso a um manipulador de conclusão.
Escribir CATransaction.begin() e CATransaction.commit() é uma transaction explícita, postada ao render server no commit. Transactions implícitas, alterações sem declarar uma transação, esperam pela próxima iteração do loop de execução antes de se cometerem.
Você pode testar isso mesmo: faça uma alteração em uma propriedade da view ou layer e depois bloquee a thread principal via sleep() commands.
A maioria do trabalho feito com UIKit é realizada implicitamente: considere cada vez que você muda a cor do texto de um rótulo. Isso é renderizado finalmente através de transações CATransactions implícitas. No final da iteração do loop de execução (run loop) acionada pelo nosso evento de toque inicial, o loop dispara CFRunLoopActivity.kCFRunLoopBeforeWaiting, que é observado pela Core Animation. Se a árvore de camadas (layer tree) tiver sido alterada, as mudanças são enviadas para um commit.
Este commit da Core Animation tem 4 fases:
Fase de Layout: A Core Animation percorre a árvore de camadas e posiciona todas as camadas em seus tamanhos e posições corretos. A função setNeedsLayout() pode marcar uma camada para atualização (geralmente na próxima iteração do loop de execução).
Fase de Exibição: Durante a mesma travessia (ambas são feitas na função layout_and_display_if_needed()), qualquer desenho, gráficos ou renderização de texto necessária é realizada.
Fase de Preparação: Trabalhos adicionais como decodificação de imagens são feitos.
Fase de Commit: A nova árvore de camadas é enviada para o servidor de renderização.
Isso levou algum tempo para eu entender: O loop de execução não está ticando constantemente ou com cada quadro. Ele apenas espera por eventos e responde a eles.
Sente-se como se estivesse sempre em funcionamento, porque na maioria das vezes, sua codificação UI será executada em resposta ao despertar do loop de execução como um toque, temporizador ou dispatch da thread principal.
No nosso código de exemplo, comitamos alguns CABasicAnimation.
Eles são enviados para o servidor de renderização, um subsistema dentro do backboardd.
Espero que você tenha tido a chance de trabalhar na sua base bronzeada. Estamos voltando para baixo, nas entranhas sombrias do sistema.
As animações são realizadas ao comitar alterações em propriedades de visualizações. Esses commits são agrupados em transações e enviados implicitamente pelo loop de execução ou explicitamente através da Core Animation para o servidor de renderização.
Antes de deixarmos seu processo de aplicativo, devemos esclarecer como a Core Animation estrutura logicamente sua UI.
Já mencionamos a árvore de camadas: uma estrutura hierárquica contendo as CALayers que coletivamente descrevem o conteúdo visual e animações em sua UI. Esta é a representação interna do iOS do que deve ser desenhado.
Podemos ver sua estrutura quando olhamos para o depurador de UI:

O que eu não lhe disse é que há na verdade 3 árvores de camadas usadas pela Core Animation.
A Model (modelo) árvore de camadas armazena os CALayers em seu estado “final”. Quando você define a propriedade backgroundColor de um botão de branco para vermelho, isso atualiza instantaneamente a árvore de camadas do modelo. Cada CALayer de uma hierarquia de visualizações é representado diretamente na árvore de camadas do modelo (acessível via view.layer).
A Presentation (apresentação) árvore de camadas contém os valores de propriedades visíveis, incluindo os valores interpolados durante uma animação. Isso significa que para o botão de cor acima, ela rastreia o estado em andamento da animação de cada quadro de #FFFFFF (branco), via #FFAAAA e seu amigo #FF5555, até #FF0000 (vermelho).
O Render layer tree é o “verdadeiro” layer usado para criar a interface de usuário na tela e realizar animações. É privativo do Core Animation, vivendo dentro do backboardd no servidor render. Os outros dois estão dentro do processo da aplicação. Porque o renderização vive em um processo separado, você frequentemente verá animações continuando a rodar mesmo quando sua thread principal está bloqueada.
Você pode estar pensando, como eu, “por que precisamos de uma árvore de camadas de apresentação se ela não é usada para renderização”?
Porque a árvore de camadas de apresentação é acessível em seu processo de aplicativo via view.layer.presentation(), você pode ler os valores atuais da animação, ver a geometria em tempo real e realizar testes de colisão para transições em andamento.
A UIWindow do seu app possui um CAContext, que por sua vez possui as árvores de camadas modelo e de apresentação. Quando você comita sua CATransaction, os dados dessa árvore de camadas são passados para o servidor render.
O CAContext da UIWindow do seu app faz ponte até o servidor render via IPC: comunicação interprocesso. É um nome muito sexy para uma maneira bastante entediante que processos separados em um SO trocam dados (no iOS, essa infraestrutura é baseada no Mach).
Este IPC é identificado pelo ID do CAContext, correspondendo-o ao seu gêmeo no servidor render. As CATransactions codificam este ID junto com as atualizações comprometidas para a árvore de camadas render.
A árvore de camadas render está escondida de nós, trabalhando nas sombras dentro do Core Animation render server, que, no iOS, é parte integrante do backboardd. O Core Animation, o framework, consiste em duas metades: a API cliente-side que você toca todos os dias e o motor de renderização server-side que vive aqui.
Espaço. Definição rápida. O que realmente é “renderização”?
Renderização é quando você produz uma imagem a partir de dados de entrada. Neste contexto, são as árvores de camadas render do CALayer rodando no servidor render. O Core Animation rodando no servidor render faz isso rasterizando e compostando as camadas na árvore de camadas render.
Pela misericórdia de Deus. Mais definições? Tudo bem.
Compostagem é o processo de combinar elementos visuais de fontes separadas em uma única imagem. Isso inclui cada árvore de camadas CALayer em cada CAContext em cada UIWindow visível.
Rasterização é pegar um conjunto de instruções de desenho e produzir uma “imagem rasterizada”, ou seja, uma textura/um bitmap/muitos pixels. Aqui, podemos pensar nas várias propriedades de cada CALayer como frame, cor e raio do canto fornecendo essas instruções de desenho.
O servidor render é a autoridade final para o que aparece na tela do seu iDevice. Ele coleta as árvores de camadas render de todos os CAContexts ativos em cada processo na tela, calcula animações em tempo real e produz o quadro final enviado ao hardware da sua tela.
Para produzir este quadro, o servidor render emite comandos de desenho para a GPU via Metal para realizar o trabalho pesado. Esses comandos podem incluir configuração, seleção de funções de shader, seleção de texturas e as chamadas de desenho reais.
A GPU é uma fera, capaz de realizar trilhões de cálculos em ponto flutuante por segundo. De cabeça, você obtém algo como 5k FLOPS por pixel por quadro em um iDevice razoável.
Pense no servidor de renderização como o jóquei, pequeno em estatura mas ardente na disposição ao emitir os comandos relevantes e texturas base. O GPU é sua trabalhadora incansável.
O jóquei não desenha pixels por conta própria. É abaixo dele
(um dos poucos itens que é, ahaha. Porque ele é muito baixinho?). Ele descreve como a próxima frame deve parecer e envia a descrição ao GPU em um buffer de comandos.iPhones usam
renderização diferida baseada em tiles, que consiste em 3 etapas principais (grandemente simplificadas, com desculpas para todos vocês entusiastas de gráficos lendo):Processamento de vértices, onde a geometria da camada é transformada matematicamente no espaço físico da tela.
Fase de tiles, onde o GPU quebra o alvo de renderização em pequenos (geralmente 32px por 32px) tiles para agrupar trabalho futuro.
Processamento de pixels, onde o GPU aplica fragment shaders e mesclagem a cada tile e escreve o tile finalizado na memória.
Essas etapas convertem os comandos (e frequentemente uma textura de entrada, no caso de texto ou imagens) em um bitmap completo.
Você pode enviar trabalho diretamente ao GPU do seu aplicativo com Metal, via CAMetalLayer ou MTKView. O servidor de renderização ainda fará a composição do resultado.
Depois que os shaders de vértices e fragmentos fizeram seu trabalho, a composição e rasterização estão completas: as CAContexts visíveis em todas as janelas visíveis no seu dispositivo foram renderizadas como um único bitmap representando esta frame da IU.
Agora para colocá-las na tela.
O bitmap para a frame é colocado em um
frame buffer na memória e lido pelo coprocessador de exibição, um chip dedicado no SoC Apple Silicon, que coordena essa etapa final.Este chip lê pixels do buffer, aplica gerenciamento de espaço de cor e fala com o driver de hardware da tela. Ele “escaneia” os pixels finais, linha por linha, para a exibição.
A escaneada, assumindo que você não está hitching, é sincronizada com o ciclo de atualização do hardware da tela,
vsync, que geralmente funciona em 16,67ms (60fps), ou talvez 8,33ms (120fps) se você for milionário.No iOS, o coprocessador de exibição usa vsync (
vertical sync) para bloquear a frame na fronteira da taxa de atualização do display. É por isso que problemas de desempenho da IU levam a frames perdidos: em outros sistemas, isso pode se manifestar como tearing, onde o buffer de tela é trocado durante a escaneada, renderizando múltiplas frames em diferentes partes da tela simultaneamente.No último passo de nossa Odisséia, nosso toque finalmente muda alguns pixels na sua tela.
Olha, você não se inscreveu no Tech Tavern do Jacob para aprender como telas LCD/OLED funcionam. Leia um livro, por Cristo.
Aqui está o resumo novamente. Imprima-o. Coloque-o em uma Displate. Compartilhe nas suas redes sociais para clout. Em vez de um grupo sombrio e assustador de caras, agora você pode vê-los como novos amigos. Talvez queira tomar uma cerveja com eles. Preciso de uma.
Francamente, não acho que eu seria capaz de codificar essa pipeline de IU do zero. Provavelmente nem conseguiria vibear código.
Core Animation foi o culminar de décadas de evolução tanto em hardware quanto em software. O iOS também existe. Sim, a renderização é difícil e estou cansado, então por favor faça um favor para mim e imagine uma conclusão realmente boa, pronto.
Rico1 é seu conselheiro de crescimento de aplicativos alimentado por IA. Pergunte sobre retenção, preços, experimentos ou tendências de receita e obtenha respostas práticas usando seus dados de assinatura, benchmarks e insights da configuração da loja.
1Saiba mais sobre Rico

