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.myProperty
Syntax.
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.
Antworten:
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
dealloc
ist es am besten, den direkten Zugriff zu verwenden. Sie können auch diese gemeinsame in den Implementierungen eines Zugriffs finden, Bequemlichkeit Konstruktorcopy
,mutableCopy
und 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:
Versuchen Sie es jetzt nur mit einem Ivar und ohne KVC.
quelle
@private
, sollte der Compiler dem Mitglied den Zugriff außerhalb der Klassen- und Instanzmethoden verbieten - sehen Sie das nicht?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
/ beinhaltenobjc_setProperty
, da dieseretain
/copy
/autorelease
die 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:
Ausgabe:
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
Account
Objekte. 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 eineinit
Methode und einecompare:
Methode. Diecompare:
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
AccountA
undAccountB
. Wenn Sie sich ihre Implementierung ansehen, werden Sie feststellen, dass sie fast vollständig identisch sind, mit einer Ausnahme: Diecompare:
Methode.AccountA
Objekte zugreifen ihre eigenen Eigenschaften durch Methode (Getter), während dieAccountB
Objekte 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
AccountA
Objekte und eines fürAccountB
Objekte, 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:
Wie Sie sehen können, die Anordnung der Sortierung
AccountB
ist Objekte immer signifikant schneller als die Anordnung vonAccountA
Sortierobjekten.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.m
Datei (der Code setzt voraus, dass ARC aktiviert ist, und verwenden Sie beim Kompilieren unbedingt die Optimierung, um den vollen Effekt zu sehen):quelle
unsigned int
das 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.copy
wird nicht eine Kopie seines Wertes machen jedes Mal , wenn Sie darauf zugreifen. Der Getter voncopy
Eigentum ist wie der Getter von einemstrong
/retain
Eigentum. Der Code ist im Grundereturn [[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 einstrong
/retain
Setter so aussieht:[self->value autorelease]; self->value = [newValue retain];
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).
quelle
Semantik
@property
kann ausdrücken, dass Ivars nicht können:nonatomic
undcopy
.@property
kann nicht:@protected
: öffentlich in Unterklassen, privat außerhalb.@package
: public on Frameworks auf 64 Bit, privat außerhalb. Gleich wie@public
bei 32 Bit. Siehe Apples 64-Bit-Zugriffssteuerung für Klassen- und Instanzvariablen .id __strong *_objs
.Performance
Kurzgeschichte: Ivars sind schneller, aber für die meisten Anwendungen spielt es keine Rolle.
nonatomic
Eigenschaften 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.quelle
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.
quelle
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.
quelle
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.
quelle