Warum würden Sie einen Ivar verwenden?


153

Normalerweise sehe ich diese Frage andersherum gestellt , z. B. Muss jeder Ivar eine Eigenschaft sein? (und ich mag bbums Antwort auf dieses Q).

Ich verwende Eigenschaften fast ausschließlich in meinem Code. Ab und zu arbeite ich jedoch mit einem Auftragnehmer zusammen, der sich schon lange auf iOS entwickelt und ein traditioneller Spielprogrammierer ist. Er schreibt Code, der fast keine Eigenschaften deklariert und sich auf Ivars stützt. Ich gehe davon aus, dass er dies tut, weil 1.) er daran gewöhnt ist, da Eigenschaften erst in Objective C 2.0 (Okt.07) und 2.) für den minimalen Leistungsgewinn vorhanden waren, wenn kein Getter / Setter durchlaufen wurde.

Während er Code schreibt, der nicht leckt, würde ich es dennoch vorziehen, wenn er Eigenschaften gegenüber Ivars verwendet. Wir haben darüber gesprochen und er sieht mehr oder weniger keinen Grund, Eigenschaften zu verwenden, da wir KVO nicht verwendet haben und er Erfahrung darin hat, sich um die Speicherprobleme zu kümmern.

Meine Frage ist mehr ... Warum sollten Sie jemals eine Ivar-Periode verwenden wollen - erfahren oder nicht. Gibt es wirklich einen so großen Leistungsunterschied, dass die Verwendung eines Ivar gerechtfertigt wäre?

Zur Verdeutlichung überschreibe ich Setter und Getter nach Bedarf und verwende den Ivar, der mit dieser Eigenschaft im Getter / Setter korreliert. Außerhalb eines Getters / Setters oder Init verwende ich jedoch immer die self.myPropertySyntax.


Bearbeiten 1

Ich schätze all die guten Antworten. Eine, die ich ansprechen möchte, die falsch erscheint, ist, dass Sie mit einem Ivar eine Kapselung erhalten, während Sie mit einer Eigenschaft dies nicht tun. Definieren Sie einfach die Eigenschaft in einer Klassenfortsetzung. Dadurch wird die Immobilie vor Außenstehenden verborgen. Sie können die Eigenschaft auch in der Schnittstelle schreibgeschützt deklarieren und in der Implementierung wie folgt als readwrite neu definieren:

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

und haben in der Klasse Fortsetzung:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

Um es vollständig "privat" zu haben, deklarieren Sie es nur in der Klassenfortsetzung.


2
Upvote für interessante Fragen - gut ausgedrückt und auch eine, die ich gerne für Ivars hören würde, da es so klingt, als ob mir beigebracht wurde, wie Sam es macht.
Damo

2
Beachten Sie, dass bei der automatischen Referenzzählung (ARC) für ivars dieselben Vorteile für die Speicherverwaltung gelten wie für Eigenschaften. Im ARC-Code besteht der Unterschied also wirklich in der Kapselung.
Benzado

1
Ihre Frage und insbesondere der Teil Bearbeiten 1 sind tatsächlich viel informativer als die gewählte Antwort.
user523234

1
Zu Edit1: Ich denke, es ist möglich, jede Eigenschaft zu lesen und zu schreiben, selbst wenn nur eine Deklaration von readonly in .h mit Schlüsselwertcodierung vorliegt, z. B.: [Object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyyname "];
Binarian

1
@Sam zu Ihrer Bearbeitung 1: Wenn Sie eine private Eigenschaft verwenden und die Klassenerweiterung / -fortsetzung in der .m-Datei verwenden, ist diese für Unterklassen nicht sichtbar. Sie müssen den Code erneut schreiben oder eine andere .h-Datei mit der Klassenerweiterung verwenden. Einfacher mit @ protected / default.
Binarian

Antworten:


100

Verkapselung

Wenn der ivar privat ist, können die anderen Teile des Programms nicht so leicht darauf zugreifen. Mit einem deklarierten Eigentum können die cleveren Leute über die Accessoren ganz einfach darauf zugreifen und mutieren.

Performance

Ja, dies kann in einigen Fällen einen Unterschied machen. Einige Programme haben Einschränkungen, bei denen sie in bestimmten Teilen des Programms keine Objektnachrichten verwenden können (denken Sie an Echtzeit). In anderen Fällen möchten Sie möglicherweise aus Geschwindigkeitsgründen direkt darauf zugreifen. In anderen Fällen liegt dies daran, dass objc messaging als Optimierungsfirewall fungiert. Schließlich kann es Ihre Referenzzählvorgänge reduzieren und die maximale Speichernutzung minimieren (wenn dies korrekt durchgeführt wird).

Nichttriviale Typen

Beispiel: Wenn Sie einen C ++ - Typ haben, ist der direkte Zugriff manchmal nur der bessere Ansatz. Der Typ ist möglicherweise nicht kopierbar oder nicht trivial zu kopieren.

Multithreading

Viele Ihrer Ivars sind codependent. Sie müssen Ihre Datenintegrität im Multithread-Kontext sicherstellen. Daher können Sie den direkten Zugriff auf mehrere Mitglieder in kritischen Abschnitten bevorzugen. Wenn Sie sich an Accessoren für codependente Daten halten, müssen Ihre Sperren in der Regel wiedereintrittsfähig sein, und Sie werden häufig viel mehr Akquisitionen tätigen (manchmal erheblich mehr).

Programmkorrektheit

Da die Unterklassen jede Methode überschreiben können, können Sie möglicherweise feststellen, dass es einen semantischen Unterschied zwischen dem Schreiben in die Schnittstelle und der angemessenen Verwaltung Ihres Status gibt. Der direkte Zugriff auf Programmkorrektheit ist besonders häufig in teilweise erstellten Zuständen - in Ihren Initialisierern und in deallocist es am besten, den direkten Zugriff zu verwenden. Sie können auch diese gemeinsame in den Implementierungen eines Zugriffs finden, Bequemlichkeit Konstruktor copy, mutableCopyund die Archivierung / Serialisierung - Implementierungen.

Es ist auch häufiger, wenn man von der Einstellung eines öffentlichen Readwrite-Accessors zu einer Einstellung übergeht , die die Implementierungsdetails / -daten gut verbirgt. Manchmal müssen Sie die Nebenwirkungen, die durch das Überschreiben einer Unterklasse auftreten können, korrekt umgehen, um das Richtige zu tun.

Binärgröße

Das standardmäßige Deklarieren von Readwrite führt normalerweise zu vielen Zugriffsmethoden, die Sie nie benötigen, wenn Sie die Ausführung Ihres Programms für einen Moment in Betracht ziehen. So wird Ihrem Programm und den Ladezeiten auch etwas Fett hinzugefügt.

Minimiert die Komplexität

In einigen Fällen ist es einfach völlig unnötig, + type + all das zusätzliche Gerüst für eine einfache Variable wie einen privaten Bool hinzuzufügen, der in einer Methode geschrieben und in einer anderen gelesen wird.


Das heißt überhaupt nicht, dass die Verwendung von Eigenschaften oder Accessoren schlecht ist - jede hat wichtige Vorteile und Einschränkungen. Wie viele OO-Sprachen und Designansätze sollten Sie auch Accessoren mit angemessener Sichtbarkeit in ObjC bevorzugen. Es wird Zeiten geben, in denen Sie abweichen müssen. Aus diesem Grund denke ich, dass es oft am besten ist, den direkten Zugriff auf die Implementierung zu beschränken, die den ivar deklariert (z @private. B. deklariert ).


erneut bearbeiten 1:

Die meisten von uns haben auswendig gelernt, wie man einen versteckten Accessor dynamisch aufruft (solange wir den Namen kennen…). In der Zwischenzeit haben die meisten von uns nicht auswendig gelernt, wie sie richtig auf nicht sichtbare Ivars zugreifen können (außerhalb von KVC). Die Klassenfortsetzung hilft , führt jedoch zu Sicherheitslücken.

Diese Problemumgehung liegt auf der Hand:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

Versuchen Sie es jetzt nur mit einem Ivar und ohne KVC.


@ Sam danke und gute Frage! Bezüglich der Komplexität: Es geht sicherlich in beide Richtungen. Re Kapselung - aktualisiert
Justin

@bbum RE: Spezielles Beispiel Obwohl ich Ihnen zustimme, dass es die falsche Lösung ist, kann ich mir nicht vorstellen, dass es viele erfahrene Objektentwickler gibt, die glauben, dass es einfach nicht passiert. Ich habe es in anderen Programmen gesehen und die App Stores haben sogar die Verwendung privater Apple-APIs verboten.
Justin

1
Kannst du nicht mit object-> foo auf einen privaten ivar zugreifen? Nicht so schwer zu merken.
Nick Lockwood

1
Ich meinte, Sie können mit einem Zeiger auf das Objekt zugreifen, indem Sie die Syntax C -> verwenden. Objective-C-Klassen sind im Grunde genommen nur Strukturen unter der Haube. Mit einem Zeiger auf eine Struktur lautet die C-Syntax für den Zugriff auf Mitglieder ->, was auch für ivars in objektiven C-Klassen funktioniert.
Nick Lockwood

1
@NickLockwood Wenn der ivar ist @private, sollte der Compiler dem Mitglied den Zugriff außerhalb der Klassen- und Instanzmethoden verbieten - sehen Sie das nicht?
Justin

76

Für mich ist es normalerweise Leistung. Der Zugriff auf einen ivar eines Objekts ist so schnell wie der Zugriff auf ein Strukturelement in C mithilfe eines Zeigers auf den Speicher, der eine solche Struktur enthält. Tatsächlich sind Objective-C-Objekte im Grunde C-Strukturen, die sich im dynamisch zugewiesenen Speicher befinden. Dies ist normalerweise so schnell, wie Ihr Code nur sein kann. Nicht einmal handoptimierter Assembler-Code kann schneller sein.

Der Zugriff auf ein ivar über einen Getter / eine Einstellung umfasst einen Objective-C-Methodenaufruf, der viel langsamer (mindestens 3-4 Mal) als ein "normaler" C-Funktionsaufruf ist, und selbst ein normaler C-Funktionsaufruf wäre bereits um ein Vielfaches langsamer als Zugriff auf ein Strukturelement. Abhängig von den Attributen Ihrer Eigenschaft kann die vom Compiler generierte Setter / Getter-Implementierung einen weiteren C-Funktionsaufruf für die Funktionen objc_getProperty/ beinhalten objc_setProperty, da diese retain/ copy/ autoreleasedie Objekte nach Bedarf benötigen und bei Bedarf ein Spinlocking für atomare Eigenschaften durchführen. Dies kann leicht sehr teuer werden und ich spreche nicht davon, 50% langsamer zu sein.

Lass uns das versuchen:

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);

Ausgabe:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

Dies ist 4,28-mal langsamer und dies war ein nicht-atomares primitives int, so ziemlich der beste Fall ; Die meisten anderen Fälle sind noch schlimmer (versuchen Sie es mit einer atomaren NSString *Eigenschaft!). Wenn Sie also damit leben können, dass jeder ivar-Zugriff 4-5 Mal langsamer ist als er sein könnte, ist die Verwendung von Eigenschaften in Ordnung (zumindest was die Leistung betrifft). Es gibt jedoch viele Situationen, in denen ein solcher Leistungsabfall auftritt völlig inakzeptabel.

Update 20.10.2015

Einige Leute argumentieren, dass dies kein Problem der realen Welt ist, der obige Code ist rein synthetisch und Sie werden das in einer realen Anwendung nie bemerken. Okay, dann probieren wir ein Beispiel aus der realen Welt.

Der folgende Code definiert AccountObjekte. Ein Konto verfügt über Eigenschaften, die den Namen ( NSString *), das Geschlecht ( enum) und das Alter ( unsigned) seines Besitzers beschreiben, sowie über einen Kontostand ( int64_t). Ein Kontoobjekt hat eine initMethode und eine compare:Methode. Die compare:Methode ist definiert als: Weibliche Bestellungen vor Männern, Namen alphabetisch sortiert, junge Bestellungen vor alten, Balance-Bestellungen niedrig bis hoch.

Tatsächlich gibt es zwei Kontoklassen AccountAund AccountB. Wenn Sie sich ihre Implementierung ansehen, werden Sie feststellen, dass sie fast vollständig identisch sind, mit einer Ausnahme: Die compare:Methode. AccountAObjekte zugreifen ihre eigenen Eigenschaften durch Methode (Getter), während die AccountBObjekte zugreifen , ihre eigenen Eigenschaften von Ivar. Das ist wirklich der einzige Unterschied! Beide greifen auf die Eigenschaften des anderen Objekts zu, mit denen sie mit dem Getter verglichen werden können (der Zugriff mit ivar wäre nicht sicher! Was ist, wenn das andere Objekt eine Unterklasse ist und den Getter überschrieben hat?). Beachten Sie auch, dass der Zugriff auf Ihre eigenen Eigenschaften als Ivars die Kapselung nicht unterbricht (die Ivars sind immer noch nicht öffentlich).

Das Test-Setup ist wirklich einfach: Erstellen Sie 1 Mio. zufällige Konten, fügen Sie sie einem Array hinzu und sortieren Sie dieses Array. Das ist es. Natürlich gibt es zwei Arrays, eines für AccountAObjekte und eines für AccountBObjekte, und beide Arrays sind mit identischen Konten (derselben Datenquelle) gefüllt. Wir messen, wie lange es dauert, die Arrays zu sortieren.

Hier ist die Ausgabe mehrerer Läufe, die ich gestern durchgeführt habe:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

Wie Sie sehen können, die Anordnung der Sortierung AccountBist Objekte immer signifikant schneller als die Anordnung von AccountASortierobjekten.

Wer behauptet, dass Laufzeitunterschiede von bis zu 1,32 Sekunden keinen Unterschied machen, sollte besser niemals UI-Programmierung durchführen. Wenn ich beispielsweise die Sortierreihenfolge einer großen Tabelle ändern möchte, machen Zeitunterschiede wie diese einen großen Unterschied für den Benutzer (den Unterschied zwischen einer akzeptablen und einer trägen Benutzeroberfläche).

Auch in diesem Fall ist der Beispielcode die einzige echte Arbeit, die hier ausgeführt wird. Wie oft ist Ihr Code jedoch nur ein kleines Zahnrad eines komplizierten Uhrwerks? Und wenn jeder Gang den gesamten Prozess so verlangsamt, was bedeutet das am Ende für die Geschwindigkeit des gesamten Uhrwerks? Insbesondere wenn ein Arbeitsschritt von der Leistung eines anderen abhängt, summieren sich alle Ineffizienzen. Die meisten Ineffizienzen sind für sich genommen kein Problem, sondern ihre bloße Summe wird zu einem Problem für den gesamten Prozess. Und ein solches Problem ist nichts, was ein Profiler leicht zeigen kann, weil es bei einem Profiler darum geht, kritische Hotspots zu finden, aber keine dieser Ineffizienzen ist für sich genommen Hotspots. Die CPU-Zeit ist nur durchschnittlich auf sie verteilt, aber jeder von ihnen hat nur einen so kleinen Bruchteil davon, dass es eine totale Zeitverschwendung zu sein scheint, sie zu optimieren. Und es ist wahr,

Und selbst wenn Sie nicht an die CPU-Zeit denken, weil Sie glauben, dass die Verschwendung von CPU-Zeit völlig akzeptabel ist, schließlich "es ist kostenlos", was ist dann mit den Server-Hosting-Kosten, die durch den Stromverbrauch verursacht werden? Was ist mit der Akkulaufzeit mobiler Geräte? Wenn Sie dieselbe mobile App zweimal schreiben würden (z. B. einen eigenen mobilen Webbrowser), einmal eine Version, in der alle Klassen nur über Getter auf ihre eigenen Eigenschaften zugreifen, und einmal, in der alle Klassen nur über Ivars auf sie zugreifen, wird die Verwendung der ersten App definitiv erschöpft Die Batterie ist viel schneller als die zweite, obwohl sie funktional gleichwertig ist und für den Benutzer würde sich die zweite wahrscheinlich sogar etwas schneller anfühlen.

Hier ist der Code für Ihre main.mDatei (der Code setzt voraus, dass ARC aktiviert ist, und verwenden Sie beim Kompilieren unbedingt die Optimierung, um den vollen Effekt zu sehen):

#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

3
Extrem informative und bodenständige Erklärung. Upvote für Codebeispiel
Philip007

1
Eines der wichtigsten Qualifikationsmerkmale, die ich in Ihrem Beitrag sehe, ist "... von den kritischen Codepfaden". Der Punkt ist, dass Sie verwenden, was das Lesen / Schreiben des Codes erleichtert, und dann optimieren, was Sie als kritische Pfade empfinden. Dies erhöht die Komplexität dort, wo sie benötigt wird.
Sandy Chapman

1
@ViktorLexington In meinem Code habe ich ein festgelegt, unsigned intdas niemals beibehalten / freigegeben wird, unabhängig davon, ob Sie ARC verwenden oder nicht. Das Zurückhalten / Freigeben selbst ist teuer, daher ist der Unterschied geringer, da das Zurückhalten-Management einen statischen Overhead hinzufügt, der immer vorhanden ist, wenn Setter / Getter oder Ivar direkt verwendet werden. Sie sparen jedoch den Overhead eines zusätzlichen Methodenaufrufs, wenn Sie direkt auf ivar zugreifen. In den meisten Fällen keine große Sache, es sei denn, Sie tun dies mehrere tausend Mal pro Sekunde. Laut Apple werden standardmäßig Getter / Setter verwendet, es sei denn, Sie befinden sich in einer Init / Dealloc-Methode oder haben einen Engpass festgestellt.
Mecki

1
@Fogmeister Es wurde ein Codebeispiel hinzugefügt, das zeigt, wie einfach dies in einem sehr einfachen Beispiel aus der realen Welt einen großen Unterschied machen kann. Und dieses Beispiel hat nichts mit einem Supercomputer zu tun, der Billionen von Berechnungen durchführt. Es geht eher darum, eine wirklich einfache Datentabelle zu sortieren (ein ziemlich häufiger Fall bei Millionen von Apps).
Mecki

2
@malhal Eine Eigenschaft markiert als copywird nicht eine Kopie seines Wertes machen jedes Mal , wenn Sie darauf zugreifen. Der Getter von copyEigentum ist wie der Getter von einem strong/ retainEigentum. Der Code ist im Grunde return [[self->value retain] autorelease];. Nur der Setter kopiert den Wert und es wird ungefähr so ​​aussehen [self->value autorelease]; self->value = [newValue copy];, während ein strong/ retainSetter so aussieht:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Der wichtigste Grund ist das OOP-Konzept des Versteckens von Informationen : Wenn Sie alles über Eigenschaften verfügbar machen und damit externen Objekten erlauben, auf die Interna eines anderen Objekts zu schauen, werden Sie diese internen verwenden und so die Änderung der Implementierung erschweren.

Der Gewinn "minimale Leistung" kann sich schnell summieren und dann zu einem Problem werden. Ich weiß aus Erfahrung; Ich arbeite an einer App, die die iDevices wirklich an ihre Grenzen bringt, und wir müssen daher unnötige Methodenaufrufe vermeiden (natürlich nur, wenn dies vernünftigerweise möglich ist). Um dieses Ziel zu erreichen, vermeiden wir auch die Punktsyntax, da es schwierig ist, die Anzahl der Methodenaufrufe auf den ersten Blick zu erkennen: Wie viele Methodenaufrufe löst der Ausdruck beispielsweise aus self.image.size.width? Im Gegensatz dazu kann man sofort mit erkennen [[self image] size].width.

Bei korrekter Ivar-Benennung ist KVO auch ohne Eigenschaften möglich (IIRC, ich bin kein KVO-Experte).


3
+1 Gute Antwort auf den Gewinn "minimale Leistung" summiert sich und möchte alle Methodenaufrufe explizit sehen. Die Verwendung der Punktsyntax mit Eigenschaften maskiert definitiv eine Menge Arbeit, die in benutzerdefinierten Gettern / Setzern ausgeführt wird (insbesondere, wenn dieser Getter bei jedem Aufruf eine Kopie von etwas zurückgibt).
Sam

1
KVO funktioniert bei mir nicht ohne Setter. Durch direktes Ändern des Ivar wird der Beobachter nicht darüber informiert, dass sich der Wert geändert hat!
Binarian

2
KVC kann auf Ivars zugreifen. KVO kann keine Änderungen an ivars erkennen (und verlässt sich stattdessen darauf, dass Accessoren aufgerufen werden).
Nikolai Ruhe

9

Semantik

  • Was @propertykann ausdrücken, dass Ivars nicht können: nonatomicund copy.
  • Was Ivars ausdrücken können, @propertykann nicht:
    • @protected: öffentlich in Unterklassen, privat außerhalb.
    • @package: public on Frameworks auf 64 Bit, privat außerhalb. Gleich wie @publicbei 32 Bit. Siehe Apples 64-Bit-Zugriffssteuerung für Klassen- und Instanzvariablen .
    • Qualifikanten. Zum Beispiel Arrays mit starken Objektreferenzen : id __strong *_objs.

Performance

Kurzgeschichte: Ivars sind schneller, aber für die meisten Anwendungen spielt es keine Rolle. nonatomicEigenschaften verwenden keine Sperren, aber direktes ivar ist schneller, da der Zugriff der Zugriffsberechtigten übersprungen wird. Weitere Informationen finden Sie in der folgenden E-Mail von lists.apple.com.

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

Eigenschaften wirken sich in vielerlei Hinsicht auf die Leistung aus:

  1. Wie bereits erläutert, ist das Senden einer Nachricht zum Laden / Speichern langsamer als das Inline-Laden / Speichern .

  2. Das Senden einer Nachricht zum Laden / Speichern ist auch viel mehr Code , der im i-Cache aufbewahrt werden muss: Selbst wenn der Getter / Setter keine zusätzlichen Anweisungen über das Laden / Speichern hinaus hinzufügt, gibt es eine solide Hälfte - Dutzend zusätzliche Anweisungen im Anrufer, um die Nachricht einzurichten und das Ergebnis zu verarbeiten.

  3. Durch das Senden einer Nachricht wird ein Eintrag für diesen Selektor im Methodencache erzwungen , und dieser Speicher bleibt im Allgemeinen im D-Cache erhalten. Dies erhöht die Startzeit, erhöht die statische Speichernutzung Ihrer App und macht Kontextwechsel schmerzhafter. Da der Methodencache spezifisch für die dynamische Klasse eines Objekts ist, steigt dieses Problem, je mehr Sie KVO darauf verwenden.

  4. Durch das Senden einer Nachricht werden alle Werte in der Funktion auf den Stapel übertragen (oder in Angerufenenregistern gespeichert, was nur bedeutet, dass sie zu einem anderen Zeitpunkt verschüttet werden).

  5. Das Senden einer Nachricht kann beliebige Nebenwirkungen haben und daher

    • zwingt den Compiler, alle seine Annahmen über den nicht lokalen Speicher zurückzusetzen
    • kann nicht gehisst, versenkt, nachbestellt, zusammengeführt oder beseitigt werden.

  6. In ARC wird das Ergebnis eines Nachrichtenversands immer beibehalten , entweder vom Angerufenen oder vom Anrufer, auch bei +0 Rückgaben: Selbst wenn die Methode das Ergebnis nicht beibehält / automatisch freigibt, weiß der Anrufer das nicht und hat es um zu versuchen, Maßnahmen zu ergreifen, um zu verhindern, dass das Ergebnis automatisch veröffentlicht wird. Dies kann niemals beseitigt werden, da Nachrichtensendungen nicht statisch analysierbar sind.

  7. In ARC gibt es keine Möglichkeit, eine Aufbewahrung dieses Objekts (das, wie oben erläutert, ARC normalerweise hat) in den ivar zu "übertragen", da eine Setter-Methode im Allgemeinen ihr Argument bei +0 verwendet, sodass der Wert im Allgemeinen abgerufen werden muss zweimal behalten / freigeben .

Nichts davon bedeutet natürlich, dass sie immer schlecht sind - es gibt viele gute Gründe, Eigenschaften zu verwenden. Denken Sie daran, dass sie wie viele andere Sprachfunktionen nicht kostenlos sind.


John.


6

Eigenschaften vs. Instanzvariablen sind ein Kompromiss. Letztendlich hängt die Auswahl von der Anwendung ab.

Kapselung / Ausblenden von Informationen Dies ist eine gute Sache (TM) aus Sicht des Designs, enge Schnittstellen und minimale Verknüpfung machen Software wartbar und verständlich. In Obj-C ist es ziemlich schwierig, etwas zu verbergen, aber in der Implementierung deklarierte Instanzvariablen kommen so nahe wie möglich.

Leistung Während "vorzeitige Optimierung" eine schlechte Sache ist, ist das Schreiben von Code mit schlechter Leistung, nur weil Sie dies können, mindestens genauso schlecht. Es ist schwer zu argumentieren, dass ein Methodenaufruf teurer ist als ein Laden oder Speichern, und in rechenintensivem Code summieren sich die Kosten bald.

In einer statischen Sprache mit Eigenschaften wie C # können Aufrufe von Setzern / Gettern häufig vom Compiler optimiert werden. Obj-C ist jedoch dynamisch und das Entfernen solcher Anrufe ist viel schwieriger.

Abstraktion Ein Argument gegen Instanzvariablen in Obj-C ist traditionell die Speicherverwaltung. Bei MRC-Instanzvariablen müssen Aufrufe zum Beibehalten / Freigeben / Autorelease im gesamten Code verteilt werden. Eigenschaften (synthetisiert oder nicht) halten den MRC-Code an einem Ort - dem Prinzip der Abstraktion, das eine gute Sache (Good Thing, TM) ist. Bei GC oder ARC verschwindet dieses Argument jedoch, sodass die Abstraktion für die Speicherverwaltung kein Argument mehr gegen Instanzvariablen ist.


5

Eigenschaften setzen Ihre Variablen anderen Klassen aus. Wenn Sie nur eine Variable benötigen, die sich nur auf die von Ihnen erstellte Klasse bezieht, verwenden Sie eine Instanzvariable. Hier ist ein kleines Beispiel: Die XML-Klassen zum Parsen von RSS und dergleichen durchlaufen eine Reihe von Delegatmethoden und dergleichen. Es ist praktisch, eine Instanz von NSMutableString zu haben, um das Ergebnis jedes einzelnen Durchlaufs der Analyse zu speichern. Es gibt keinen Grund, warum eine externe Klasse jemals auf diese Zeichenfolge zugreifen oder sie manipulieren müsste. Sie deklarieren es also einfach in der Kopfzeile oder privat und greifen in der gesamten Klasse darauf zu. Das Festlegen einer Eigenschaft dafür ist möglicherweise nur hilfreich, um sicherzustellen, dass keine Speicherprobleme vorliegen. Verwenden Sie self.mutableString, um den Getter / die Setter aufzurufen.


5

Abwärtskompatibilität war für mich ein Faktor. Ich konnte keine Objective-C 2.0-Funktionen verwenden, da ich Software und Druckertreiber entwickelte, die unter Mac OS X 10.3 als Teil einer Anforderung funktionieren mussten. Ich weiß, dass Ihre Frage auf iOS ausgerichtet war, aber ich dachte, ich würde trotzdem meine Gründe für die Nichtverwendung von Eigenschaften mitteilen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.