Per me è di solito una performance. Accedere a un ivar di un oggetto è veloce quanto accedere a un membro di una struttura in C usando un puntatore alla memoria contenente una tale struttura. In effetti, gli oggetti Objective-C sono sostanzialmente strutture C posizionate nella memoria allocata dinamicamente. Di solito è più veloce del tuo codice, nemmeno il codice assembly ottimizzato a mano può essere più veloce di così.
L'accesso a un ivar tramite un getter / impostazione comporta una chiamata al metodo Objective-C, che è molto più lenta (almeno 3-4 volte) di una chiamata di funzione C "normale" e anche una normale chiamata di funzione C sarebbe già più volte più lenta di accesso a un membro struct. A seconda degli attributi della propria proprietà, l'implementazione setter / getter generata dal compilatore può comportare un'altra chiamata di funzione C alle funzioni objc_getProperty
/ objc_setProperty
, poiché queste dovranno retain
/ copy
/ autorelease
gli oggetti in base alle necessità e, se necessario, eseguire ulteriori spin-lock per le proprietà atomiche. Questo può facilmente diventare molto costoso e non sto parlando di essere il 50% più lento.
Proviamo questo:
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);
Produzione:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
Questo è 4,28 volte più lento e questo era un int primitivo non atomico, praticamente il caso migliore ; la maggior parte degli altri casi è anche peggiore (prova una NSString *
proprietà atomica !). Quindi, se riesci a convivere con il fatto che ogni accesso agli ivar è 4-5 volte più lento di quanto potrebbe essere, l'uso delle proprietà va bene (almeno quando si tratta di prestazioni), tuttavia, ci sono molte situazioni in cui tale calo delle prestazioni è completamente inaccettabile.
Aggiornamento 20-10-2015
Alcune persone sostengono che questo non è un problema del mondo reale, il codice sopra è puramente sintetico e non lo noterai mai in una vera applicazione. Va bene allora, proviamo un campione del mondo reale.
Il seguente codice definisce Account
oggetti. Un account ha proprietà che descrivono name ( NSString *
), gender ( enum
) ed age ( unsigned
) del suo proprietario, nonché un saldo ( int64_t
). Un oggetto account ha un init
metodo e un compare:
metodo. Il compare:
metodo è definito come: ordini femminili prima del maschio, nomi in ordine alfabetico, ordini giovani prima dei vecchi, ordini del saldo da basso a alto.
In realtà esistono due classi di account AccountA
e AccountB
. Se osservi la loro implementazione, noterai che sono quasi del tutto identici, con una sola eccezione: il compare:
metodo. AccountA
gli oggetti accedono alle proprie proprietà con il metodo (getter), mentre gli AccountB
oggetti accedono alle proprie proprietà con ivar. Questa è davvero l'unica differenza! Entrambi accedono alle proprietà dell'altro oggetto a cui confrontare con getter (accedervi con ivar non sarebbe sicuro! E se l'altro oggetto fosse una sottoclasse e ha scavalcato il getter?). Si noti inoltre che l'accesso alle proprie proprietà come ivar non interrompe l'incapsulamento (gli ivar non sono ancora pubblici).
La configurazione del test è davvero semplice: crea 1 account casuale Mio, aggiungili a un array e ordina quell'array. Questo è tutto. Naturalmente, ci sono due array, uno per gli AccountA
oggetti e uno per gli AccountB
oggetti ed entrambi gli array sono riempiti con account identici (stessa origine dati). Dedichiamo il tempo necessario per ordinare le matrici.
Ecco l'output di diverse esecuzioni che ho fatto ieri:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Come puoi vedere, l'ordinamento dell'array di AccountB
oggetti è sempre significativamente più veloce dell'ordinamento dell'array di AccountA
oggetti.
Chiunque sostenga che differenze di runtime fino a 1,32 secondi non fanno alcuna differenza non dovrebbe mai fare la programmazione dell'interfaccia utente. Se voglio cambiare l'ordine di ordinamento di una tabella di grandi dimensioni, ad esempio, differenze di tempo come queste fanno un'enorme differenza per l'utente (la differenza tra un'interfaccia utente accettabile e una lenta).
Anche in questo caso il codice di esempio è l'unico vero lavoro eseguito qui, ma con quale frequenza il tuo codice è solo un piccolo ingranaggio di un orologio complicato? E se ogni marcia rallenta l'intero processo in questo modo, cosa significa per la velocità dell'intero orologio alla fine? Soprattutto se un passaggio di lavoro dipende dall'output di un altro, il che significa che tutte le inefficienze si sommeranno. La maggior parte delle inefficienze non è un problema da sola, è la loro somma che diventa un problema per l'intero processo. E un problema del genere è nulla che un profiler mostrerà facilmente perché un profiler si occupa di trovare hot spot critici, ma nessuna di queste inefficienze è hot spot da sola. Il tempo della CPU è solo mediamente ripartito tra loro, ma ognuno di essi ne ha solo una minima parte, sembra una perdita di tempo totale per ottimizzarlo. Ed è vero
E anche se non pensi in termini di tempo della CPU, perché ritieni che sprecare il tempo della CPU sia del tutto accettabile, dopo tutto "è gratis", che dire dei costi di hosting del server causati dal consumo di energia? Che dire della durata della batteria dei dispositivi mobili? Se dovessi scrivere due volte la stessa app mobile (ad es. Un browser Web mobile), una volta una versione in cui tutte le classi accedono alle proprie proprietà solo tramite i getter e una volta in cui tutte le classi accedono a esse solo tramite gli ivar, l'utilizzo costante della prima sicuramente esaurirà la batteria molto più veloce rispetto all'utilizzo della seconda, anche se sono equivalenti dal punto di vista funzionale e per l'utente la seconda potrebbe anche sembrare un po 'più veloce.
Ora ecco il codice per il tuo main.m
file (il codice si basa sull'attivazione di ARC e assicurati di utilizzare l'ottimizzazione durante la compilazione per vedere l'effetto 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