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 que retain/ copy/ autoreleaseos 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:
CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
[self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
Resultado:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
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 Accountobjetos. 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 um initmétodo e um compare:método. O compare: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, AccountAe AccountB. Se você observar a implementação deles, notará que eles são quase totalmente idênticos, com uma exceção: o compare:método. AccountAos objetos acessam suas próprias propriedades pelo método (getter), enquanto os AccountBobjetos 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 AccountAobjetos e outra para AccountBobjetos, 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:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Como você pode ver, classificar a matriz de AccountBobjetos é sempre significativamente mais rápido do que classificar a matriz de AccountAobjetos.
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.marquivo (o código depende da ativação do ARC e use a otimização ao compilar para ver o efeito completo):
#import <Foundation/Foundation.h>
typedef NS_ENUM(int, Gender) {
GenderMale,
GenderFemale
};
@interface AccountA : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountA *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
@interface AccountB : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountB *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
static
NSMutableArray * allAcocuntsA;
static
NSMutableArray * allAccountsB;
static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
assert(min <= max);
uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
rnd = (rnd << 32) | arc4random();
rnd = rnd % ((max + 1) - min); // Trim it to range
return (rnd + min); // Lift it up to min value
}
static
void createAccounts ( const NSUInteger ammount ) {
NSArray *const maleNames = @[
@"Noah", @"Liam", @"Mason", @"Jacob", @"William",
@"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
];
NSArray *const femaleNames = @[
@"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
@"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
];
const NSUInteger nameCount = maleNames.count;
assert(maleNames.count == femaleNames.count); // Better be safe than sorry
allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
allAccountsB = [NSMutableArray arrayWithCapacity:ammount];
for (uint64_t i = 0; i < ammount; i++) {
const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
const unsigned age = (unsigned)getRandom(18, 120);
const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;
NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
NSString *const name = nameArray[nameIndex];
AccountA *const accountA = [[AccountA alloc]
initWithName:name age:age gender:g balance:balance
];
AccountB *const accountB = [[AccountB alloc]
initWithName:name age:age gender:g balance:balance
];
[allAcocuntsA addObject:accountA];
[allAccountsB addObject:accountB];
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
@autoreleasepool {
NSUInteger ammount = 1000000; // 1 Million;
if (argc > 1) {
unsigned long long temp = 0;
if (1 == sscanf(argv[1], "%llu", &temp)) {
// NSUIntegerMax may just be UINT32_MAX!
ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
}
}
createAccounts(ammount);
}
// Sort A and take time
const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;
// Sort B and take time
const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAccountsB sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;
NSLog(@"runTime 1: %f", runTime1);
NSLog(@"runTime 2: %f", runTime2);
}
return 0;
}
@implementation AccountA
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (self.gender != account.gender) {
if (self.gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![self.name isEqualToString:account.name]) {
return [self.name compare:account.name];
}
// Otherwise sort by age, young to old
if (self.age != account.age) {
if (self.age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (self.balance != account.balance) {
if (self.balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end
@implementation AccountB
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (_gender != account.gender) {
if (_gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![_name isEqualToString:account.name]) {
return [_name compare:account.name];
}
// Otherwise sort by age, young to old
if (_age != account.age) {
if (_age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (_balance != account.balance) {
if (_balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end