Normalmente, vejo essa pergunta de outra maneira, como Todo ivar deve ser uma propriedade? (e eu gosto da resposta do bbum a este Q).
Eu uso propriedades quase exclusivamente no meu código. De vez em quando, no entanto, trabalho com um empreiteiro que desenvolve o iOS há muito tempo e é um programador de jogos tradicional. Ele escreve um código que declara quase nenhuma propriedade e se apóia em ivars. Suponho que ele faça isso porque 1.) ele está acostumado a isso, pois as propriedades nem sempre existiam até o Objetivo C 2.0 (outubro '07) e 2.) para o ganho de desempenho mínimo de não passar por um getter / setter.
Enquanto ele escreve um código que não vaza, eu ainda prefiro que ele use propriedades sobre ivars. Nós conversamos sobre isso e ele mais ou menos não vê razão para usar propriedades, já que não estávamos usando o KVO e ele tem experiência em cuidar de problemas de memória.
Minha pergunta é mais ... Por que você gostaria de usar um período de ivar - experiente ou não. Existe realmente uma grande diferença de desempenho que justificasse o uso de um ivar?
Também como um ponto de esclarecimento, substituo setters e getters conforme necessário e uso o ivar que se correlaciona com essa propriedade dentro do getter / setter. No entanto, fora de um getter / setter ou init, eu sempre uso a self.myProperty
sintaxe.
Editar 1
Agradeço todas as boas respostas. Um que eu gostaria de abordar que parece incorreto é que, com um ivar, você obtém um encapsulamento onde, com uma propriedade que não recebe. Apenas defina a propriedade em uma continuação de classe. Isso ocultará a propriedade de pessoas de fora. Você também pode declarar a propriedade somente leitura na interface e redefini-la como readwrite na implementação, como:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
e tem na continuação da aula:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
Tê-lo completamente "privado" apenas o declara na continuação da classe.
Respostas:
Encapsulamento
Se o ivar é privado, as outras partes do programa não conseguem chegar com tanta facilidade. Com uma propriedade declarada, as pessoas inteligentes podem acessar e alterar facilmente através dos acessadores.
atuação
Sim, isso pode fazer a diferença em alguns casos. Alguns programas têm restrições nas quais eles não podem usar nenhuma mensagem objc em determinadas partes do programa (pense em tempo real). Em outros casos, convém acessá-lo diretamente para obter velocidade. Em outros casos, é porque as mensagens objc atuam como um firewall de otimização. Por fim, pode reduzir as operações de contagem de referência e minimizar o pico de uso da memória (se feito corretamente).
Tipos não triviais
Exemplo: se você possui um tipo C ++, o acesso direto é a melhor abordagem algumas vezes. O tipo pode não ser copiável ou pode não ser trivial copiar.
Multithreading
Muitos de seus ivars são co-dependentes. Você deve garantir a integridade dos dados no contexto multithread. Portanto, você pode favorecer o acesso direto a vários membros em seções críticas. Se você ficar com os acessadores para obter dados dependentes de código, seus bloqueios normalmente deverão ser reentrantes e, com frequência, você acabará fazendo muito mais aquisições (significativamente mais às vezes).
Correção do programa
Como as subclasses podem substituir qualquer método, você pode eventualmente perceber que há uma diferença semântica entre gravar na interface e gerenciar seu estado adequadamente. O acesso direto à correção do programa é especialmente comum em estados parcialmente construídos - nos inicializadores e no
dealloc
, é melhor usar o acesso direto. Você também pode encontrar este comuns nas implementações de um assessor, um construtor de conveniência,copy
,mutableCopy
implementações, e arquivamento / serialização.Também é mais frequente à medida que se muda da mentalidade do acessador de readwrite para tudo, para uma que oculta bem os dados / detalhes da implementação. Às vezes, você precisa contornar corretamente os efeitos colaterais que a substituição de uma subclasse pode introduzir para fazer a coisa certa.
Tamanho binário
Declarar tudo como readwrite por padrão geralmente resulta em muitos métodos de acessador dos quais você nunca precisa, quando considera a execução do seu programa por um momento. Por isso, adicionará um pouco de gordura ao seu programa e também aumenta o tempo de carregamento.
Minimiza a complexidade
Em alguns casos, é completamente desnecessário adicionar + type + manter todo esse andaime extra para uma variável simples, como um bool privado que é escrito em um método e lido em outro.
Isso não significa que o uso de propriedades ou acessores seja ruim - cada um possui importantes benefícios e restrições. Como muitas linguagens OO e abordagens de design, você também deve favorecer os acessadores com visibilidade adequada no ObjC. Haverá momentos em que você precisará se desviar. Por esse motivo, acho que geralmente é melhor restringir acessos diretos à implementação que declara o ivar (por exemplo, declare-o
@private
).re Editar 1:
Muitos de nós memorizamos como chamar um acessador oculto dinamicamente (desde que conheçamos o nome…). Enquanto isso, a maioria de nós não memorizou como acessar corretamente os ivars que não são visíveis (além do KVC). A continuação da classe ajuda , mas introduz vulnerabilidades.
Esta solução alternativa é óbvia:
Agora tente com apenas um ivar e sem KVC.
fonte
@private
, o compilador deve proibir o acesso de membros fora dos métodos de classe e instância - não é isso que você vê?Para mim, geralmente é desempenho. Acessar um ivar de um objeto é tão rápido quanto acessar um membro struct em C usando um ponteiro para a memória que contém essa estrutura. De fato, os objetos Objective-C são basicamente estruturas C localizadas na memória alocada dinamicamente. Isso geralmente é o mais rápido que seu código pode obter, nem mesmo o código de montagem otimizado manualmente pode ser mais rápido que isso.
Acessar um ivar através de um getter / configuração envolve uma chamada de método Objective-C, que é muito mais lenta (pelo menos 3-4 vezes) que uma chamada de função C "normal" e até mesmo uma chamada de função C normal já seria várias vezes mais lenta que acessando um membro struct. Dependendo dos atributos de sua propriedade, a implementação do setter / getter gerada pelo compilador pode envolver outra chamada de função C para as funções
objc_getProperty
/objc_setProperty
, pois elas terão queretain
/copy
/autorelease
os objetos conforme necessário e, posteriormente, realizarão spinlocking para propriedades atômicas. Isso pode facilmente ficar muito caro e não estou falando de ser 50% mais lento.Vamos tentar isso:
Resultado:
Isso é 4,28 vezes mais lento e esse foi um int primitivo não atômico, praticamente o melhor caso ; a maioria dos outros casos é ainda pior (tente um teste atômico).
NSString *
propriedade !). Portanto, se você pode conviver com o fato de que cada acesso ao ivar é 4-5 vezes mais lento do que poderia ser, o uso de propriedades é bom (pelo menos no que diz respeito ao desempenho), no entanto, existem muitas situações em que essa queda de desempenho é completamente inaceitável.Atualização 20/10/2015
Algumas pessoas argumentam que esse não é um problema do mundo real, o código acima é puramente sintético e você nunca notará isso em um aplicativo real. Ok, então, vamos tentar uma amostra do mundo real.
O código a seguir define
Account
objetos. Uma conta possui propriedades que descrevem o nome (NSString *
), sexo (enum
) e idade (unsigned
) de seu proprietário, além de um saldo (int64_t
). Um objeto de conta tem uminit
método e umcompare:
método. Ocompare:
método é definido como: pedidos femininos antes do masculino, nomes ordenados alfabeticamente, pedidos jovens antes do antigo, saldo de pedidos baixo para alto.Na verdade, existem duas classes de conta,
AccountA
eAccountB
. Se você observar a implementação deles, notará que eles são quase totalmente idênticos, com uma exceção: ocompare:
método.AccountA
os objetos acessam suas próprias propriedades pelo método (getter), enquanto osAccountB
objetos acessam suas próprias propriedades pelo ivar. Essa é realmente a única diferença! Ambos acessam as propriedades do outro objeto para comparar com o getter (acessá-lo pelo ivar não seria seguro! E se o outro objeto for uma subclasse e tiver substituído o getter?). Observe também que acessar suas próprias propriedades como ivars não quebra o encapsulamento (os ivars ainda não são públicos).A configuração do teste é realmente simples: crie contas aleatórias de 1 milhão, adicione-as a uma matriz e classifique-a. É isso aí. Obviamente, existem duas matrizes, uma para
AccountA
objetos e outra paraAccountB
objetos, e ambas são preenchidas com contas idênticas (mesma fonte de dados). Cronometramos quanto tempo leva para classificar as matrizes.Aqui está a saída de várias execuções que fiz ontem:
Como você pode ver, classificar a matriz de
AccountB
objetos é sempre significativamente mais rápido do que classificar a matriz deAccountA
objetos.Quem quer que afirme que diferenças de tempo de execução de até 1,32 segundos não fazem diferença, nunca deve fazer a programação da interface do usuário. Se eu quiser alterar a ordem de classificação de uma tabela grande, por exemplo, diferenças de horário como essas fazem uma enorme diferença para o usuário (a diferença entre uma interface do usuário aceitável e uma interface lenta).
Também nesse caso, o código de exemplo é o único trabalho real realizado aqui, mas com que frequência seu código é apenas uma pequena engrenagem de um relógio complicado? E se todo equipamento atrasar todo o processo como este, o que isso significa para a velocidade de todo o relógio no final? Especialmente se uma etapa do trabalho depende da saída de outra, o que significa que todas as ineficiências serão somadas. A maioria das ineficiências não é um problema por si só, é a soma total que se torna um problema para todo o processo. E esse problema não é nada que um criador de perfil mostre facilmente, porque ele trata de encontrar pontos críticos críticos, mas nenhuma dessas ineficiências são pontos críticos por si só. O tempo da CPU é distribuído apenas entre eles, mas cada um deles possui apenas uma fração tão pequena que parece uma perda total de tempo para otimizá-lo. E é verdade,
E mesmo que você não pense em termos de tempo de CPU, porque acredita que desperdiçar tempo de CPU é totalmente aceitável, afinal "é de graça", e quanto aos custos de hospedagem de servidores causados pelo consumo de energia? E quanto ao tempo de execução da bateria de dispositivos móveis? Se você escrever o mesmo aplicativo móvel duas vezes (por exemplo, um navegador da web móvel próprio), uma vez em que todas as classes acessem suas próprias propriedades apenas por getters e uma vez em que todas as classes os acessem apenas por ivars, usar o primeiro constantemente irá drenar definitivamente a bateria é muito mais rápida do que usar a segunda, mesmo que seja equivalente funcional e, para o usuário, a segunda provavelmente se sentiria um pouco mais rápida.
Agora, aqui está o código do seu
main.m
arquivo (o código depende da ativação do ARC e use a otimização ao compilar para ver o efeito completo):fonte
unsigned int
que nunca é retido / liberado, se você usa o ARC ou não. A retenção / liberação em si é cara, portanto a diferença será menor, pois o gerenciamento de retenção adiciona uma sobrecarga estática que sempre existe, usando o setter / getter ou ivar diretamente; no entanto, você ainda salvará a sobrecarga de uma chamada de método extra se acessar o ivar diretamente. Na maioria dos casos, não é grande coisa, a menos que você esteja fazendo isso milhares de vezes por segundo. A Apple diz que usa getters / setters por padrão, a menos que você esteja em um método init / dealloc ou tenha um gargalo.copy
vai NÃO fazer uma cópia de seu valor cada vez que você acessá-lo. O getter decopy
propriedade é como o getter de umstrong
/retain
property. Seu código é basicamentereturn [[self->value retain] autorelease];
. Somente o setter copia o valor e ele ficará mais ou menos assim[self->value autorelease]; self->value = [newValue copy];
, enquanto umstrong
/retain
setter fica assim:[self->value autorelease]; self->value = [newValue retain];
O motivo mais importante é o conceito OOP de ocultação de informações : se você expõe tudo por meio de propriedades e permite que objetos externos espiem as partes internas de outro objeto, você as utilizará internas e, portanto, complicará a alteração da implementação.
O ganho de "desempenho mínimo" pode resumir rapidamente e se tornar um problema. Eu sei por experiência; Trabalho em um aplicativo que realmente leva os iDevices a seus limites e, portanto, precisamos evitar chamadas de métodos desnecessárias (é claro, apenas quando razoavelmente possível). Para ajudar nesse objetivo, também estamos evitando a sintaxe do ponto, pois dificulta a visualização do número de chamadas de método à primeira vista: por exemplo, quantas chamadas de método a expressão
self.image.size.width
dispara? Por outro lado, você pode contar imediatamente com[[self image] size].width
.Além disso, com a nomeação correta de ivar, o KVO é possível sem propriedades (IIRC, eu não sou especialista em KVO).
fonte
Semântica
@property
pode expressar que os ivars não podem:nonatomic
ecopy
.@property
não podem:@protected
: público em subclasses, privado fora.@package
: público em estruturas em 64 bits, privado fora. O mesmo que@public
em 32 bits. Consulte Controle de acesso variável de classe e instância de 64 bits da Apple .id __strong *_objs
.atuação
Resumindo: os ivars são mais rápidos, mas isso não importa para a maioria dos usos.
nonatomic
As propriedades não usam bloqueios, mas o ivar direto é mais rápido porque ignora a chamada dos acessadores. Para detalhes, leia o seguinte email em lists.apple.com.fonte
Propriedades versus variáveis de instância é uma troca, no final, a escolha se resume ao aplicativo.
Encapsulamento / ocultação de informações Isso é uma coisa boa (TM) do ponto de vista do design, interfaces estreitas e ligação mínima é o que torna o software sustentável e compreensível. É muito difícil no Obj-C ocultar qualquer coisa, mas as variáveis de instância declaradas na implementação chegam o mais perto possível.
Desempenho Embora a "otimização prematura" seja uma coisa ruim (TM), escrever código com desempenho ruim apenas porque você pode ser pelo menos tão ruim. É difícil argumentar que uma chamada de método é mais cara que uma carga ou loja e, em código intensivo de computação, o custo logo aumenta.
Em uma linguagem estática com propriedades, como C #, as chamadas para setters / getters geralmente podem ser otimizadas pelo compilador. No entanto, o Obj-C é dinâmico e a remoção dessas chamadas é muito mais difícil.
Abstração Um argumento contra variáveis de instância no Obj-C é tradicionalmente o gerenciamento de memória. Com a instância do MRC, as variáveis exigem que as chamadas de retenção / liberação / liberação automática sejam espalhadas por todo o código, as propriedades (sintetizadas ou não) mantêm o código MRC em um único local - o princípio da abstração que é uma Good Thing (TM). No entanto, com GC ou ARC esse argumento desaparece, portanto a abstração para gerenciamento de memória não é mais um argumento contra variáveis de instância.
fonte
Propriedades expõem suas variáveis a outras classes. Se você só precisa de uma variável que seja apenas relativa à classe que está criando, use uma variável de instância. Aqui está um pequeno exemplo: as classes XML para analisar RSS e similares passam por vários métodos delegados e coisas do tipo. É prático ter uma instância do NSMutableString para armazenar o resultado de cada passo diferente da análise. Não há razão para que uma classe externa precise acessar ou manipular essa string. Então, você apenas a declara no cabeçalho ou em particular e a acessa por toda a classe. Definir uma propriedade para ela pode ser útil apenas para garantir que não haja problemas de memória, usando self.mutableString para chamar o getter / setters.
fonte
A compatibilidade com versões anteriores foi um fator para mim. Não pude usar nenhum recurso do Objective-C 2.0 porque estava desenvolvendo softwares e drivers de impressora que precisavam funcionar no Mac OS X 10.3 como parte de um requisito. Sei que sua pergunta parecia direcionada ao iOS, mas pensei em compartilhar meus motivos para não usar propriedades.
fonte