Este guia descreve as convenções de desenvolvimento da equipe de iOS do The New York Times. Sua contribução é bem-vinda em nossas issues, pull requests e tweets. Além disso, estamos contratando.
Agradecemos a todos os nossos contribuidores.
Seguem alguns dos documentos oficiais da Apple que informam a convenção padrão da linguagem. Caso algo não seja mencionado aqui, as seguintes referências podem ajudar:
- A linguagem de programação Objective-C
- Guia de fundamentos Cocoa
- Convenções de código Cocoa
- Guia de programação de aplicativos iOS
- Quando utilizar ponto
- Espaçamento
- Condicionais
- Tratamento de erros
- Métodos
- Variáveis
- Nomenclaturas
- Comentários
- Init e Dealloc
- Literais
- Funções CGRect
- Constantes
- Tipos enumerados
- Máscara de bits
- Propriedades privadas
- Nomenclatura de imagens
- Booleanos
- Singletons
- Imports
- Protocols
- Projeto no Xcode
Utilize o ponto sempre quando for acessar e alterar uma propriedade. Em todos os outros casos é recomendada a utilização dos colchetes.
Exemplo correto:
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;
Inadequado:
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;
- Idente utilizando 4 espaços, Nunca idente com tabulações. Certifique-se de configurar essa preferência no Xcode.
- Métodos e qualquer bloco de códico que utilize chaves (
if
/else
/switch
/while
etc.) devem sempre ser declarados na mesma linha. Feche o bloco em um nova linha.
Exemplo correto:
if (user.isHappy) {
// verdadeiro
}
else {
// falso
}
- Deve haver exatamente uma linha em branco entre os métodos para auxiliar na organização visual.
- Espaços em branco dentro dos métodos devem separar funcionalidades (embora muitas das vezes isso indique uma oportunidade para dividir o método em outros métodos menores). Em métodos com nomes longos ou verbosos, uma única linha em branco pode ser utilizada para prover separação visual antes do corpo do método.
@synthesize
e@dynamic
devem ser declarados em novas linhas.
Condicionais, como por exemplo if
, devem sempre utilizar chaves (mesmo em casos onde o corpo da condicional necessite apenas de uma linha) evitando assim esse tipo de erro. Esses erros incluem a adição de uma segunda linha e esperam que a mesma faça parte do bloco. Outra falha ainda maior pode ocorrer quando a linha que faz parte da condicional é comentada fazendo com que a próxima linha, involuntariamente, se torne parte da condicional. Além disso, esse estilo é mais consistente com todas as outras condicionais e, portanto, mais facilmente interpretado.
Exemplo correto:
if (!error) {
return success;
}
Inadequado:
if (!error)
return success;
or
if (!error) return success;
O operador ternário, ?
, deve ser utilizado apenas em caso onde sua aplicação facilita a interpretação e clareza visual do código. Ele deve ser aplicado quando somente uma condição é avaliada. A avaliação de várias condições é normalmente mais compreensível com uma instrução if
, ou refatorada em variáveis nomeadas.
Exemplo correto:
result = a > b ? x : y;
Inadequado:
result = a > b ? x = c > d ? c : d : y;
Quando os métodos retornarem um parâmetro de erro referenciado, deve-se tratar o valor retornado e não a variável de erro.
Exemplo correto:
NSError *error;
if (![self trySomethingWithError:&error]) {
// tratar o erro
}
Inadequado:
NSError *error;
[self trySomethingWithError:&error];
if (error) {
// tratar o erro
}
Algumas APIs da Apple armazenam valores ao parâmetro de erro sem existir um erro de fato (um exemplo seria armazenar o valor 'não existem erros'), por isso validar se a váriavel é nula pode ocasionar um falso negativo (e, posteriormente, falhas).
Na assinatura de um método, deve haver um espaço após o escopo (símbolo -
ou +
). Também deve existir um espaço entre os parâmetros dos métodos.
Examplo correto:
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
As variáveis devem ser nomeadas de forma descritiva, com o nome da variável comunicando claramente o que a variável é e também informações pertinentes que um programador precise para usar o valor corretamente.
Por exemplo:
NSString *title
: É sensato assumir que "title" é uma string.NSString *titleHTML
: Indica que o título pode conter HTML que precisa ser tratado para ser exibido. "HTML" é necessário para que o programador utilize essa variável efetivamente.NSAttributedString *titleAttributedString
: Um título, já formatado para ser exibido.AttributedString
dá uma dica que esse valor não é somente um título puro, e adicioná-lo pode ser uma escolha razoável dependendo do contexto.NSDate *now
: Não é necessário especificar melhor.NSDate *lastModifiedDate
:lastModified
somente pode ser ambíguo; dependendo do contexto, uma pessoa pode assumir diferentes tipos.NSURL *URL
vs.NSString *URLString
: Em situações onde um valor pode ser representada por diferentes classes, é normalmente útil remover essa ambiguidade no nome da variável.NSString *releaseDateString
: Outro exemplo onde um valor pode ser representado por outra classe, e o nome pode ajudar a remover a ambiguidade.
Variáveis com somente uma letra deve ser evitados, exceto para simples contadores em loops.
Os asteriscos, que indicam que um tipo de uma variável é um ponteiro, deve estar "anexado" ao nome da variável, por exemplo: NSString *text
, não NSString* text
ou NSString * text
, exceto em caso onde sejam aplicados a constantes (NSString * const NYTConstantString
).
As definições de propriedade devem ser utilizadas sempre que possível. O acesso direto a variáveis de instância deve ser evitado, com excessão de métodos inicializadores (init
, initWithCoder:
...), métodos dealloc
e métodos acessores (set
e get
). Para mais informações, veja a documentação da Apple sobre o uso de métodos acessores em métodos inicializadores e dealloc
.
Exemplo correto:
@interface NYTSection: NSObject
@property (nonatomic) NSString *headline;
@end
Inadequado:
@interface NYTSection : NSObject {
NSString *headline;
}
Quando se trata de qualificadores de variáveis introduzidos com o ARC, o qualificador (__strong
, __weak
, __unsafe_unretained
, __autoreleasing
) deve ser colocado entre o asterisco e o nome da variável, por exemplo: NSString * __weak text
.
As convenções de nomenclatura da Apple devem ser respeitadas sempre que possível, especialmente aquelas relacionadas a regras de gerenciamento de memória (NARC).
Nomes longos e descritivos para variáveis e métodos são uma boa opção.
Exemplo correto:
UIButton *settingsButton;
Inadequado:
UIButton *setBut;
O prefixo de 3 letras (exemplo: NYT
) deve sempre ser usado em nomes de classes e contantes, no entanto, pode ser omitido para nomes de entidades do tipo Core Data. Contantes devem ser camel-case com todas as palavras em a maiúsculo e prefixadas com o nome da classe, facilitando a interpretação do código. Prefixo de duas letras (por exemplo: NS
) é reservado para uso pela Apple.
Exemplo correto:
static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;
Inadequado:
static const NSTimeInterval fadetime = 1.7;
Propriedades e variáveis locais devem ser nomeadas utilizando o padrão camel-case com a primeira palavra em letra minúscula.
Variáveis de instância devem ser camel-case iniciando com a primeira palavra em letra minúscula, e devem ser prefixadas com um underscore (_
). Esse formato é consistente com as variáveis sintetizadas automaticamente pelo LLVM. Se o LLVM pode sintetizar uma variável automaticamente, então deixe-o.
Exemplo correto:
@synthesize descriptiveVariableName = _descriptiveVariableName;
Inadequado:
id varnm;
Categorias podem ser usadas para segmentar funcionalidade concisamente e devem ser nomeadas de modo a descrever essa funcionalidade.
Exemplo correto:
@interface UIViewController (NYTMediaPlaying)
@interface NSString (NSStringEncodingDetection)
Inadequado:
@interface NYTAdvertisement (private)
@interface NSString (NYTAdditions)
Métodos e propriedades adicionadas em categorias devem ser nomeadas com o prefixo da aplicação ou específico da organização. Isso evita sobrescrever involuntariamente um método existente, e reduz a change de duas categorias de diferentes bibliotecas adicionarem métodos com o mesmo nome. (A runtime do Objective-C não especifica qual método será chamado no último caso, o que pode levar a efeitos indesejados.)
Exemplo correto:
@interface NSArray (NYTAccessors)
- (id)nyt_objectOrNilAtIndex:(NSUInteger)index;
@end
Inadequado:
@interface NSArray (NYTAccessors)
- (id)objectOrNilAtIndex:(NSUInteger)index;
@end
Quando forem necessários, os comentários devem explicar o porquê um determinado bloco de código faz algo. Qualquer comentário utilizado deve ser mantido atualizado ou excluído.
Blocos de comentários devem ser evitados, de forma que o código deve se auto-documentar, ou seja, necessitar de uma quantidade mínima de explicações. Isso não se aplica as informações utilizadas para gerar uma documentação.
Métodos dealloc
devem ser colocados no topo das classes de implementação, logo após as declarações de @synthesize
e @dynamic
. O init
deve ser colocado imediatamente abaixo do método dealloc
de qualquer classe.
Métodos init
devem ser estruturados da seguinte forma:
- (instancetype)init {
self = [super init]; // ou chame o inicializar correspondente
if (self) {
// Inicialização customizada
}
return self;
}
NSString
, NSDictionary
, NSArray
e NSNumber
devem ser usados sempre para a criação de instâncias imutáveis desses objetos. Atenção especial a utilização do valor nil
em NSArray
ou NSDictionary
, ela pode gerar falha.
Exemplo correto:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;
Inadequado:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
Ao acessar x
, y
, width
ou height
de um CGRect
, sempre use as funções CGGeometry
ao invés de acessar os membros diretamente. Referência da Apple sobre as funções CGGeometry
:
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
Exemplo correto:
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
Inadequado:
CGRect frame = self.view.frame;
CGFloat x = frame.origin.x;
CGFloat y = frame.origin.y;
CGFloat width = frame.size.width;
CGFloat height = frame.size.height;
Constantes são preferidas ao invés de strings literais ou números, uma vez que permitem fácil reprodução de variáveis comumente usadas e podem ser rapidamente alteradas sem a necessidade de localizar e substituir. Constantes devem ser declaradas como static
e não #define
, a menos que sejam utilizadas explicitamente como macros.
Exemplo correto:
static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";
static const CGFloat NYTImageThumbnailHeight = 50.0;
Inadequado:
#define CompanyName @"The New York Times Company"
#define thumbnailHeight 2
Ao usar enum
s, recomenda-se utilizar a nova espeficicação de tipo fixo subjacente, pois esta tem forte verificação de código. O SDK inclui agora uma macro que facilita e incentiva o uso de tipos fixos subjacentes - NZ_ENUM()
.
Exemplo:
typedef NS_ENUM(NSInteger, NYTAdRequestState) {
NYTAdRequestStateInactive,
NYTAdRequestStateLoading
};
Quando trabalhar com máscara de bits, utilize a macro NS_OPTIONS
.
Exemplo:
typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
NYTAdCategoryAutos = 1 << 0,
NYTAdCategoryJobs = 1 << 1,
NYTAdCategoryRealState = 1 << 2,
NYTAdCategoryTechnology = 1 << 3
};
Propriedades privadas devem ser decladas em extensões da classe (categorias anônimas) no arquivo de implementação.
Exemplo correto:
@interface NYTAdvertisement ()
@property (nonatomic, strong) GADBannerView *googleAdView;
@property (nonatomic, strong) ADBannerView *iAdView;
@property (nonatomic, strong) UIWebView *adXWebView;
@end
O nome de uma imagem deve ser consistente, preservando a organização e o objetivo ao qual foi criada. Elas deve utilizar o padrão camel-case para descrever sua finalidade, seguido do nome não prefixado da classe ou propriedade que está sendo personalizada (caso exista), seguido por uma descrição mais detalhada de sua cor e, finalmente, seu estado (selecionado, por exemplo).
Exemplo correto:
RefreshBarButtonItem
/RefreshBarButtonItem@2x
andRefreshBarButtonItemSelected
/RefreshBarButtonItemSelected@2x
ArticleNavigationBarWhite
/ArticleNavigationBarWhite@2x
andArticleNavigationBarBlackSelected
/ArticleNavigationBarBlackSelected@2x
.
Imagens que são utilizadas para um propósito similar devem fazer parte do mesmo grupo, dentro de uma pasta ou um Asset Catalog
.
Nunca compare algo diretamente com YES
, porque YES
é definido como 1
, e um BOOL
em Objective-C é um CHAR
que é 8 bits (então um valor 11111110
retornará NO
se comparado com YES
).
Exemplo com ponteiro para objeto:
if (!someObject) {
}
if (someObject == nil) {
}
Para um valor BOOL
:
if (isAwesome)
if (!someObject.boolValue)
if (someObject.boolValue == NO)
Inadequado:
if (isAwesome == YES) // Nunca faça isso.
Se o nome de uma propriedade do tipo BOOL
é expresso como um adjetivo, o nome da propriedade pode omitir o prefixo is
, mas deve continuar especificando o nome convencional para o metodo de acesso get
.
Por exemplo:
@property (assign, getter=isEditable) BOOL editable;
Textos e exemplos tirados do Cocoa Naming Guidelines.
Objetos Singleton devem utilizar o padrão thread-safe para criação de uma instância compartilhada.
+ (instancetype)sharedInstance {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Isso evita possíveis falhas.
Se existir mais de uma declaração de import, agrupe-os. Comentar cada grupo é opcional.
Nota: Para módulos use a syntax @import.
// Frameworks
@import QuartzCore;
// Models
#import "NYTUser.h"
// Views
#import "NYTButton.h"
#import "NYTUserView.h"
Em um protocolo de delegate ou data source, o primeiro parâmetro do método deve ser o objeto que envia a mensagem.
Isso ajuda a desambiguar casos onde um objeto é o delegate de múltiplos objetos de tipos similares, e isso ajuda a clarificar a intenção para os leitores de uma classe implementando esses métodos delegate.
Exemplo correto:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
Inadequado:
- (void)didSelectTableRowAtIndexPath:(NSIndexPath *)indexPath;
Os arquivos físicos (estrutura de diretórios do Finder), devem ser mantidos em sincronia com os arquivos do projeto no Xcode. Qualquer grupo criado no Xcode deve refletir na geração de uma pasta no sistema de arquivos (Finder). O código não deve ser agrupado apenas pelo tipo, mas também pela sua funcionalidade para maior clareza.
Quando possível, sempre habilite Treat Warning as Errors
no Build Settings
do target
, para tratar os avisos de alerta como erros, e também habilite todos os tipos de alertas adicionais possíveis. Caso seja necessário ignorar uma advertência específica, utilize a pragma do Clang.
Caso não goste de nossa convenção, segue abaixo outros padrões: