Guia de Entrevista iOS: 12 perguntas técnicas para um developer iOS

Tudo que você deve perguntar antes de contratar um developer iOS (ou dev iOS) está aqui, neste guia. Boa entrevista!

1

PERGUNTA: Considere o seguinte construtor UITableViewCell

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier

Qual é o propósito do reuseIdentifier? Qual é a vantagem de defini-lo como um valor diferente de nil?

RESPOSTA: O reuseIdentifier é usado para agrupar linhas semelhantes em uma UITableView. Ou seja, linhas que são diferentes apenas nos conteúdos, mas que, por outro lado, possuem layouts similares.

A UITableView normalmente aloca apenas o suficiente de objetos da UITableViewCell para exibir o conteúdo visível na tabela. Se o reuseIdentifier está definido para um valor diferente de nil, quando a exibição da tabela for rolada para baixo, a UITableView tentará primeiro reutilizar uma UITableViewCell já atribuída com o mesmo reuseIdentifier. Se o reuseIdentifier não estiver definido, a UITableView será forçada a alocar novos objetos da UITableViewCell para cada novo item que aparecer, resultando em um desempenho deficitário.


2

PERGUNTA: Quais são as diferentes maneiras para especificar o layout de elementos em uma UIView?

RESPOSTA: Eis algumas maneiras comuns para especificar o layout de elementos em uma UIView:

  • Ao utilizar o InterfaceBuilder, é possível adicionar um arquivo XIB aos elementos de layout dentro do seu projeto e depois carregar o XIB no código do aplicativo (automaticamente, com base nas convenções de nomenclatura, ou manualmente). Além disso, usando o InterfaceBuilder, você pode criar um storyboard para o seu aplicativo.
  • Você pode usar o seu próprio código para utilizar um NSLayoutConstraints para ter os elementos visualizados de forma organizada pelo o Auto Layout.
  • Você pode criar CGRect descrevendo as coordenadas exatas para cada elemento e passá-las para os métodos de – (id)initWithFrame:(CGRect)frame das UIView.

3

Pergunta: Qual é a diferença entre as propriedades atomic e nonatomic? Qual é o padrão para as propriedades sintetizadas? Quando você deve usar uma ou a outra?

Resposta: Propriedades especificadas como atomic garantem sempre retornar um objeto totalmente inicializado. Este também é o estado padrão para propriedades sintetizadas, portanto, enquanto é uma boa prática especificar atomic para remover as chances de criar alguma confusão, caso isto não seja feito, as suas propriedades ainda serão atomic. Esta garantia das propriedades atomic vem acompanhada de um custo de desempenho. Se você possui uma propriedade para a qual você sabe que recuperar um valor não inicializado não é um risco (por exemplo, se todos os acessos à propriedade já estão sincronizados através de outros meios), em seguida, definindo-a como nonatomic, você pode ganhar um pouco de desempenho.


4

Pergunta: Imagine que você quis gravar o horário em que o seu aplicativo foi lançado e para isto você criou uma classe que definiu uma variável global no cabeçalho: NSString *startTime;. Então, na implementação da classe, você definiu a variável da seguinte forma:

+ (void)initialize {    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];    [formatter setDateStyle:NSDateFormatterNoStyle];    [formatter setTimeStyle:NSDateFormatterMediumStyle];    startTime = [formatter stringFromDate:[NSDate date]];}

Se em seguida você adicionou a seguinte linha

application:didFinishLaunchingWithOptions:

ao método no seu AppDelegate:

NSLog(@”Application was launched at: %@”, startTime);

o que você espera que estará registrado no console do depurador? Como você corrigir isso para que funcione da forma correta?

Resposta: O console do depurador irá registrar Application was launched at: (null) porque a variável global startTime ainda não foi definida. O método initialize de uma classe de Objective-C só é chamado logo antes que a primeira mensagem é enviada para esta classe. Por outro lado, quaisquer métodos load definidos pelas classes de Objective-C serão chamados assim que a classe for adicionada ao tempo de execução de Objective-C.

Há duas resoluções diferentes para este problema.

Resolução 1: Mudando initialize para load irá obter o resultado desejado (mas cuidado ao fazer muito no load, pois pode  aumentar o tempo de carregamento do seu aplicativo).

Resolução 2 (graças ao comentário do John): criar um outro método de classe na sua classe criada. Vamos chamar este novo método de getStartTime e tudo que ele irá fazer é retornar o nosso objeto global startTime.

Agora, mudamos a nossa linha NSLog para:

NSLog(@”Application was launched at: %@”, [OurCreatedClass getStartTime]);

Como nós estamos enviando uma mensagem aos nossos OurCreatedClass, a sua inicialização será chamada, definindo o startTime. O método getStartTime será, então, chamado e retornará o horário de início!


5

Pergunta: Considere o seguinte código:

#import “TTAppDelegate.h” @interface TTParent : NSObject @property (atomic) NSMutableArray *children; @end @implementation [email protected] @interface TTChild : NSObject @property (atomic) TTParent *parent; @end @implementation [email protected] @implementation TTAppDelegate – (BOOL)application:(UIApplication *)application  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    TTParent *parent = [[TTParent alloc] init];    parent.children = [[NSMutableArray alloc] init];    for (int i = 0; i < 10; i++) {        TTChild *child = [[TTChild alloc] init];        child.parent = parent;        [parent.children addObject:child];    }    return YES;}@end

Qual é o bug neste código e quais as suas consequências? Como você o consertaria?

Resposta: Este é um exemplo clássico de um ciclo de retenção. O parente irá manter a matriz child e a matriz irá reter cada objeto TTChild adicionado a ela. Cada objeto child que é criado também irá manter o seu parent, de modo que, mesmo após a última referência externa para o parent estiver desmarcada, a contagem de retenção no parent vai ser maior que zero e não vai ser removida.

Para corrigir isto, a referência da child com o parent precisa ser declarada como uma referência weak como:

@interface TTChild : NSObject @property (weak, atomic) TTParent *parent; @end

Uma referência fraca não irá incrementar a contagem de retenção do alvo e irá ser definida para nil quando o alvo estiver finalmente destruído.

Nota:

Para uma variação mais complicada desta questão, você poderia considerar dois itens que mantêm referências entre um e outro em uma matriz. Neste caso, você terá de substituir o NSArray / NSMutableArray com um NSPointerArray declarado como:

NSPointerArray *weakRefArray = [[NSPointerArray alloc] initWithOptions: NSPointerFunctionsWeakMemory];

pois o NSArray normalmente armazena uma forte referência em relação aos seus membros.


6

Pergunta: Identifique o bug no código abaixo:

@interface TTWaitController : UIViewController @property (strong, nonatomic) UILabel *alert; @end @implementation TTWaitController – (void)viewDidLoad{    CGRect frame = CGRectMake(20, 200, 200, 20);    self.alert = [[UILabel alloc] initWithFrame:frame];    self.alert.text = @”Please wait 10 seconds…”;    self.alert.textColor = [UIColor whiteColor];    [self.view addSubview:self.alert];     NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];    [waitQueue addOperationWithBlock:^{        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];        self.alert.text = @”Thanks!”;    }];} @end @implementation TTAppDelegate – (BOOL)application:(UIApplication *)application  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    self.window.rootViewController = [[TTWaitController alloc] init];    [self.window makeKeyAndVisible];    return YES;}

Como você pode corrigir esse problema?

Resposta: Quando o código acima despacha trabalho utilizando o addOperationWithBlock como método do NSOperationQueue, não há garantia de que o bloco sendo enfileirado será executado no thread principal. Note que o conteúdo do UILabel está sendo atualizado dentro do corpo do bloco. Atualizações de UI que não são executadas no thread principal podem causar um comportamento indefinido. O código pode parecer funcionar corretamente por um longo período de tempo antes de qualquer coisa der errado, mas atualizações de UI devem sempre acontecer no thread principal.

A maneira mais fácil de corrigir o potencial problema é mudando o corpo do bloco, de forma que a atualização é re-enfileirada utilizando a fila do principal thread da seguinte maneira:

[waitQueue addOperationWithBlock:^{    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];    [[NSOperationQueue mainQueue] addOperationWithBlock:^{        self.alert.text = @”Thanks!”;    }];}];


7

Pergunta: Qual é a diferença entre um “app ID” e um “bundle ID” e para que cada um é utilizado?

Resposta: Um App ID é uma string de duas partes utilizada para identificar um ou mais aplicativos de uma única equipe de desenvolvimento. A string consiste em um ID do Time e em um Bundle ID, com um ponto (.) separando as duas partes. O ID do Time é fornecido pela Apple e é único para cada equipe de desenvolvimento, enquanto o Bundle ID é fornecido pelo desenvolvedor para corresponder ao Bundle ID de um único aplicativo ou um conjunto de Bundle IDs para um grupo de aplicativos.

Pelo fato da maioria das pessoas pensar no App ID como uma string, eles o consideram intercambiável com o Bundle ID. Isto acontece porque como o App ID é criado no Member Center, você utiliza somente o prefixo do App ID que corresponde ao Bundle ID.

O Bundle ID define unicamente cada aplicativo. Ele é especificado no Xcode. Um projeto Xcode pode ter vários Targets e, portanto, produzir diversos aplicativos. Um caso comum é um aplicativo que possui ambas as versões lite/free e pro/full ou é rotulado de várias maneiras.


8

Perguntas: O que são referências “fortes” e “fracas”? Porque elas são importantes e como podem ser usadas para ajudar no gerenciamento de memória de controle e evitar vazamentos de memória?

Respostas: Por padrão, qualquer variável que aponte para outro objeto faz isto com o que é chamado de referência “forte”. Um ciclo de retenção ocorre quando dois ou mais objetos têm fortes referências recíprocas (ou seja, fortes referências um para o outro). Quaisquer destes objetos nunca serão destruídos pelo ARC (iOS ‘Automatic Reference Counting). Mesmo que todos os outros objetos no aplicativo liberem a posse destes objetos, esses objetos (e, por sua vez, todos os objetos que os referenciam) continuarão a existir em virtude destas fortes referências mútuas. Portanto, resultando em um vazamento de memória.

Desta forma, fortes referências recíprocas entre objetos devem ser evitadas na medida do possível. No entanto, quando são necessárias, uma maneira de evitar este tipo de vazamento de memória é empregar referências fracas. Declarando uma das duas referências como weak irá quebrar o ciclo de retenção e, assim, evitar o vazamento de memória.

Para decidir qual das duas referências deve ser a fraca, pense nos objetos do ciclo de retenção estando em uma relação parent-child. Nesta relação, o parent deve manter uma referência forte (isto é, a posse) com o child, mas o child não deve manter uma referência forte (isto é, a posse) com o parente.

Por exemplo, se você tem objetos Employer e Employee que fazem referência um ao outro, você provavelmente quer manter uma forte referência do Employer para o objeto Employee, mas uma referência fraca do Employee para o Employer.


9

Pergunta: Descreva o contexto de objetos gerenciados e a sua funcionalidade.

Resposta: Um contexto de objetos gerenciados (representado pela instância NSManagedObjectContext) é basicamente um “scratch pad” temporário em um aplicativo para (presumivelmente) uma coleção relacionada de objetos. Estes objetos representam coletivamente uma visão consistente internamente de um ou mais armazenamentos persistentes. Uma única instância de objeto gerenciado existe em somente um contexto, mas diversas cópias de um objeto podem existir em diferentes contextos.

Você pode pensar em um contexto de objetos gerenciados como um scratch pad inteligente. Quando você buscar objetos a partir de um armazenamento persistente, você traz cópias temporárias para o scratch pad onde eles formam um objeto gráfico (ou um conjunto de objetos gráficos). Desta forma, você pode modificar estes objetos da maneira que preferir. A menos que você realmente salve estas alterações, o armazenamento persistente permanece inalterado.

 

As funcionalidades contexto de objetos gerenciados incluem:

Gerenciamento do ciclo de vida. O contexto fornece validação, manipulação de relação inversa e desfazer/refazer. Através de um contexto você pode recuperar ou “buscar” objetos a partir de um armazenamento persistente, efetuar alterações nestes objetos e, em seguida, descartar as alterações ou salva-las no armazenamento persistente. O contexto é responsável por observar por mudanças nos seus objetos e manter um gerenciador de reversão de ações para que você possa ter um controle mais detalhado sobre desfazer e refazer ações. Você pode inserir novos objetos e excluir aqueles que você buscou e salvar estas modificações no armazenamento persistente.

Notificações. Um contexto envia notificações em vários pontos que podem opcionalmente ser monitoradas em qualquer lugar no seu aplicativo.

Simultaneidade. O Core Data utiliza confinamento de thread ou de fila serializada para proteger objetos gerenciados e contextos de objetos gerenciados. No OS X v10.7 e posterior e iOS v5.0 e posterior, ao criar um contexto, você pode especificar o padrão de simultaneidade com o qual você vai usá-lo utilizando initWithConcurrencyType:.

Para mais informações, consulte o iOS Developer Library Core Data Basics ou NSManagedObjectContext reference.


10

Pergunta: Compare e contraste as diferentes maneiras de alcançar a simultaneidade no OS X e no iOS.

Resposta: Existem basicamente três maneiras de alcançar a simultaneidade em iOS:

  • threads
  • filas de despacho
  • filas de operação

A desvantagem de threads é que eles dificultam a criação de uma solução escalável para o desenvolvedor. Você tem que decidir quantos threads devem ser criados e ajustar este número de forma dinâmica à medida que as condições mudam. Além disso, o aplicativo assume a maior parte dos custos associados à criação e manutenção dos threads que são utilizados.

Portanto, para OS X e iOS é preferível ter uma abordagem de design assíncrona para resolver o problema de simultaneidade, ao invés de depender de threads.

Uma das tecnologias para iniciar tarefas de forma assíncrona é o Grand Central Dispatch (GCD), que relega a gestão de threads ao nível do sistema. Tudo o que o desenvolvedor tem a fazer é definir as tarefas a serem executadas e adicioná-las à fila de despacho apropriada. O GCD se encarrega de criar os threads necessários e de agendar as tarefas para serem executadas nestes threads.

Todas as filas de despacho são estruturas de dados FIFO (First-In, First-Out), de modo que as tarefas sempre irão começar na mesma ordem que foram adicionadas.

Uma fila de operação é o equivalente ao Cocoa de uma fila de despacho simultâneo e é implementada pela classe initWithConcurrencyType. Ao contrário das filas de despacho, filas de operações não estão limitadas à execução de tarefas em ordem FIFO e apoiam a criação de complexos gráficos de ordem de execução para as suas tarefas.


11

Pergunta: O resultado do código abaixo será “areEqual” ou “areNotEqual”? Explique a sua resposta.

NSString *firstUserName = @”nick”;NSString *secondUserName = @”nick”; if (firstUserName  == secondUserName){  NSLog(@”areEqual”);}else{  NSLog(@”areNotEqual”);}

Resposta: O resultado do código será “AreEqual”.

Embora se possa pensar que isto é óbvio, na verdade, não é. E aqui está o porquê:

Comparando valores de ponteiro equivale a verificar se eles apontam para o mesmo objeto. Ponteiros terão o mesmo valor se, e somente se, eles realmente apontarem para o mesmo objeto (ao passo que os ponteiros de objetos diferentes não terão o mesmo valor, mesmo que os objetos que são apontados tenham o mesmo valor).

No trecho de código acima, firstUserName e secondUserName estão apontados cada um para objetos de string diferentes. Pode-se facilmente assumir que eles estão apontando para objetos de string diferentes, apesar do fato de que os objetos que eles apontam possuírem o mesmo valor. No entanto, o compilador de iOS otimiza as referências a objetos de string que possuem o mesmo valor (ou seja, os reutiliza ao invés de alocar objetos de string idênticos de forma redundante), de modo que ambos os ponteiros estão, de fato, apontando para o mesmo endereço e, portanto, a condição é tida como verdadeira.


12

Pergunta: Liste e explique os diferentes tipos de Estados de Aplicativos iOS.

Resposta: Os diferentes tipos de estados de aplicativos iOS são:

Estado de não execução: O aplicativo não foi lançado ou estava sendo executado e foi interrompido pelo sistema.

Estado inativo: O aplicativo é executado em primeiro plano, mas não está recebendo eventos. (Embora, possa estar executando outro código.) Um aplicativo normalmente fica neste estado apenas de forma breve, pois em seguida vai para outro estado. A única vez que ele permanece inativo por qualquer período de tempo é quando o usuário bloqueia a tela ou o sistema solicita que o usuário responda algum evento (como uma chamada telefônica ou mensagem SMS).

Estado ativo: O aplicativo é executado no primeiro plano e está recebendo eventos. Este é o modo normal para aplicativos de primeiro plano.

Estado de fundo: O aplicativo está em segundo plano e executando o código. A maioria dos aplicativos entra neste estado de forma breve à caminho para ser suspenso. No entanto, um aplicativo que solicita um tempo de execução extra pode permanecer neste estado por algum tempo. Além disso, um aplicativo que está sendo lançado diretamente no fundo entra neste estado, ao invés do estado inativo.

Estado suspenso: Enquanto suspenso, o aplicativo permanece na memória, mas não executa qualquer código. Quando uma condição de pouca memória ocorre, o sistema pode purgar aplicativos suspensos, sem aviso prévio, para ter mais espaço para aplicativos no primeiro plano.


Encontre o melhor Developer iOS

Antes de chegar à entrevista, você precisa ter acesso aos melhores profissionais do mercado. Para encontrá-los, basta acessar Contratado.me