Quais são as melhores práticas que você usa ao escrever Objective-C e Cocoa? [fechadas]

346

Eu sei sobre o HIG (o que é bastante útil!), Mas quais práticas de programação você usa ao escrever o Objective-C e, mais especificamente, ao usar o Cocoa (ou CocoaTouch).

pixel
fonte
veja este post do blog, muito bom. ironwolf.dangerousgames.com/blog/archives/913
user392412

Respostas:

398

Há algumas coisas que comecei a fazer que não considero padrão:

1) Com o advento das propriedades, não uso mais "_" para prefixar variáveis ​​de classe "privadas". Afinal, se uma variável pode ser acessada por outras classes, não deveria haver uma propriedade para ela? Eu sempre não gostei do prefixo "_" para tornar o código mais feio e agora posso deixá-lo de fora.

2) Falando em assuntos particulares, prefiro colocar definições de métodos particulares no arquivo .m em uma extensão de classe como:

#import "MyClass.h"

@interface MyClass ()
- (void) someMethod;
- (void) someOtherMethod;
@end

@implementation MyClass

Por que bagunçar o arquivo .h com coisas que as pessoas de fora não deveriam se importar? O empty () funciona para categorias privadas no arquivo .m e emite avisos de compilação se você não implementar os métodos declarados.

3) Decidi colocar dealloc na parte superior do arquivo .m, logo abaixo das diretivas @synthesize. O que você desaloca não deveria estar no topo da lista de coisas que deseja pensar em uma classe? Isso é especialmente verdade em um ambiente como o iPhone.

3.5) Nas células da tabela, torne cada elemento (incluindo a própria célula) opaco para o desempenho. Isso significa definir a cor de fundo apropriada em tudo.

3.6) Ao usar um NSURLConnection, como regra, você pode implementar o método delegate:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
      return nil;
}

Acho que a maioria das chamadas na web é muito singular e é mais uma exceção do que a regra em que você deseja respostas em cache, especialmente para chamadas de serviço da web. A implementação do método conforme mostrado desativa o armazenamento em cache de respostas.

Também interessam algumas boas dicas específicas de Joseph Mattiello sobre o iPhone (recebidas em uma lista de discussão do iPhone). Há mais, mas essas foram as mais úteis que pensei (note que agora alguns bits foram ligeiramente editados a partir do original para incluir detalhes oferecidos nas respostas):

4) Use apenas precisão dupla se for necessário, como ao trabalhar com o CoreLocation. Certifique-se de terminar suas constantes em 'f' para que o gcc as armazene como flutuadores.

float val = someFloat * 2.2f;

Isso é importante principalmente quando someFloat na verdade, pode ser um duplo, você não precisa da matemática de modo misto, pois está perdendo a precisão em 'val' no armazenamento. Embora os números de ponto flutuante sejam suportados no hardware dos iPhones, ainda pode levar mais tempo para fazer aritmética de precisão dupla em oposição à precisão única. Referências:

Nos telefones mais antigos, supostamente os cálculos operam na mesma velocidade, mas você pode ter mais componentes de precisão únicos nos registros do que o dobro, portanto, para muitos cálculos, a precisão única acaba sendo mais rápida.

5) Defina suas propriedades como nonatomic. Eles estãoatomic por padrão e, após a síntese, o código do semáforo será criado para evitar problemas de multiencadeamento. 99% de vocês provavelmente não precisam se preocupar com isso, e o código é muito menos inchado e mais eficiente em termos de memória quando definido como não-atômico.

6) O SQLite pode ser uma maneira muito, muito rápida de armazenar em cache grandes conjuntos de dados. Um aplicativo de mapa, por exemplo, pode armazenar em cache seus blocos em arquivos SQLite. A parte mais cara é a E / S do disco. Evite muitas gravações pequenas enviando BEGIN;eCOMMIT; entre blocos grandes. Utilizamos um timer de 2 segundos, por exemplo, que é redefinido a cada novo envio. Quando expira, enviamos COMMIT; , o que faz com que todas as suas gravações ocorram em um grande bloco. O SQLite armazena dados de transação em disco e, ao fazer isso, o agrupamento Begin / End evita a criação de muitos arquivos de transação, agrupando todas as transações em um arquivo.

Além disso, o SQL bloqueará sua GUI se estiver no seu thread principal. Se você tiver uma consulta muito longa, é uma boa ideia armazenar suas consultas como objetos estáticos e executar seu SQL em um thread separado. Certifique-se de agrupar qualquer coisa que modifique o banco de dados para cadeias de caracteres de consulta em @synchronize() {}blocos. Para consultas curtas, basta deixar as coisas no segmento principal para facilitar a conveniência.

Mais dicas de otimização do SQLite estão aqui, embora o documento pareça desatualizado, muitos dos pontos provavelmente ainda são bons;

http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html

Kendall Helmstetter Gelner
fonte
3
Boa dica sobre a aritmética dupla.
Adam Ernst
8
As extensões de classe agora são a maneira preferida para métodos particulares: developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/…
Casebash
9
Seus conselhos sobre duplas no iPhone estão desatualizados stackoverflow.com/questions/1622729/…
Casebash
3
Não está desatualizado; totalmente errado: o iPhone original suportado flutua e dobra em hardware aproximadamente na mesma velocidade. O SQLite também não mantém transações na memória; eles estão registrados no disco. Somente consultas longas bloqueiam sua interface do usuário; é menos confuso executar tudo no thread principal e usar consultas mais rápidas.
tc.
11
@tc: Corrigi o item SQL sobre transações, observe que eu mesmo não escrevi esses últimos quatro itens. Também esclareci que a parte de mover as consultas para o segundo plano era apenas para consultas muito longas (às vezes você simplesmente não consegue reduzi-las). Mas chamar a coisa toda de "errada" por causa de alguns pontos é que me sinto um tanto extremo. Além disso, a resposta acima já afirmava: "Nos telefones antigos, os cálculos supostamente operam na mesma velocidade", mas observe a parte sobre o maior número de registros de precisão únicos, tornando-os ainda preferíveis.
Kendall Helmstetter Gelner 23/09/10
109

Não use strings desconhecidas como strings de formato

Quando métodos ou funções adotam um argumento de sequência de formato, você deve ter controle sobre o conteúdo da sequência de formato.

Por exemplo, ao registrar seqüências de caracteres, é tentador passar a variável de sequência como o único argumento para NSLog:

    NSString *aString = // get a string from somewhere;
    NSLog(aString);

O problema é que a cadeia pode conter caracteres que são interpretados como cadeias de formato. Isso pode levar a resultados errados, falhas e problemas de segurança. Em vez disso, você deve substituir a variável string por uma string formatada:

    NSLog(@"%@", aString);
mmalc
fonte
4
Eu já fui mordido por este antes.
Adam Ernst
Este é um bom conselho para qualquer linguagem de programação
Tom Fobear
107

Use convenções e terminologia padrão de nomenclatura e formatação do cacau, em vez do que você estiver acostumado a partir de outro ambiente. Não são muitos desenvolvedores de cacau lá fora, e quando mais um deles começa a trabalhar com o seu código, que vai ser muito mais acessível se olha e se sente semelhante a outro código de cacau.

Exemplos do que fazer e do que não fazer:

  • Não declare id m_something;na interface de um objeto e chame-o de variável ou campo membro ; use somethingou _somethingcomo nome e chame-a de variável de instância .
  • Não nomeie um getter -getSomething; o nome apropriado de cacau é justo -something.
  • Não nomeie um setter -something:; deveria ser-setSomething:
  • O nome do método é intercalado com os argumentos e inclui dois pontos; é -[NSObject performSelector:withObject:], não NSObject::performSelector.
  • Use inter-caps (CamelCase) em nomes de métodos, parâmetros, variáveis, nomes de classes, etc., em vez de barras inferiores (sublinhados).
  • Os nomes de classe começam com uma letra maiúscula, nomes de variáveis ​​e métodos com minúsculas.

O que quer que você faça, não use a notação húngara no estilo Win16 / Win32. Até a Microsoft desistiu disso com a mudança para a plataforma .NET.

Chris Hanson
fonte
5
Eu diria que não use setSomething: / alguma coisa - use propriedades. Neste ponto, há poucas pessoas que realmente precisam para atingir Tiger (a única razão para não usar propriedades)
Kendall Helmstetter Gelner
18
As propriedades ainda geram métodos de acesso para você, e os atributos getter = / setter = na propriedade permitem especificar os nomes dos métodos. Além disso, você pode usar a sintaxe [foo something] em vez da foo.something sintaxe com propriedades. Portanto, a nomeação de acessador ainda é relevante.
31120 Chris Hanson
3
Esta é uma ótima referência para alguém vindo de C ++, onde eu fiz a maioria das coisas contra as quais você desaconselha.
Clinton Blackmore
4
Um setter não deve estar fazendo com que algo seja salvo no banco de dados. Há uma razão para o Core Data ter um método -save: no NSManagedObjectContext, em vez de os setters gerarem atualizações imediatas.
21320 Chris Hanson
2
Duvido que não tenha sido uma opção, no entanto, pode ter sido necessário revisitar a arquitetura do aplicativo. (Para deixar claro: não estou dizendo "Você deveria ter usado os Dados Principais." Estou dizendo "Setters não devem ser salvos no banco de dados".) Tendo um contexto para gerenciar um gráfico de objeto, em vez de salvar objetos individuais nele , é praticamente sempre possível e uma solução melhor.
Chris Hanson
106

IBOutlets

Historicamente, o gerenciamento de memória das tomadas tem sido ruim. A melhor prática atual é declarar pontos de venda como propriedades:

@interface MyClass :NSObject {
    NSTextField *textField;
}
@property (nonatomic, retain) IBOutlet NSTextField *textField;
@end

O uso de propriedades torna clara a semântica do gerenciamento de memória; Ele também fornece um padrão consistente se você usar a síntese de variáveis ​​de instância.

mmalc
fonte
11
o carregamento da ponta não a reteria duas vezes? (uma vez na ponta, segundo por atribuição à propriedade). Devo liberar aqueles em desalojamento?
Kornel
6
Você precisa nula as saídas no viewDidUnload (iPhone OS 3.0+) ou em um método setView: personalizado para evitar vazamentos. Obviamente, você também deve liberar em desalocação.
24630 Frank Szczerba
2
Tenha em mente que nem todos concordam com este estilo: weblog.bignerdranch.com/?p=95
Michael
É assim que a Apple também faz as coisas. "Iniciando o desenvolvimento do iPhone 3" também menciona essa alteração em relação às versões anteriores.
Ustun
Eu mencionei isso em outro comentário, mas deveria ter colocado aqui: Quando a síntese dinâmica de ivar começar a acontecer para aplicativos iOS (se / quando?), Você ficará feliz por colocar o IBOutlet na propriedade versus o ivar!
30510 Joe D'Andrea
97

Use o analisador estático LLVM / Clang

NOTA: No Xcode 4, isso agora está incorporado ao IDE.

Você usa o Clang Static Analyzer para - sem surpresa - analisar seu código C e Objective-C (sem C ++ ainda) no Mac OS X 10.5. É trivial instalar e usar:

  1. Baixe a versão mais recente desta página .
  2. Da linha de comando, cdpara o diretório do projeto.
  3. Execute scan-build -k -V xcodebuild.

(Existem algumas restrições adicionais, etc., em particular, você deve analisar um projeto em sua configuração "Debug" - consulte http://clang.llvm.org/StaticAnalysisUsage.html para obter detalhes - mas isso é mais ou menos o que se resume.)

O analisador produz um conjunto de páginas da Web para você que mostra o gerenciamento provável da memória e outros problemas básicos que o compilador não consegue detectar.

mmalc
fonte
11
Eu tive alguns problemas começar este trabalho até que eu segui estas instruções: oiledmachine.com/posts/2009/01/06/...
bbrown
15
No XCode 3.2.1 no Snow Leopard, ele já está embutido. Você pode executá-lo manualmente, usando Executar -> Compilar e Analisar , ou pode habilitá-lo para todas as compilações através da configuração de compilação "Executar Static Analyzer". Observe que atualmente esta ferramenta suporta apenas C e Objective-C, mas não C ++ / Objective-C ++.
Oefe 18/10/09
94

Este é sutil, mas prático. Se você estiver se passando como delegado para outro objeto, redefina o delegado desse objeto antes de você dealloc.

- (void)dealloc
{
self.someObject.delegate = NULL;
self.someObject = NULL;
//
[super dealloc];
}

Ao fazer isso, você garante que não serão enviados mais métodos delegados. Enquanto você está prestes a deallocdesaparecer no éter, deseja ter certeza de que nada poderá lhe enviar mais mensagens por acidente. Lembre-se self.someObject pode ser retido por outro objeto (pode ser um singleton ou no pool de liberação automática ou o que for) e até você dizer "pare de me enviar mensagens!", Ele acha que seu objeto está prestes a ser desalocado é um jogo justo.

Entrar nesse hábito irá salvá-lo de muitas falhas estranhas que são difíceis de depurar.

O mesmo princípio se aplica à Observação do valor-chave e às NSNotifications também.

Editar:

Ainda mais defensivo, mude:

self.someObject.delegate = NULL;

para dentro:

if (self.someObject.delegate == self)
    self.someObject.delegate = NULL;
schwa
fonte
8
Não há nada sutil nisso, a documentação diz claramente que você é obrigado a fazer isso. De Memory Management Programming Guide for Cocoa: Additional cases of weak references in Cocoa include, but are not restricted to, table data sources, outline view items, notification observers, and miscellaneous targets and delegates. In most cases, the weak-referenced object is aware of the other object’s weak reference to it, as is the case for circular references, and is responsible for notifying the other object when it deallocates.
johne 21/07/2009
É melhor usar nulo em vez de NULL, porque o NULL não liberará a memória.
Naveen Shan 10/10
@NaveenShan nil == NULL. Eles são exatamente os mesmos, exceto que nilé um ide NULLé um void *. Sua afirmação não é verdadeira.
@WTP sim, nil == NULL, mas usar nil é claramente a maneira preferida, se você olhar através de fragmentos de código de exemplo de maçãs, eles estão usando nil em todos os lugares e, como você disse, nil é um id, o que o torna preferível em relação ao vazio * , nos casos em que você envia IDs, ou seja.
Ahti 23/11
11
@Ahti exatamente e Nil(maiúsculo) é do tipo Class*. Mesmo sendo todos iguais, usar o incorreto pode introduzir pequenos erros desagradáveis, especialmente no Objective-C ++.
86

@kendell

Ao invés de:

@interface MyClass (private)
- (void) someMethod
- (void) someOtherMethod
@end

Usar:

@interface MyClass ()
- (void) someMethod
- (void) someOtherMethod
@end

Novo no Objective-C 2.0.

As extensões de classe são descritas na Referência do Objective-C 2.0 da Apple.

"As extensões de classe permitem que você declare API adicional necessária para uma classe em locais diferentes do bloco @interface da classe principal"

Portanto, eles fazem parte da classe real - e NÃO de uma categoria (privada) além da classe. Diferença sutil, mas importante.

schwa
fonte
Você poderia fazer isso, mas eu gosto de rotulá-la explicitamente como uma seção "private" (mais documentação do que funcional) embora, naturalmente, que já é bastante óbvio que estando localizado no arquivo .m ...
Kendall Helmstetter Gelner
2
Exceto que não é uma diferença entre as categorias privadas e extensões de classe: "extensões de classe permitem que você declarar API necessário adicional para uma classe em diferentes dentro do bloco de classe @ interface primária locais, como ilustrado no exemplo a seguir:" Veja o link na edição.
schwa
Concordo que há uma diferença em que o compilador avisará você quando você não implementou os métodos CE - mas não acho esse aspecto muito importante quando todos os métodos estão no mesmo arquivo e todos são privados. Eu ainda prefiro o aspecto de manutenção de marcar o bloco de referência para a frente privada
Kendall Helmstetter Gelner
3
Realmente não vejo (Privado) mais sustentável que (). Se você está preocupado, uma boa dose de comentários pode ajudar. Mas, obviamente, viva e deixe viver. YMMV etc.
schwa
17
Há uma vantagem bastante importante a ser usada em ()vez de (Private)(ou algum outro nome de categoria): Você pode redefinir as propriedades como readwrite enquanto para o público elas são apenas de leitura. :)
Pascal
75

Evitar liberação automática

Como você (1) normalmente não tem controle direto sobre sua vida útil, os objetos liberados automaticamente podem persistir por um período comparativamente longo e aumentar desnecessariamente a área de armazenamento de memória do seu aplicativo. Enquanto na área de trabalho, isso pode ter pouca importância, em plataformas mais restritas, isso pode ser um problema significativo. Em todas as plataformas, portanto, e especialmente em plataformas mais restritas, é considerado uma prática recomendada evitar o uso de métodos que levariam a objetos liberados automaticamente e, em vez disso, é recomendável usar o padrão de alocação / inicialização.

Assim, ao invés de:

aVariable = [AClass convenienceMethod];

onde for possível, você deve usar:

aVariable = [[AClass alloc] init];
// do things with aVariable
[aVariable release];

Ao escrever seus próprios métodos que retornam um objeto recém-criado, você pode tirar proveito da convenção de nomenclatura do Cocoa para sinalizar ao destinatário que ele deve ser liberado, acrescentando o nome do método com "novo".

Assim, em vez de:

- (MyClass *)convenienceMethod {
    MyClass *instance = [[[self alloc] init] autorelease];
    // configure instance
    return instance;
}

você poderia escrever:

- (MyClass *)newInstance {
    MyClass *instance = [[self alloc] init];
    // configure instance
    return instance;
}

Como o nome do método começa com "novo", os consumidores da sua API sabem que são responsáveis ​​por liberar o objeto recebido (consulte, por exemplo, o newObjectmétodo NSObjectController ).

(1) Você pode assumir o controle usando seus próprios pools de autorelease locais. Para mais informações, consulte Pools de liberação automática .

mmalc
fonte
6
Acho que os benefícios de não usar a liberação automática superam seus custos (ou seja, mais erros de vazamento de memória). O código no encadeamento principal deve ser bastante curto de qualquer maneira (ou você congelará a interface do usuário) e, para um código de segundo plano com uso intenso de memória e execução mais longa, sempre é possível agrupar as partes com uso intenso de memória em conjuntos de liberação automática local.
adib 04/07/10
56
Discordo. Você deve usar objetos liberados automaticamente sempre que possível. Se eles aumentam demais a área de memória, você deve usar outra NSAutoreleasePool. Mas somente depois de confirmar que isso realmente é um problema. Otimização prematura e tudo o que ...
Sven
3
Eu gasto menos de 40 segundos. um dia digitando [alguma versão do objeto] e lendo a "linha extra" ao instanciar um novo objeto, mas uma vez gravei 17 horas para encontrar um bug de autorelease que só aparecia em casos especiais e não dava nenhum erro coerente no console. Por isso, concordo com o adib quando ele coloca como "acho que os benefícios de não usar o autorelease superam seus custos".
RickiG
7
Eu concordo com Sven. O objetivo principal deve ser a clareza do código e a redução dos erros de codificação, com otimização de memória apenas onde for necessário. Digitar uma autorelease [[[Foo aloced] init]] é rápido e você lida imediatamente com a questão de liberar esse novo objeto. Ao ler o código, você não precisa procurar o release correspondente para garantir que ele não esteja vazando.
Mike Weller
3
O ciclo de vida de objetos liberados automaticamente é bem definido e determinável em nível suficiente.
Eonil
69

Algumas delas já foram mencionadas, mas aqui está o que consigo pensar:

  • Siga as regras de nomenclatura do KVO. Mesmo que você não use o KVO agora, na minha experiência muitas vezes ainda é benéfico no futuro. E se você estiver usando KVO ou ligações, precisará saber que as coisas vão funcionar da maneira que deveriam. Isso abrange não apenas métodos de acesso e variáveis ​​de instância, mas também muitos relacionamentos, validação, chaves dependentes de notificação automática e assim por diante.
  • Coloque métodos privados em uma categoria. Não apenas a interface, mas também a implementação. É bom ter alguma distância conceitual entre métodos privados e não privados. Eu incluo tudo no meu arquivo .m.
  • Coloque métodos de encadeamento em segundo plano em uma categoria. O mesmo que acima. Descobri que é bom manter uma barreira conceitual clara quando você está pensando sobre o que está no thread principal e o que não está.
  • Use #pragma mark [section]. Geralmente, agrupo de acordo com meus próprios métodos, as substituições de cada subclasse e qualquer informação ou protocolo formal. Isso torna muito mais fácil pular exatamente para o que estou procurando. No mesmo tópico, agrupe métodos semelhantes (como os métodos delegados de uma exibição de tabela), não os cole em qualquer lugar.
  • Prefixe métodos privados e ivars com _. Gosto da aparência e tenho menos probabilidade de usar um ivar quando digo uma propriedade por acidente.
  • Não use métodos / propriedades de mutadores em init & dealloc. Eu nunca tive nada de ruim por causa disso, mas posso ver a lógica se você alterar o método para fazer algo que depende do estado do seu objeto.
  • Coloque IBOutlets nas propriedades. Na verdade, acabei de ler este aqui, mas vou começar a fazê-lo. Independentemente de quaisquer benefícios de memória, parece melhor estilisticamente (pelo menos para mim).
  • Evite escrever código que você não precisa absolutamente. Isso realmente abrange muitas coisas, como fazer ivars quando a #definevontade o fará ou armazenar em cache uma matriz em vez de classificá-la sempre que os dados forem necessários. Há muito o que dizer sobre isso, mas o ponto principal é que não escreva código até que você precise, ou o criador de perfil diz para você. Torna as coisas muito mais fáceis de manter a longo prazo.
  • Termine o que você começa. Ter muitos códigos incompletos e incompletos é a maneira mais rápida de matar um projeto morto. Se você precisar de um método de stub adequado, indique-o inserindo-o NSLog( @"stub" )ou, no entanto, deseja acompanhar as coisas.
Marc Charbonneau
fonte
3
Eu sugiro que você coloque métodos privados em uma continuação de classe. (ie @interface MyClass () ... @end em sua .m)
Jason Medeiros
3
Em vez de #PRAGMA, você pode usar um comentário // Mark: [Section], que é mais portátil e funciona de forma idêntica.
2119 Aleemb
A menos que exista uma sintaxe especial, // Mark: não adiciona um rótulo no menu suspenso de funções do Xcode, o que é realmente metade do motivo de usá-lo.
22611 Marc Charbonneau
6
Você precisa usar letras maiúsculas, "// MARK: ...", para que apareça no menu suspenso.
Rhult 21/11/2009
3
Em relação a Finish what you startvocê também pode usar // TODO:para marcar o código para conclusão, que será exibido no menu suspenso.
iwasrobbed
56

Escreva testes de unidade. Você pode testar muitas coisas no Cocoa que podem ser mais difíceis em outras estruturas. Por exemplo, com o código da interface do usuário, você geralmente pode verificar se as coisas estão conectadas como deveriam e confiar que elas funcionarão quando usadas. E você pode configurar o estado e chamar métodos delegados facilmente para testá-los.

Você também não tem visibilidade de método público x protegido x privado, impedindo a criação de testes para seus internos.

Chris Hanson
fonte
Quais estruturas de teste você recomenda?
melfar
13
O Xcode inclui o OCUnit, uma estrutura de teste de unidade Objective-C e suporte para a execução de pacotes de testes de unidade como parte do seu processo de construção.
Chris Hanson
55

Regra de Ouro: Se você, allocentão você release!

ATUALIZAÇÃO: A menos que você esteja usando o ARC

logancautrell
fonte
26
Também se você copy, mutableCopy, newou retain.
Sven
54

Não escreva Objective-C como se fosse Java / C # / C ++ / etc.

Uma vez vi uma equipe acostumada a escrever aplicativos da Web Java EE tentando escrever um aplicativo de desktop Cocoa. Como se fosse um aplicativo da web Java EE. Havia muito AbstractFooFactory e FooFactory e IFoo e Foo voando quando tudo o que eles realmente precisavam era de uma classe Foo e possivelmente um protocolo Fooable.

Parte de garantir que você não faça isso é realmente entender as diferenças no idioma. Por exemplo, você não precisa das classes abstract factory e factory acima, porque os métodos da classe Objective-C são despachados tão dinamicamente quanto os métodos de instância e podem ser substituídos nas subclasses.

Chris Hanson
fonte
10
Como desenvolvedor Java que escreveu uma fábrica abstrata no Objective-C, acho isso intrigante. Você se importaria de explicar um pouco mais como isso funciona - talvez com um exemplo?
teabot
2
Você ainda acredita que não precisamos de aulas abstratas de fábrica depois de todo o tempo desde que você postou esta resposta?
kirk.burleson
50

Certifique-se de marcar a página Magia de depuração . Esta deve ser sua primeira parada ao bater a cabeça contra uma parede enquanto tenta encontrar a fonte de um inseto de cacau.

Por exemplo, ele mostrará como encontrar o método em que você alocou a memória pela primeira vez que mais tarde está causando falhas (como durante o encerramento do aplicativo).

mj1531
fonte
11
Agora existe uma versão específica para iOS da página Debugging Magic .
Jeethu
38

Tente evitar o que agora decidi chamar de Newbiecategoryaholism. Quando os recém-chegados ao Objective-C descobrem categorias, geralmente ficam loucos, adicionando pequenas categorias úteis a todas as classes existentes ( "O quê? Posso adicionar um método para converter um número em números romanos em NSNumber rock on!" ).

Não faça isso.

Seu código será mais portátil e mais fácil de entender, com dezenas de pequenos métodos de categoria espalhados por duas dúzias de classes de fundação.

Na maioria das vezes, quando você realmente pensa que precisa de um método de categoria para ajudar a simplificar algum código, verá que nunca acaba reutilizando o método.

Existem outros perigos também, a menos que você esteja colocando o nome dos métodos de categoria (e quem além do ddribin totalmente insano é?), Há uma chance de que a Apple, ou um plug-in ou outra coisa em execução no seu espaço de endereço também defina a mesma categoria método com o mesmo nome com um efeito colateral ligeiramente diferente ....

ESTÁ BEM. Agora que você foi avisado, ignore o "não faça esta parte". Mas exerça extrema restrição.

schwa
fonte
Gosto da sua resposta, meu conselho seria não usar uma categoria para armazenar o código do utilitário, a menos que você esteja prestes a replicar algum código em mais de um local e o código claramente pertença à classe que você está categorizando ...
Kendall Helmstetter Gelner 01/10/08
Gostaria apenas de expressar e expressar meu apoio aos métodos de categoria de namespacing. Parece apenas a coisa certa a fazer.
Michael Buckley
+1 se apenas para números romanos. Eu faria isso totalmente!
Brian Postow
14
Contraponto: no último ano e meio, segui a política exatamente oposta: "Se puder ser implementada em uma categoria, faça-o". Como resultado, meu código é muito mais conciso, mais expressivo e mais fácil de ler do que o código de amostra detalhado que a Apple fornece. Perdi um total de 10 minutos em um conflito de espaço de nome e provavelmente ganhei meses-homem com a eficiência que criei para mim. Para cada um deles, mas adotei essa política conhecendo os riscos e estou extremamente feliz por ter feito.
Cduhn #
7
Eu não concordo Se for uma função e se aplica a um objeto Foundation, e você pode pensar em um bom nome, coloque-o em uma categoria. Seu código será mais legível. Eu acho que realmente o ponto mais importante aqui é: faça tudo com moderação.
Mxcl
37

Resista a subclassificar o mundo. No Cocoa, muito é feito através da delegação e uso do tempo de execução subjacente, que em outras estruturas é feito através da subclassificação.

Por exemplo, em Java, você usa muito instâncias de *Listenersubclasses anônimas e, no .NET, usa muito suas EventArgssubclasses. No cacau, você também não faz - a ação-alvo é usada.

Chris Hanson
fonte
6
Também conhecido como "Composição sobre herança".
Andrew Ebling
37

Classifique as strings conforme o usuário desejar

Ao classificar seqüências de caracteres para apresentar ao usuário, você não deve usar o compare:método simples . Em vez disso, você sempre deve usar métodos de comparação localizados como localizedCompare:ou localizedCaseInsensitiveCompare:.

Para obter mais detalhes, consulte Pesquisando, comparando e classificando cadeias .

mmalc
fonte
31

Propriedades declaradas

Você normalmente deve usar o recurso Propriedades declaradas do Objective-C 2.0 para todas as suas propriedades. Se eles não forem públicos, adicione-os em uma extensão de classe. O uso de propriedades declaradas torna a semântica de gerenciamento de memória imediatamente clara e facilita a verificação de seu método de desdobramento - se você agrupar suas declarações de propriedades, poderá digitalizá-las rapidamente e comparar com a implementação do seu método de desdobramento.

Você deve pensar bastante antes de não marcar as propriedades como 'não atômicas'. Como observa o Guia de Linguagem de Programação do Objective C , as propriedades são atômicas por padrão e incorrem em custos consideráveis. Além disso, simplesmente tornar todas as suas propriedades atômicas não torna seu aplicativo seguro para threads. Observe também, é claro, que se você não especificar 'não-atômico' e implementar seus próprios métodos de acesso (em vez de sintetizá-los), deverá implementá-los de maneira atômica.

mmalc
fonte
26

Pense em valores nulos

Como observa essa pergunta , as mensagens para nilsão válidas no Objective-C. Embora isso freqüentemente seja uma vantagem - levando a um código mais limpo e mais natural -, o recurso pode ocasionalmente levar a erros peculiares e difíceis de rastrear se você obtiver um nilvalor quando não o esperava.

mmalc
fonte
Eu tenho isso: #define SXRelease(o); o = nile o mesmo para CFReleasee free. Isso simplifica tudo.
26

Use NSAssert e amigos. Eu uso nulo como objeto válido o tempo todo ... especialmente o envio de mensagens para nil é perfeitamente válido no Obj-C. No entanto, se eu realmente quiser ter certeza do estado de uma variável, eu uso o NSAssert e o NSParameterAssert, o que ajuda a rastrear os problemas facilmente.

NikWest
fonte
23

Simples, mas muitas vezes esquecido. De acordo com as especificações:

Em geral, métodos em diferentes classes que possuem o mesmo seletor (o mesmo nome) também devem compartilhar os mesmos tipos de retorno e argumento. Essa restrição é imposta pelo compilador para permitir a ligação dinâmica.

nesse caso, todos os mesmos seletores nomeados, mesmo que em classes diferentes , serão considerados como tendo tipos idênticos de retorno / argumento. Aqui está um exemplo simples.

@interface FooInt:NSObject{}
-(int) print;
@end

@implementation FooInt
-(int) print{
    return 5;
}
@end

@interface FooFloat:NSObject{}
-(float) print;
@end

@implementation FooFloat
-(float) print{
    return 3.3;
}
@end

int main (int argc, const char * argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];    
    id f1=[[FooFloat alloc]init];
    //prints 0, runtime considers [f1 print] to return int, as f1's type is "id" and FooInt precedes FooBar
    NSLog(@"%f",[f1 print]);

    FooFloat* f2=[[FooFloat alloc]init];
    //prints 3.3 expectedly as the static type is FooFloat
    NSLog(@"%f",[f2 print]);

    [f1 release];
    [f2 release]
    [pool drain];

    return 0;
}   
Comptrol
fonte
é fácil esquecer. No entanto, importante
Brock Woolf
3
Isso é apenas uma preocupação ao se abster de digitar estática. Se o compilador conhece o tipo, os tipos de argumento e retorno podem diferir sem problemas. Pessoalmente, acho que isso não costuma ser um problema. A Apple também possui muitos métodos que têm o mesmo nome, mas diferem nos tipos de retorno. Finalmente, há um sinalizador do compilador para avisá-lo em casos ambíguos.
Nikolai Ruhe
Se seguirmos Naming Convention diretriz da Apple, esta situação não vai ser acontecer :)
Eonil
22

Se você estiver usando o Leopard (Mac OS X 10.5) ou posterior, poderá usar o aplicativo Instruments para encontrar e rastrear vazamentos de memória. Após criar seu programa no Xcode, selecione Executar> Iniciar com a Ferramenta de Desempenho> Vazamentos.

Mesmo que seu aplicativo não mostre vazamentos, você pode manter objetos por muito tempo. Em Instrumentos, você pode usar o instrumento ObjectAlloc para isso. Selecione o instrumento ObjectAlloc no documento Instruments e exiba os detalhes do instrumento (se ele ainda não estiver aparecendo) escolhendo Exibir> Detalhes (ele deve ter uma marca de seleção ao lado). Em "Tempo de vida da alocação" nos detalhes do ObjectAlloc, escolha o botão de opção ao lado de "Criado e ainda vivo".

Agora, sempre que você parar de gravar seu aplicativo, selecionar a ferramenta ObjectAlloc mostrará quantas referências existem para cada objeto ainda vivo no seu aplicativo na coluna "# Net". Certifique-se de não apenas olhar para suas próprias classes, mas também para as classes dos objetos de nível superior dos arquivos NIB. Por exemplo, se você não possui janelas na tela e vê referências a um NSWindow ainda vivo, talvez não o tenha lançado no seu código.

mj1531
fonte
21

Limpar em desalocação.

Esta é uma das coisas mais fáceis de esquecer - esp. ao codificar a 150 mph. Sempre, sempre, sempre limpe seus atributos / variáveis ​​de membro em desalocação.

Eu gosto de usar os atributos do Objc 2 - com a nova notação de pontos -, portanto, isso torna a limpeza indolor. Muitas vezes, tão simples quanto:

- (void)dealloc
{
    self.someAttribute = NULL;
    [super dealloc];
}

Isso cuidará da liberação para você e definirá o atributo como NULL (que considero programação defensiva - caso outro método mais abaixo no desalocamento acesse a variável membro novamente - raro, mas pode acontecer).

Com o GC ativado na 10.5, isso não é mais necessário - mas você ainda pode precisar limpar os outros recursos que criar, pode fazer isso no método finalize.

schwa
fonte
12
Em geral, você não deve usar métodos de acesso em dealloc (ou init).
mmalc 03/10/08
11
Além dos motivos de desempenho (os acessadores são um pouco mais lentos que o acesso direto), por que não devo usar os acessadores em dealloc ou init?
schwa
11
(a) Os motivos de desempenho são perfeitamente adequados em si mesmos (especialmente se seus acessadores são atômicos). (b) Você deve evitar quaisquer efeitos colaterais que os acessadores possam ter. Este último é particularmente um problema se sua classe puder ser subclassificada.
Mmalc 11/10/08
3
Observarei que, se você estiver executando no tempo de execução moderno com ivars sintetizados, deverá usar acessadores em desalocação. Muitos códigos de tempo de execução modernos são GC, mas nem todos.
Louis Gerbarg 10/11/2008
11
Uma visão mais alargada em tempo ou não usar métodos de acesso / propriedades -inite -deallocmétodos podem ser encontrados aqui: mikeash.com/?page=pyblog/...
Johan Kool
17

Todos esses comentários são ótimos, mas estou realmente surpreso que ninguém tenha mencionado o Guia de estilo Objective-C do Google, publicado há algum tempo. Eu acho que eles fizeram um trabalho muito completo.

slf
fonte
7
Hmm, o primeiro exemplo já está cheio de besteira. Nunca documente idiomas de idioma. Se eu encontrasse esses tipos de comentários em um arquivo de cabeçalho, não me incomodaria em continuar lendo.
precisa saber é o seguinte
5
Oh meus olhos !!!!! Não acredito no que vi.
Eonil
13

Não esqueça que o NSWindowController e o NSViewController liberarão os objetos de nível superior dos arquivos NIB que eles governam.

Se você carregar manualmente um arquivo NIB, será responsável por liberar os objetos de nível superior do NIB quando terminar com eles.

mj1531
fonte
12

Um exemplo bastante óbvio para um iniciante: use o recurso de indentação automática do Xcode para o seu código. Mesmo se você estiver copiando / colando de outra fonte, depois de colar o código, você pode selecionar todo o bloco de código, clicar com o botão direito do mouse e escolher a opção de re-recuar tudo dentro desse bloco.

O Xcode realmente analisará essa seção e a indentará com base em colchetes, loops etc. É muito mais eficiente do que pressionar a barra de espaço ou a tecla Tab para cada linha.

iWasRobbed
fonte
Você pode até definir Tab para recuar e, em seguida, fazer Cmd-A e Tab.
Plumenator
10

Eu sei que ignorei isso quando comecei a programação de cacau.

Certifique-se de entender as responsabilidades de gerenciamento de memória em relação aos arquivos NIB. Você é responsável por liberar os objetos de nível superior em qualquer arquivo NIB carregado. Leia a documentação da Apple sobre o assunto.

mj1531
fonte
6
Isso não é verdade. A responsabilidade ou não de liberar objetos de nível superior depende de qual classe você herda e de qual plataforma está usando. Consulte developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/… entre outros.
Mmalc 03/10/08
10

Ative todos os avisos do GCC e depois os que são causados ​​regularmente pelos cabeçalhos da Apple para reduzir o ruído.

Também execute a análise estática de Clang com frequência; você pode habilitá-lo para todas as compilações através da configuração de compilação "Executar Static Analyzer".

Escreva testes de unidade e execute-os com cada compilação.

oefe
fonte
E, se puder, ative "Tratar avisos como erros". Não permita que nenhum aviso exista.
22711 Peter Hosey
2
Um script útil para configurar o seu projeto com avisos recomendados está disponível aqui: rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Johan Kool
10

Variáveis ​​e propriedades

1 / Mantendo seus cabeçalhos limpos, ocultando a implementação
Não inclua variáveis ​​de instância em seu cabeçalho. Variáveis ​​privadas colocadas na continuação de classe como propriedades. Variáveis ​​públicas são declaradas como propriedades públicas no seu cabeçalho. Se for apenas para leitura, declare-o como somente leitura e substitua-o como readwrite na continuação da classe. Basicamente, não estou usando variáveis, apenas propriedades.

2 / Dê um nome de variável não padrão às suas propriedades, por exemplo:


@synthesize property = property_;

Razão 1: você detectará erros causados ​​pelo esquecimento do "eu". ao atribuir a propriedade. Razão 2: Nas minhas experiências, o Leak Analyzer in Instruments tem problemas para detectar vazamentos de propriedades com o nome padrão.

3 / Nunca use reter ou liberar diretamente nas propriedades (ou apenas em situações muito excepcionais). Em seu negócio, apenas atribua um valor nulo. As propriedades de retenção destinam-se a lidar com a retenção / liberação por si próprias. Você nunca sabe se um levantador não está, por exemplo, adicionando ou removendo observadores. Você deve usar a variável diretamente apenas dentro do seu setter e getter.

Visualizações

1 / Coloque todas as definições de exibição em um xib, se puder (a exceção geralmente é conteúdo dinâmico e configurações de camada). Economiza tempo (é mais fácil do que escrever código), é fácil mudar e mantém seu código limpo.

2 / Não tente otimizar visualizações diminuindo o número de visualizações. Não crie UIImageView no seu código, em vez de xib, apenas porque você deseja adicionar subviews a ele. Use UIImageView como plano de fundo. A estrutura de exibição pode lidar com centenas de visualizações sem problemas.

3 / IBOutlets nem sempre precisam ser mantidos (ou fortes). Observe que a maioria dos seus IBOutlets faz parte da hierarquia de visualizações e, portanto, é retida implicitamente.

4 / Libere todos os IBOutlets no viewDidUnload

5 / Ligue para viewDidUnload do seu método de desdobramento. Não é chamado implicitamente.

Memória

1 / Liberar objetos automaticamente quando você os cria. Muitos bugs são causados ​​ao mover sua chamada de liberação para uma ramificação if-else ou após uma declaração de retorno. A liberação em vez da liberação automática deve ser usada apenas em situações excepcionais - por exemplo, quando você está esperando por um runloop e não deseja que seu objeto seja liberado automaticamente muito cedo.

2 / Mesmo se você estiver usando a Contagem de referência automática, você precisa entender perfeitamente como os métodos de liberação de retenção funcionam. O uso da liberação manual de retenção não é mais complicado do que o ARC, em ambos os casos você deve considerar vazamentos e ciclos de retenção. Considere usar a liberação de retenção manualmente em grandes projetos ou hierarquias de objetos complicadas.

Comentários

1 / Crie seu código documentado automaticamente. Todo nome de variável e método deve dizer o que está fazendo. Se o código for escrito corretamente (você precisará de muita prática nisso), não precisará de nenhum comentário de código (não o mesmo que comentários de documentação). Os algoritmos podem ser complicados, mas o código deve ser sempre simples.

2 / Às vezes, você precisa de um comentário. Geralmente para descrever um comportamento ou hack de código não aparente. Se você acha que precisa escrever um comentário, primeiro tente reescrever o código para ser mais simples e sem a necessidade de comentários.

Indentação

1 / Não aumente muito o recuo. A maior parte do código do método deve ser recuada no nível do método. Blocos aninhados (se, para etc.) diminuem a legibilidade. Se você tiver três blocos aninhados, tente colocar os blocos internos em um método separado. Quatro ou mais blocos aninhados nunca devem ser usados. Se a maior parte do código do método estiver dentro de um if, negue a condição if, exemplo:


if (self) {
   //... long initialization code ...
}

return self;

if (!self) {
   return nil;
}

//... long initialization code ...

return self;

Entenda o código C, principalmente estruturas C

Observe que o Obj-C é apenas uma camada OOP leve sobre a linguagem C. Você deve entender como as estruturas básicas de código em C funcionam (enumerações, estruturas, matrizes, ponteiros etc.). Exemplo:


view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height + 20);

é o mesmo que:


CGRect frame = view.frame;
frame.size.height += 20;
view.frame = frame;

E muitos mais

Mantenha seu próprio documento de padrões de codificação e atualize-o com frequência. Tente aprender com seus erros. Entenda por que um bug foi criado e tente evitá-lo usando padrões de codificação.

Atualmente, nossos padrões de codificação têm cerca de 20 páginas, uma mistura de padrões de codificação Java, padrões Obj-C / C ++ do Google e nossos próprios complementos. Documente seu código, use recuo padrão padrão, espaços em branco e linhas em branco nos lugares certos etc.

Sulthan
fonte
9

Seja mais funcional .

O Objective-C é uma linguagem orientada a objetos, mas a estrutura do Cocoa reconhece o estilo funcional e é projetada em muitos casos.

  1. Há separação de mutabilidade. Use classes imutáveis como objeto primário e mutável como secundário. Por exemplo, use o NSArray principalmente e use o NSMutableArray somente quando necessário.

  2. Existem funções puras. Não muitos, compre muitas APIs de estrutura projetadas como função pura. Veja funções como CGRectMake()ou CGAffineTransformMake(). Obviamente, a forma do ponteiro parece mais eficiente. No entanto, argumentos indiretos com ponteiros não podem oferecer efeitos colaterais. Projete estruturas o máximo possível. Separe objetos de estado pares. Use em -copyvez de -retainpassar um valor para outro objeto. Porque o estado compartilhado pode influenciar a mutação no valor em outro objeto silenciosamente. Portanto, não pode ser livre de efeitos colaterais. Se você tiver um valor de externo a partir do objeto, copie-o. Portanto, também é importante projetar o estado compartilhado o mínimo possível.

No entanto, não tenha medo de usar funções impuras também.

  1. Há uma avaliação preguiçosa. Veja algo como -[UIViewController view]propriedade. A vista não será criada quando o objeto for criado. Ele será criado quando o chamador ler a viewpropriedade pela primeira vez. UIImagenão será carregado até ser realmente desenhado. Existem muitas implementações como este design. Esse tipo de design é muito útil para o gerenciamento de recursos, mas se você não conhece o conceito de avaliação lenta, não é fácil entender o comportamento deles.

  2. Há fechamento. Use blocos C, tanto quanto possível. Isso simplificará muito sua vida. Mas leia mais uma vez sobre o gerenciamento de memória de bloco antes de usá-lo.

  3. Há GC semi-automático. NSAutoreleasePool. Use -autoreleaseprimário. Use o manual -retain/-releasesecundário quando realmente precisar. (ex: otimização de memória, exclusão explícita de recursos)

Eonil
fonte
2
Quanto a 3) proponho a abordagem oposta: use retenção manual / liberação sempre que possível! Quem sabe como esse código será usado - e se for usado em um loop restrito, pode aumentar o uso de memória desnecessariamente.
Eiko
@Eiko Isso é apenas uma otimização prematura , não pode ser uma orientação geral.
Eonil
11
Eu acho que é mais uma coisa de design, especialmente quando se trabalha em classes de modelo. Considero o aumento da memória como um efeito colateral, e não é isso que quero que apareça com frequência. Pior, outro desenvolvedor que usa meu código não tem chance a não ser envolver chamadas caras em pools de liberação automática (se possível - meus objetos podem ser enviados para outro código de biblioteca). E esses problemas são difíceis de diagnosticar mais tarde, mas baratos para evitar em primeiro lugar. Se você copiar / liberar objetos que foram passados, poderá se perder se eles forem muito maiores do que o esperado. Estou mais relaxado com o código da GUI, no entanto.
Eiko
@Eiko Eu concordo autoreleaseque manterá a memória por mais tempo em geral, e o manual retain/releasepode reduzir o consumo de memória no caso. No entanto, deve ser uma orientação para otimização de caso especial (mesmo que você esteja se sentindo sempre!), Não pode ser o motivo para generalizar a otimização prematura como prática . E, de fato, sua sugestão não é oposta a mim. Eu mencionei-lo como caso de realmente necessidade :)
Eonil