Pour moi, c'est généralement la performance. Accéder à un ivar d'un objet est aussi rapide que d'accéder à un membre struct en C en utilisant un pointeur vers la mémoire contenant un tel struct. En fait, les objets Objective-C sont essentiellement des structures C situées dans une mémoire allouée dynamiquement. C'est généralement aussi rapide que votre code peut obtenir, même le code d'assemblage optimisé à la main ne peut pas être plus rapide que cela.
L'accès à un ivar via un getter / paramètre implique un appel de méthode Objective-C, qui est beaucoup plus lent (au moins 3-4 fois) qu'un appel de fonction C "normal" et même un appel de fonction C normal serait déjà plusieurs fois plus lent que accéder à un membre de structure. Selon les attributs de votre propriété, l'implémentation setter / getter générée par le compilateur peut impliquer un autre appel de fonction C aux fonctions objc_getProperty
/ objc_setProperty
, car celles-ci devront retain
/ copy
/ autorelease
les objets selon les besoins et effectuer en outre le verrouillage de rotation pour les propriétés atomiques si nécessaire. Cela peut facilement devenir très coûteux et je ne parle pas d'être 50% plus lent.
Essayons ça:
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);
Production:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
C'est 4,28 fois plus lent et c'était un int primitif non atomique, à peu près le meilleur des cas ; la plupart des autres cas sont encore pires (essayez unNSString *
propriété !). Donc, si vous pouvez vivre avec le fait que chaque accès ivar est 4 à 5 fois plus lent qu'il ne pourrait l'être, utiliser les propriétés est bien (au moins en ce qui concerne les performances), cependant, il existe de nombreuses situations dans lesquelles une telle baisse de performance est complètement inacceptable.
Mise à jour 20/10/2015
Certaines personnes affirment que ce n'est pas un problème du monde réel, le code ci-dessus est purement synthétique et vous ne le remarquerez jamais dans une application réelle. Bon alors, essayons un échantillon du monde réel.
Le code ci-dessous définit les Account
objets. Un compte a des propriétés qui décrivent le nom ( NSString *
), le sexe ( enum
) et l'âge ( unsigned
) de son propriétaire, ainsi qu'un solde ( int64_t
). Un objet de compte a une init
méthode et uncompare:
méthode. lecompare:
méthode est définie comme suit: Ordres féminins avant les hommes, ordre alphabétique des noms, ordres jeunes avant vieux, ordres d'équilibre de bas en haut.
En fait, il existe deux classes de comptes, AccountA
et AccountB
. Si vous regardez leur implémentation, vous remarquerez qu'elles sont presque entièrement identiques, à une exception près: la compare:
méthode. AccountA
les objets accèdent à leurs propres propriétés par méthode (getter), tandis que les AccountB
objets accèdent à leurs propres propriétés par ivar. C'est vraiment la seule différence! Ils accèdent tous les deux aux propriétés de l'autre objet à comparer par getter (y accéder par ivar ne serait pas sûr! Et si l'autre objet est une sous-classe et a remplacé le getter?). Notez également que l'accès à vos propres propriétés en tant qu'ivars ne rompt pas l'encapsulation (les ivars ne sont toujours pas publics).
La configuration du test est vraiment simple: créez 1 Mio de comptes aléatoires, ajoutez-les à un tableau et triez ce tableau. C'est tout. Bien sûr, il existe deux tableaux, un pour les AccountA
objets et un pour les AccountB
objets et les deux tableaux sont remplis de comptes identiques (même source de données). Nous chronométrons le temps nécessaire pour trier les tableaux.
Voici la sortie de plusieurs courses que j'ai faites hier:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
Comme vous pouvez le voir, le tri du tableau d' AccountB
objets est toujours plus rapide que le tri du tableau d' AccountA
objets.
Quiconque prétend que des différences d'exécution allant jusqu'à 1,32 seconde ne font aucune différence devrait mieux ne jamais faire de programmation d'interface utilisateur. Si je veux changer l'ordre de tri d'une grande table, par exemple, des différences de temps comme celles-ci font une énorme différence pour l'utilisateur (la différence entre une interface utilisateur acceptable et une interface utilisateur lente).
Dans ce cas également, l'exemple de code est le seul vrai travail effectué ici, mais à quelle fréquence votre code est-il juste un petit engrenage d'une horloge compliquée? Et si chaque engrenage ralentit tout le processus comme celui-ci, qu'est-ce que cela signifie pour la vitesse de l'ensemble du mouvement d'horlogerie à la fin? Surtout si une étape de travail dépend de la sortie d'une autre, ce qui signifie que toutes les inefficacités se résumeront. La plupart des inefficacités ne sont pas un problème en soi, c'est leur somme qui devient un problème pour l'ensemble du processus. Et un tel problème n'est rien qu'un profileur montrera facilement, car un profileur consiste à trouver des points chauds critiques, mais aucune de ces inefficacités n'est en soi des points chauds. Le temps processeur n'est que moyennement réparti entre eux, mais chacun d'entre eux n'en a qu'une infime fraction, il semble que l'optimiser soit une perte de temps totale. Et c'est vrai,
Et même si vous ne pensez pas en termes de temps CPU, parce que vous pensez que gaspiller du temps CPU est tout à fait acceptable, après tout «c'est gratuit», qu'en est-il des coûts d'hébergement de serveur causés par la consommation d'énergie? Qu'en est-il de l'autonomie de la batterie des appareils mobiles? Si vous écrivez la même application mobile deux fois (par exemple un propre navigateur Web mobile), une fois une version où toutes les classes accèdent à leurs propres propriétés uniquement par des getters et une fois où toutes les classes y accèdent uniquement par ivars, utiliser le premier constamment épuisera définitivement la batterie beaucoup plus rapide que l'utilisation de la seconde, même si elles sont fonctionnelles équivalentes et pour l'utilisateur la seconde serait même probablement même un peu plus rapide.
Voici maintenant le code de votre main.m
fichier (le code repose sur l'activation d'ARC et assurez-vous d'utiliser l'optimisation lors de la compilation pour voir l'effet complet):
#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