dezembro 21, 2025
19 min de leitura

Perguntas para Entrevista de Desenvolvedor Mobile Sênior (iOS): Guia Completo

interview
career-advice
job-search
Perguntas para Entrevista de Desenvolvedor Mobile Sênior (iOS): Guia Completo
Milad Bonakdar

Milad Bonakdar

Autor

Domine o desenvolvimento iOS avançado com perguntas essenciais para entrevistas, abrangendo padrões de arquitetura, otimização de desempenho, concorrência, Core Data e design de sistemas para desenvolvedores seniores.


Introdução

Espera-se que desenvolvedores iOS seniores arquitetem aplicações robustas e escaláveis, mantendo alta qualidade de código e desempenho. Esta função requer conhecimento profundo de frameworks iOS, padrões de design, gerenciamento de memória e a capacidade de tomar decisões arquiteturais informadas.

Este guia abrangente cobre questões essenciais de entrevista para Desenvolvedores iOS Seniores, abrangendo conceitos avançados de Swift, padrões arquiteturais, otimização de desempenho, concorrência e design de sistemas. Cada questão inclui respostas detalhadas, avaliação de raridade e classificações de dificuldade.


Swift Avançado & Recursos da Linguagem (6 Questões)

1. Explique o gerenciamento de memória do Swift e o ARC (Automatic Reference Counting).

Resposta: O ARC gerencia automaticamente a memória rastreando e gerenciando referências a instâncias de classes.

  • Como funciona: Cada instância de classe tem uma contagem de referências. Quando a contagem chega a zero, a instância é desalocada.
  • Referências Fortes (Strong References): Padrão. Aumenta a contagem de referências.
  • Referências Fracas (Weak References): Não aumentam a contagem de referências. Automaticamente se tornam nil quando a instância é desalocada.
  • Referências Não-Posseídas (Unowned References): Não aumentam a contagem de referências, mas assumem que a instância sempre existe.
  • Ciclos de Retenção (Retain Cycles): Ocorrem quando dois objetos mantêm referências fortes um para o outro, impedindo a desalocação.
class Pessoa {
    var nome: String
    var apartamento: Apartamento?
    
    init(nome: String) { self.nome = nome }
    deinit { print("\(nome) está sendo desalocado(a)") }
}

class Apartamento {
    var unidade: String
    weak var inquilino: Pessoa?  // weak para quebrar o ciclo de retenção
    
    init(unidade: String) { self.unidade = unidade }
    deinit { print("Apartamento \(unidade) está sendo desalocado(a)") }
}

Raridade: Muito Comum Dificuldade: Difícil


2. O que são Generics em Swift e por que são úteis?

Resposta: Generics permitem que você escreva funções e tipos flexíveis e reutilizáveis que podem funcionar com qualquer tipo.

  • Benefícios: Reutilização de código, segurança de tipo, desempenho (sem sobrecarga de tempo de execução)
  • Restrições de Tipo (Type Constraints): Restringem tipos genéricos a protocolos ou classes específicas
  • Tipos Associados (Associated Types): Usados em protocolos para definir tipos de placeholder
// Função genérica
func trocar<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Tipo genérico
struct Pilha<Elemento> {
    private var itens: [Elemento] = []
    
    mutating func push(_ item: Elemento) {
        itens.append(item)
    }
    
    mutating func pop() -> Elemento? {
        return itens.popLast()
    }
}

// Restrição de tipo
func encontrarIndice<T: Equatable>(de valorParaEncontrar: T, em array: [T]) -> Int? {
    for (indice, valor) in array.enumerated() {
        if valor == valorParaEncontrar {
            return indice
        }
    }
    return nil
}

Raridade: Comum Dificuldade: Média


3. Explique a diferença entre closures escaping e non-escaping.

Resposta:

  • Non-escaping (padrão): A closure é executada antes que a função retorne. O compilador pode otimizar melhor.
  • Escaping (@escaping): A closure sobrevive à função (armazenada em uma propriedade, chamada assincronamente). Deve capturar explicitamente self.
class GerenciadorDeRede {
    var completionHandlers: [() -> Void] = []
    
    // Closure escaping - armazenada para mais tarde
    func buscarDados(completion: @escaping (Data?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, _ in
            completion(data)  // Chamada depois que a função retorna
        }.resume()
    }
    
    // Closure non-escaping - executada imediatamente
    func processarDados(_ data: Data, transform: (Data) -> String) -> String {
        return transform(data)  // Chamada antes que a função retorne
    }
}

Raridade: Comum Dificuldade: Média


4. Qual é a diferença entre map, flatMap e compactMap?

Resposta: Estas são funções de ordem superior para transformar coleções:

  • map: Transforma cada elemento e retorna um array de resultados
  • compactMap: Como map, mas filtra valores nil
  • flatMap: Achata arrays aninhados em um único array
let numeros = [1, 2, 3, 4, 5]

// map
let dobrados = numeros.map { $0 * 2 }
// [2, 4, 6, 8, 10]

// compactMap - remove nil
let strings = ["1", "2", "tres", "4"]
let numerosValidos = strings.compactMap { Int($0) }
// [1, 2, 4]

// flatMap - achata arrays
let aninhado = [[1, 2], [3, 4], [5]]
let achatado = aninhado.flatMap { $0 }
// [1, 2, 3, 4, 5]

Raridade: Comum Dificuldade: Fácil


5. Explique Property Wrappers em Swift.

Resposta: Property wrappers adicionam uma camada de separação entre o código que gerencia como uma propriedade é armazenada e o código que define uma propriedade.

  • Exemplos Built-in: @State, @Published, @AppStorage em SwiftUI
  • Wrappers Customizados: Defina comportamentos de propriedade reutilizáveis
@propertyWrapper
struct Capitalizado {
    private var valor: String = ""
    
    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized }
    }
}

struct Usuario {
    @Capitalizado var primeiroNome: String
    @Capitalizado var ultimoNome: String
}

var usuario = Usuario()
usuario.primeiroNome = "joão"
print(usuario.primeiroNome)  // "João"

// Wrapper UserDefaults
@propertyWrapper
struct UserDefault<T> {
    let chave: String
    let valorPadrao: T
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: chave) as? T ?? valorPadrao }
        set { UserDefaults.standard.set(newValue, forKey: chave) }
    }
}

struct Configurações {
    @UserDefault(key: "nomeDeUsuario", defaultValue: "Convidado")
    static var nomeDeUsuario: String
}

Raridade: Média Dificuldade: Difícil


6. O que é o tipo Result e como ele é usado?

Resposta: Result é um enum que representa sucesso ou falha, tornando o tratamento de erros mais explícito.

  • Definição: enum Result<Success, Failure: Error>
  • Benefícios: Tratamento de erros type-safe, contratos de API mais claros, melhor do que funções que lançam erros para código assíncrono
enum ErroDeRede: Error {
    case urlInvalida
    case semDados
    case erroDeDecodificação
}

func buscarUsuario(id: Int, completion: @escaping (Result<Usuario, ErroDeRede>) -> Void) {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        completion(.failure(.urlInvalida))
        return
    }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else {
            completion(.failure(.semDados))
            return
        }
        
        do {
            let usuario = try JSONDecoder().decode(Usuario.self, from: data)
            completion(.success(usuario))
        } catch {
            completion(.failure(.erroDeDecodificação))
        }
    }.resume()
}

// Uso
buscarUsuario(id: 1) { result in
    switch result {
    case .success(let usuario):
        print("Usuário: \(usuario.nome)")
    case .failure(let error):
        print("Erro: \(error)")
    }
}

Raridade: Comum Dificuldade: Média


Padrões de Arquitetura (5 Questões)

7. Explique o padrão MVVM (Model-View-ViewModel).

Resposta: MVVM separa a lógica da UI da lógica de negócios, tornando o código mais testável e manutenível.

Loading diagram...
  • Model: Dados e lógica de negócios
  • View: UI (UIViewController, SwiftUI View)
  • ViewModel: Lógica de apresentação, transforma dados do modelo para a view
  • Benefícios: Testável (ViewModel não tem dependências da UI), ViewModels reutilizáveis, separação clara de responsabilidades
// Model
struct Usuario {
    let id: Int
    let primeiroNome: String
    let ultimoNome: String
}

// ViewModel
class UserViewModel {
    private let usuario: Usuario
    
    var nomeCompleto: String {
        "\(usuario.primeiroNome) \(usuario.ultimoNome)"
    }
    
    var textoDeExibição: String {
        "Usuário #\(usuario.id): \(nomeCompleto)"
    }
    
    init(usuario: Usuario) {
        self.usuario = usuario
    }
}

// View
class UserViewController: UIViewController {
    private let viewModel: UserViewModel
    private let label = UILabel()
    
    init(viewModel: UserViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = viewModel.textoDeExibição
    }
}

Raridade: Muito Comum Dificuldade: Média


8. O que é o padrão Coordinator e por que usá-lo?

Resposta: O padrão Coordinator separa a lógica de navegação dos view controllers.

  • Problema: View Controllers Massivos com lógica de navegação misturada com lógica de UI
  • Solução: Coordinators gerenciam o fluxo de navegação
  • Benefícios: View controllers reutilizáveis, navegação testável, fluxo de aplicativo claro
protocol Coordinator {
    var navigationController: UINavigationController { get }
    func start()
}

class AppCoordinator: Coordinator {
    let navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        mostrarLogin()
    }
    
    func mostrarLogin() {
        let loginVC = LoginViewController()
        loginVC.coordinator = self
        navigationController.pushViewController(loginVC, animated: true)
    }
    
    func mostrarHome() {
        let homeVC = HomeViewController()
        homeVC.coordinator = self
        navigationController.pushViewController(homeVC, animated: true)
    }
}

Raridade: Média Dificuldade: Difícil


9. Explique Injeção de Dependência em iOS.

Resposta: Injeção de Dependência é um padrão de design onde as dependências são fornecidas a um objeto em vez de serem criadas internamente.

  • Benefícios: Testabilidade (injetar mocks), flexibilidade, acoplamento fraco
  • Tipos:
    • Injeção de Construtor: Passar dependências via inicializador (mais comum)
    • Injeção de Propriedade: Definir dependências após a inicialização
    • Injeção de Método: Passar dependências como parâmetros de método
// Protocolo para abstração
protocol ServicoDeRede {
    func buscarDados(completion: @escaping (Result<Data, Error>) -> Void)
}

// Implementação concreta
class URLSessionServicoDeRede: ServicoDeRede {
    func buscarDados(completion: @escaping (Result<Data, Error>) -> Void) {
        // Chamada de rede real
    }
}

// Mock para testes
class MockServicoDeRede: ServicoDeRede {
    func buscarDados(completion: @escaping (Result<Data, Error>) -> Void) {
        completion(.success(Data()))
    }
}

// ViewModel com injeção de dependência
class UserViewModel {
    private let servicoDeRede: ServicoDeRede
    
    // Injeção de construtor
    init(servicoDeRede: ServicoDeRede) {
        self.servicoDeRede = servicoDeRede
    }
    
    func carregarUsuarios() {
        servicoDeRede.buscarDados { result in
            // Lidar com o resultado
        }
    }
}

// Uso
let viewModel = UserViewModel(servicoDeRede: URLSessionServicoDeRede())

// Testes
let testViewModel = UserViewModel(servicoDeRede: MockServicoDeRede())

Raridade: Comum Dificuldade: Média


10. O que é o padrão Repository?

Resposta: O padrão Repository abstrai a lógica de acesso a dados, fornecendo uma API limpa para operações de dados.

  • Benefícios: Lógica de dados centralizada, fácil de trocar fontes de dados (API, banco de dados, cache), testável
  • Implementação: Repository coordena entre múltiplas fontes de dados
protocol UserRepository {
    func getUser(id: Int) async throws -> Usuario
    func saveUser(_ user: Usuario) async throws
}

class UserRepositoryImpl: UserRepository {
    private let apiService: APIService
    private let cacheService: CacheService
    
    init(apiService: APIService, cacheService: CacheService) {
        self.apiService = apiService
        self.cacheService = cacheService
    }
    
    func getUser(id: Int) async throws -> Usuario {
        // Verificar cache primeiro
        if let cachedUser = try? await cacheService.getUser(id: id) {
            return cachedUser
        }
        
        // Buscar da API
        let user = try await apiService.fetchUser(id: id)
        
        // Atualizar cache
        try? await cacheService.saveUser(user)
        
        return user
    }
    
    func saveUser(_ user: Usuario) async throws {
        try await apiService.updateUser(user)
        try await cacheService.saveUser(user)
    }
}

Raridade: Média Dificuldade: Média


11. Explique as diferenças entre MVC, MVP e MVVM.

Resposta:

  • MVC (Model-View-Controller):
    • Padrão padrão da Apple
    • Controller medeia entre Model e View
    • Problema: View Controllers Massivos
  • MVP (Model-View-Presenter):
    • Presenter gerencia toda a lógica da UI
    • View é passiva (apenas exibe dados)
    • Melhor testabilidade que MVC
  • MVVM (Model-View-ViewModel):
    • ViewModel expõe fluxos de dados
    • View se liga ao ViewModel
    • Melhor para programação reativa (Combine, RxSwift)

Raridade: Comum Dificuldade: Difícil


Desempenho & Otimização (5 Questões)

12. Como você otimiza o desempenho de table view e collection view?

Resposta: Múltiplas estratégias melhoram o desempenho de rolagem:

  1. Reutilização de Células: Use dequeueReusableCell corretamente
  2. Evite Operações Pesadas: Não execute cálculos caros em cellForRowAt
  3. Otimização de Imagens:
    • Redimensione imagens para o tamanho de exibição
    • Use threads de fundo para processamento de imagem
    • Armazene em cache imagens decodificadas
  4. Pré-busca: Implemente UITableViewDataSourcePrefetching
  5. Cache de Altura: Armazene em cache as alturas de células calculadas
  6. Evite Transparência: Views opacas renderizam mais rápido
class ImageTableViewCell: UITableViewCell {
    static let identifier = "ImageCell"
    private let customImageView = UIImageView()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        customImageView.image = nil  // Limpar imagem antiga
    }
}

// No view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: ImageTableViewCell.identifier, for: indexPath) as! ImageTableViewCell
    
    // Carregar imagem assincronamente
    DispatchQueue.global(qos: .userInitiated).async {
        let image = self.loadAndResizeImage(at: indexPath)
        DispatchQueue.main.async {
            cell.imageView?.image = image
        }
    }
    
    return cell
}

Raridade: Muito Comum Dificuldade: Média


13. Explique instruments e como você os usa para profiling de desempenho.

Resposta: Instruments é a ferramenta de análise de desempenho do Xcode.

  • Instruments Comuns:
    • Time Profiler: Identifica código intensivo em CPU
    • Allocations: Rastreia alocações de memória e vazamentos
    • Leaks: Detecta vazamentos de memória
    • Network: Monitora a atividade de rede
    • Energy Log: Analisa o uso da bateria
  • Fluxo de Trabalho:
    1. Fazer profiling do aplicativo (Cmd+I)
    2. Escolher instrumento
    3. Gravar e interagir com o aplicativo
    4. Analisar a árvore de chamadas e a linha do tempo
    5. Identificar gargalos

Raridade: Comum Dificuldade: Média


14. Como você detecta e corrige vazamentos de memória?

Resposta: Vazamentos de memória ocorrem quando objetos não são desalocados quando não são mais necessários.

  • Causas Comuns:
    • Ciclos de retenção (ciclos de referência fortes)
    • Closures capturando self fortemente
    • Delegates não marcados como weak
  • Detecção:
    • Ferramenta Instruments Leaks
    • Memory Graph Debugger no Xcode
    • Observar o aumento do uso de memória
  • Correções:
    • Use weak ou unowned para delegates
    • Use [weak self] ou [unowned self] em closures
    • Quebrar ciclos de retenção
class ViewController: UIViewController {
    var closure: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // RUIM - ciclo de retenção
        closure = {
            self.view.backgroundColor = .red
        }
        
        // BOM - weak self
        closure = { [weak self] in
            self?.view.backgroundColor = .red
        }
        
        // BOM - unowned self (se self sempre existe)
        closure = { [unowned self] in
            self.view.backgroundColor = .red
        }
    }
}

Raridade: Muito Comum Dificuldade: Média


15. Quais técnicas você usa para otimização de inicialização do aplicativo?

Resposta: Uma inicialização mais rápida do aplicativo melhora a experiência do usuário:

  1. Carregamento Preguiçoso (Lazy Loading): Inicialize objetos apenas quando necessário
  2. Reduzir Carregamento de Dylib: Minimize bibliotecas dinâmicas
  3. Otimizar application:didFinishLaunching:
    • Mova o trabalho não crítico para o fundo
    • Adie a inicialização pesada
  4. Tamanho do Binário: Binário menor carrega mais rápido
  5. Evite Operações Pesadas: Não bloqueie a thread principal
  6. Medir: Use o template App Launch do Instruments
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Crítico: Configurar janela e view controller raiz
    setupWindow()
    
    // Adiar trabalho não crítico
    DispatchQueue.main.async {
        self.setupAnalytics()
        self.setupCrashReporting()
    }
    
    // Trabalho de fundo
    DispatchQueue.global(qos: .background).async {
        self.preloadData()
    }
    
    return true
}

Raridade: Comum Dificuldade: Média


16. Como você lida com o cache e o carregamento de imagens?

Resposta: O tratamento eficiente de imagens é crucial para o desempenho:

  • Estratégias:
    • Cache de Memória: Acesso rápido, tamanho limitado
    • Cache de Disco: Persistente, maior capacidade
    • Download: Buscar da rede
  • Bibliotecas: SDWebImage, Kingfisher (gerenciam o cache automaticamente)
  • Implementação Customizada:
class ImageCache {
    static let shared = ImageCache()
    
    private let memoryCache = NSCache<NSString, UIImage>()
    private let fileManager = FileManager.default
    private let cacheDirectory: URL
    
    private init() {
        cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("ImageCache")
        try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
    }
    
    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        let key = url.absoluteString as NSString
        
        // Verificar cache de memória
        if let cachedImage = memoryCache.object(forKey: key) {
            completion(cachedImage)
            return
        }
        
        // Verificar cache de disco
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        if let diskImage = UIImage(contentsOfFile: fileURL.path) {
            memoryCache.setObject(diskImage, forKey: key)
            completion(diskImage)
            return
        }
        
        // Download
        URLSession.shared.dataTask(with: url) { data, _, _ in
            guard let data = data, let image = UIImage(data: data) else {
                completion(nil)
                return
            }
            
            // Salvar em caches
            self.memoryCache.setObject(image, forKey: key)
            try? data.write(to: fileURL)
            
            DispatchQueue.main.async {
                completion(image)
            }
        }.resume()
    }
}

Raridade: Comum Dificuldade: Difícil


Concorrência & Programação Assíncrona (4 Questões)

17. Explique async/await em Swift.

Resposta: Modelo de concorrência moderno do Swift introduzido no Swift 5.5.

  • Benefícios: Sintaxe mais limpa que completion handlers, tratamento de erros mais fácil, segurança de thread imposta pelo compilador
  • Palavras-chave:
    • async: Marca uma função que pode suspender
    • await: Marca um ponto de suspensão
    • Task: Cria um novo contexto assíncrono
    • actor: Tipo de referência thread-safe
// Maneira antiga com completion handlers
func buscarUsuario(id: Int, completion: @escaping (Result<Usuario, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        // Lidar com o resultado
    }.resume()
}

// Nova maneira com async/await
func buscarUsuario(id: Int) async throws -> Usuario {
    let (data, _) = try await URLSession.shared.data(from: url)
    let user = try JSONDecoder().decode(Usuario.self, from: data)
    return user
}

// Uso
Task {
    do {
        let user = try await buscarUsuario(id: 1)
        print(user.nome)
    } catch {
        print("Erro: \(error)")
    }
}

// Execução paralela
async let user1 = buscarUsuario(id: 1)
async let user2 = buscarUsuario(id: 2)
let users = try await [user1, user2]

Raridade: Muito Comum Dificuldade: Difícil


18. O que são Actors em Swift?

Resposta: Actors são tipos de referência que protegem seu estado mutável de data races.

  • Segurança de Thread: Apenas uma tarefa pode acessar o estado mutável do actor por vez
  • Sincronização Automática: O compilador impõe acesso seguro
  • Main Actor: Actor especial para atualizações da UI
actor BankAccount {
    private var balance: Double = 0
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
    
    func getBalance() -> Double {
        return balance
    }
}

// Uso
let account = BankAccount()

Task {
    await account.deposit(amount: 100)
    let balance = await account.getBalance()
    print("Balance: \(balance)")
}

// Main Actor para atualizações da UI
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    func loadData() async {
        let newData = await fetchDataFromAPI()
        // Automaticamente na thread principal
        self.data = newData
    }
}

Raridade: Média Dificuldade: Difícil


19. Explique o framework Combine.

Resposta: Combine é o framework de programação reativa da Apple.

  • Conceitos Principais:
    • Publisher: Emite valores ao longo do tempo
    • Subscriber: Recebe valores
    • Operator: Transforma valores
  • Benefícios: Declarativo, composable, operadores built-in
  • Casos de Uso: Networking, tratamento de entrada do usuário, data binding
import Combine

class UserViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var users: [Usuario] = []
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .filter { !$0.isEmpty }
            .flatMap { query in
                self.searchUsers(query: query)
            }
            .receive(on: DispatchQueue.main)
            .sink { [weak self] users in
                self?.users = users
            }
            .store(in: &cancellables)
    }
    
    func searchUsers(query: String) -> AnyPublisher<[Usuario], Never> {
        // Retornar publisher
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [Usuario].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .eraseToAnyPublisher()
    }
}

Raridade: Comum Dificuldade: Difícil


20. Qual é a diferença entre filas Serial e Concurrent?

Resposta: Filas de despacho executam tarefas serialmente ou concorrentemente:

  • Fila Serial: Executa uma tarefa por vez em ordem FIFO. As tarefas esperam que a tarefa anterior seja concluída.
  • Fila Concorrente: Executa várias tarefas simultaneamente. As tarefas começam em ordem FIFO, mas podem terminar em qualquer ordem.
  • Fila Principal (Main Queue): Fila serial especial para atualizações da UI
// Fila serial
let serialQueue = DispatchQueue(label: "com.app.serial")
serialQueue.async { print("Tarefa 1") }
serialQueue.async { print("Tarefa 2") }
// Saída: Tarefa 1, Tarefa 2 (sempre em ordem)

// Fila concorrente
let concurrentQueue = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
concurrentQueue.async { print("Tarefa A") }
concurrentQueue.async { print("Tarefa B") }
// Saída: Tarefa A, Tarefa B OU Tarefa B, Tarefa A (ordem não garantida)

// Barreira para operações de escrita
concurrentQueue.async(flags: .barrier) {
    // Acesso exclusivo - nenhuma outra tarefa é executada simultaneamente
    print("Operação de escrita")
}

Raridade: Comum Dificuldade: Média


Core Data & Persistência (3 Questões)

21. Explique a arquitetura do Core Data e seus principais componentes.

Resposta: Core Data é o framework de gráfico de objetos e persistência da Apple.

Loading diagram...
  • NSManagedObjectModel: Definição de esquema (entidades, atributos, relacionamentos)
  • NSPersistentStoreCoordinator: Coordena entre o contexto e o armazenamento
  • NSManagedObjectContext: Memória de trabalho para objetos (como um rascunho)
  • NSPersistentStore: Armazenamento real (SQLite, binário, na memória)

Raridade: Comum Dificuldade: Média


22. Como você lida com concorrência no Core Data?

Resposta: Os contextos do Core Data não são thread-safe. Use padrões de concorrência adequados:

  • Tipos de Contexto:
    • Contexto da Fila Principal (Main Queue Context): Para operações da UI
    • Contexto da Fila Privada (Private Queue Context): Para operações em segundo plano
  • Melhores Práticas:
    • Nunca passe objetos gerenciados entre threads
    • Use perform ou performAndWait para operações de contexto
    • Passe IDs de objetos entre contextos
class CoreDataManager {
    let persistentContainer: NSPersistentContainer
    
    var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
        persistentContainer.performBackgroundTask { context in
            block(context)
            
            if context.hasChanges {
                try? context.save()
            }
        }
    }
}

// Uso
coreDataManager.performBackgroundTask { context in
    let user = Usuario(context: context)
    user.nome = "João"
    // O contexto salva automaticamente
}

Raridade: Média Dificuldade: Difícil


23. O que é NSFetchedResultsController e quando você o usa?

Resposta: NSFetchedResultsController gerencia eficientemente os resultados do Core Data para table/collection views.

  • Benefícios:
    • Rastreamento automático de alterações
    • Eficiência de memória (batching)
    • Gerenciamento de seção
    • Atualizações automáticas da UI
  • Caso de Uso: Exibição de objetos Core Data em table/collection views
class UsersViewController: UITableViewController, NSFetchedResultsControllerDelegate {
    var fetchedResultsController: NSFetchedResultsController<Usuario>!
    
    func setupFetchedResultsController() {
        let fetchRequest: NSFetchRequest<Usuario> = Usuario.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        
        fetchedResultsController = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil
        )
        
        fetchedResultsController.delegate = self
        try? fetchedResultsController.performFetch()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fetchedResultsController.sections?[section].numberOfObjects ?? 0
    }
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.reloadData()
    }
}

Raridade: Média Dificuldade: Média


Design de Sistemas & Melhores Práticas (2 Questões)

24. Como você projetaria um aplicativo móvel offline-first?

Resposta: Aplicativos offline-first funcionam sem internet e sincronizam quando conectados.

  • Arquitetura:
    • Banco de dados local como fonte da verdade (Core Data, Realm)
    • Camada de sincronização para reconciliar com o servidor
    • Estratégia de resolução de conflitos
  • Estratégias:
    • UI Otimista: Mostrar alterações imediatamente, sincronizar em segundo plano
    • Operações em Fila: Armazenar solicitações com falha, tentar novamente quando online
    • Timestamp/Versão: Rastrear a atualização dos dados
  • Desafios:
    • Resolução de conflitos (última gravação vence, mesclar, escolha do usuário)
    • Consistência de dados
    • Limites de armazenamento

Raridade: Média Dificuldade: Difícil


25. Quais são suas estratégias para segurança de aplicativos em iOS?

Resposta: Múltiplas camadas protegem o aplicativo e os dados do usuário:

  1. Proteção de Dados:
    • Keychain para dados confidenciais (senhas, tokens)
Newsletter subscription

Dicas de carreira semanais que realmente funcionam

Receba as últimas ideias diretamente na sua caixa de entrada

Crie um Currículo que Te Contrate 60% Mais Rápido

Em minutos, crie um currículo personalizado e compatível com ATS comprovado para conseguir 6 vezes mais entrevistas.

Crie um currículo melhor

Compartilhar esta publicação

Supere a Taxa de Rejeição de 75% do ATS

3 em cada 4 currículos nunca chegam a um olho humano. Nossa otimização de palavras-chave aumenta sua taxa de aprovação em até 80%, garantindo que os recrutadores realmente vejam seu potencial.