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
/ 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:
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 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 um init
mé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, AccountA
e 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. AccountA
os objetos acessam suas próprias propriedades pelo método (getter), enquanto os AccountB
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 para AccountB
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:
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 AccountB
objetos é sempre significativamente mais rápido do que classificar a matriz de AccountA
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):
#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