Automatische Referenzzählung: Teil 1

Ursprünglicher Autor: Mike Ash
  • Übersetzung
Hallo Kollegen.

Ich habe lange Zeit Blogs und Artikel von ausländischen Entwicklern für iOS gelesen. Neulich stieß ich auf einen neugierigen, ziemlich detaillierten Artikel über Automatic Reference Counting von einem Entwickler namens Mike Ash .
Der Artikel ist ziemlich umfangreich, daher riskiere ich, die von mir angefertigte Übersetzung in mehrere Teile zu unterteilen. Ich hoffe, in 2 Teilen zu halten.


Seitdem Apple diese Innovation angekündigt hat, haben mich die Leser ermutigt, über den automatischen Referenzzählmechanismus (Automatic Reference Counting, ARC) zu schreiben. Die Zeit ist gekommen. Heute sprechen wir über das neue Speicherverwaltungssystem von Apple: wie es funktioniert und wie Sie es für sich selbst nutzen können.

Konzept

Clang Static Analyzer ist ein sehr nützliches Tool zum Auffinden von Speicherverwaltungsfehlern in Ihrem Code. Wenn Sie und ich uns gleich sind, denken wir, wenn wir die Ausgabe des Analysators betrachten: "Wenn der Analysator den Fehler erkannt hat, warum sollte er dann nicht anstelle von mir korrigiert werden?"

Kurz gesagt, dies ist das Wesen von ARC. Speicherverwaltungsregeln sind in den Compiler integriert. Anstatt sie zu verwenden, um dem Entwickler das Auffinden von Fehlern zu erleichtern, fügt der ARC-Mechanismus einfach die erforderlichen Aufrufe ein. Und alle.

Der ARC-Mechanismus befindet sich irgendwo zwischen dem Garbage Collector und der manuellen Speicherverwaltung. Wie der Garbage Collector müssen Entwickler in ARC keine Aufrufe zum Speichern / Freigeben / Autorelease mehr schreiben . Im Gegensatz zum Garbage Collector reagiert ARC jedoch nicht auf das Beibehalten von Schleifen.. Zwei Objekte mit starken Verknüpfungen werden von ARC niemals verarbeitet, auch wenn sich niemand darauf bezieht. Während ARC den Programmierer von den meisten Speicherverwaltungsaufgaben entlastet, muss der Entwickler weiterhin strenge Zirkelverweise auf das Objekt vermeiden oder manuell zerstören.

In Bezug auf die Implementierungsspezifikationen ist dies ein weiterer wesentlicher Unterschied zwischen ARC und der GC-Implementierung von Apple: ARC ist keine Verbesserung. Wenn Sie GC von Apple verwenden, wird entweder die gesamte Anwendung in der GC-Umgebung ausgeführt oder nicht. Dies bedeutet, dass der gesamte Objective-C-Code in der Anwendung, einschließlich der verwendeten Apple-Frameworks und Bibliotheken von Drittanbietern, mit GC kompatibel sein muss, damit er verwendet werden kann. Es ist bemerkenswert, dass ARC friedlich mit "Nicht-ARC" -Code mit manueller Speicherverwaltung in derselben Anwendung koexistiert. Dies ermöglicht das Konvertieren von Projekten in Teile ohne die großen Kompatibilitäts- und Zuverlässigkeitsprobleme, die bei der Einführung von GC aufgetreten sind.

Xcode

ARC ist ab der Beta-Version von Xcode 4.2 und nur verfügbar, wenn Sie die Kompilierung mit Clang (auch als "Apple LLVM-Compiler" bezeichnet) auswählen. Um es zu verwenden, genügt es, die Einstellung mit dem offensichtlichen Namen "Objective-C Automatic Reference Counting" zu notieren.
Wenn Sie bereits mit einer vorhandenen Codebasis arbeiten, kann das Ändern dieser Einstellung zu einer großen Anzahl von Fehlern führen. ARC verwaltet nicht nur das Gedächtnis an Ihrem Platz, sondern verhindert auch, dass Sie es selbst tun. Die Verwendung von ARC ist völlig inakzeptabel, um Retain / Release / Autorelease-Nachrichten an Objekte zu senden . Aufgrund der Tatsache, dass Cocoa voll von solchen Nachrichten ist, werden Sie unweigerlich eine Menge Fehler bekommen.
Glücklicherweise bietet Xcode ein Tool zum Konvertieren von vorhandenem Code. Wählen Sie dazu einfachBearbeiten -> Refactor ... -> In Objective-C ARC konvertieren ... - und mit Xcode können Sie Ihren Code schrittweise konvertieren. Abgesehen von Engpässen, bei denen Sie angeben müssen, was zu tun ist, erfolgt dieser Vorgang weitgehend automatisch.

Grundlegende Funktionen

Die Speicherverwaltungsregeln von Cocoa sind recht einfach. Kurz gesagt:
1. Wenn Sie eine Zuweisung, eine neue Zuweisung, eine Kopie oder eine Speicherung an ein Objektsenden , müssen Sie dies durch Freigabe oder Autorelease ausgleichen .
2. Wenn Sie das Objekt auf eine andere Weise als in Absatz 1 beschrieben erhalten haben und das Objekt für eine ausreichend lange Zeit „am Leben“ sein muss, müssen Sie behalten oder kopieren. Dies sollte natürlich später von Ihnen ausgeglichen werden.

Sie (die Regeln) tragen wesentlich zur Automatisierung des Prozesses bei. Wenn du schreibst:
  Foo *foo = [[Foo alloc] init];
   [foo something];
   return;


Der Compiler sieht eine unausgeglichene Zuweisung und konvertiert zu Folgendem:
  Foo *foo = [[Foo alloc] init];
   [foo something];
   [foo release];
   return;


In der Realität fügt der Compiler den Code zum Senden der Freigabemeldung an das Objekt nicht ein. Stattdessen wird ein Aufruf einer speziellen Laufzeitfunktion eingefügt:
  Foo *foo = [[Foo alloc] init];
   [foo something];
   objc_release(foo);
   return;

Hier können Sie einige Optimierungen vornehmen. In den meisten Fällen, in denen - release nicht neu definiert wird, kann objc_release den Objective-C-Nachrichtenmechanismus umgehen, was zu einer leichten Geschwindigkeitssteigerung führt.

Eine solche Automatisierung trägt dazu bei, den Code sicherer zu machen. Viele Cocoa-Entwickler interpretieren den Begriff "ziemlich lange" aus Abschnitt 2 und implizieren Objekte, die in Instanzvariablen oder ähnlichen Orten gespeichert sind. Normalerweise wenden wir Retain und Release nicht auf lokale temporäre Objekte an:
  Foo *foo = [self foo];
   [foo bar];
   [foo baz];
   [foo quux];


Die folgende Konstruktion ist jedoch ziemlich gefährlich:
  Foo *foo = [self foo];
   [foo bar];
   [foo baz];
   [self setFoo: newFoo];
   [foo quux]; // crash


Dies kann durch ein Getter for - foo behoben werden , in dem eine Retain / Autorelease gesendet wird, bis der Wert zurückgegeben wird. Dies ist eine funktionierende Option, kann jedoch viele temporäre Objekte erzeugen, die den Speicher aktiv nutzen. ARC fügt hier jedoch paranoide zusätzliche Aufrufe ein:
  Foo *foo = objc_retainAutoreleasedReturnValue([self foo]);
   [foo bar];
   [foo baz];
   [self setFoo: newFoo];
   [foo quux]; // fine
   objc_release(foo);


Selbst wenn Sie einen einfachen Getter geschrieben haben, wird ARC ihn so sicher wie möglich machen:
  - (Foo *)foo
   {
       return objc_retainAutoreleaseReturnValue(_foo);
   }


Das Problem einer zu großen Anzahl temporärer Objekte ist damit jedoch nicht vollständig gelöst! Wir verwenden wie bisher die Retain / Autorelease-Kombination im Getter und die Retain / Release- Kombination im aufrufenden Code. Dies ist zweifellos unwirksam!

Aber keine Sorge ohne Maß. Wie bereits erwähnt, verwendet ARC (zu Optimierungszwecken) spezielle Aufrufe, anstatt nur Nachrichten zu senden. Diese Aufrufe beschleunigen nicht nur das Speichern und Freigeben , sondern eliminieren auch einige Vorgänge im Allgemeinen.

When objc_retainAutoreleaseReturnValuefunktioniert, überwacht es den Stack und merkt sich die Absenderadresse des Aufrufers. Dadurch kann er genau wissen, was nach seiner Fertigstellung passieren wird. Wenn die Kompilierungsoptimierung aktiviert ist, kann der Aufruf von objc_retainAutoreleaseReturnValue der Grund für die Tail-Call-Optimierung sein .

Rantime verwendet diese verrückte Überprüfung der Absenderadresse, um unnötige Arbeit zu vermeiden. Dadurch wird das Senden der automatischen Freigabe verhindert und ein Flag gesetzt, das den Anrufer anweist, die Aufbewahrungsnachricht zu zerstören. Die gesamte Konstruktion verwendet insgesamt ein einziges Retentat im Getter und ein einziges Releaseim aufrufenden Code, der sicherer und effizienter ist.

Hinweis: Diese Optimierung ist vollständig kompatibel mit Code, der den ARC-Mechanismus nicht verwendet.
In einem Ereignis, dessen Getter ARC nicht verwendet, wird das obige Flag nicht gesetzt, und der Aufrufer kann die vollständige Kombination von Retain / Release- Nachrichten verwenden.
In einem Ereignis, dessen Getter ARC verwendet, der Aufrufer jedoch nicht, sieht der Getter, dass er nicht zu dem Code zurückkehrt, der sofort spezielle spezielle Laufzeitfunktionen aufruft, und kann daher die vollständige Kombination von Retain- / Autorelease- Nachrichten verwenden.
Ein Teil der Leistung geht verloren, der Code ist jedoch weiterhin vollständig korrekt.

Darüber hinaus erstellt oder füllt die ARC-Engine die Methode -dealloc automatisch für alle Klassen, um alle Instanzvariablen freizugeben . Gleichzeitig besteht weiterhin die Möglichkeit, -dealloc manuell zu schreiben (und dies ist für Klassen erforderlich, die externe Ressourcen verwalten). In Zukunft besteht jedoch keine Notwendigkeit (oder Möglichkeit), Klasseninstanzvariablen manuell freizugeben. ARC fügt am Ende sogar einen [Super Dealloc] -Aufruf ein, sodass Sie sich keine Sorgen mehr machen müssen.

Wir veranschaulichen, was gesagt wurde.
Bisher konnten Sie die folgende Konstruktion haben:
  - (void)dealloc
   {
       [ivar1 release];
       [ivar2 release];
       free(buffer);
       [super dealloc];
   }


Aber du musst nur schreiben:
  - (void)dealloc
   {
       free(buffer);
   }


Falls Ihre -dealloc- Methode gerade Instanzvariablen freigegeben hat, wird dieses Konstrukt (mit ARC) dasselbe für Sie tun.

Schleifen und schwache Links

ARC erfordert weiterhin, dass der Entwickler kreisförmige Links manuell verarbeitet. Die beste Lösung für dieses Problem ist die Verwendung schwacher Links.

ARC verwendet das Nullen schwacher Verbindungen. Solche Links behalten nicht nur das Objekt, auf das sie verweisen, "live" bei, sondern werden auch automatisch null, wenn das Objekt, auf das sie verweisen, zerstört wird. Durch das Nullsetzen von schwachen Links wird das Problem von toten Zeigern und den damit verbundenen Abstürzen sowie dem unvorhersehbaren Verhalten der Anwendung beseitigt.
Um eine nullbare schwache Verbindung zu erstellen, müssen Sie nur das Präfix hinzufügen__schwach bei der Ankündigung.
Hier ist zum Beispiel die Erstellung einer Instanzvariablen dieses Typs:
  @interface Foo : NSObject
   {
       __weak Bar *_weakBar;
   }


Lokale Variablen werden auf dieselbe Weise erstellt:
  __weak Foo *_weakFoo = [object foo];


Sie können sie später als reguläre Variablen verwenden, aber ihr Wert wird automatisch null, wenn der Bedarf besteht:
  [_weakBar doSomethingIfStillAlive];


Beachten Sie jedoch, dass die Variable __weak zu fast jedem Zeitpunkt null werden kann.
Die Speicherverwaltung ist im Wesentlichen ein Multithread-Prozess, und ein lose gekoppeltes Objekt kann in einem Thread zerstört werden, während ein anderer Thread darauf zugreift.
Wir werden es veranschaulichen.
Der folgende Code ist falsch:
  if(_weakBar)
       [self mustNotBeNil: _weakBar];


Verwenden Sie stattdessen einen lokalen starken Link und überprüfen Sie Folgendes:
  Bar *bar = _weakBar;
   if(bar)
       [self mustNotBeNil: bar];


Dies garantiert, dass das Objekt für die gesamte Zeit, in der es verwendet wird, aktiv ist (und die Nicht- Null- Variable ), da in diesem Fall der Balken eine starke Referenz darstellt.

Die Implementierung des Nulling von schwachen Gliedern in ARC erfordert eine enge Koordinierung zwischen dem Referenzrechnungssystem in Objective-C und dem Nulling-System von schwachen Gliedern. Dies bedeutet, dass eine Klasse, die Beibehalten und Freigeben überschreibt , kein Objekt einer nullfähigen schwachen Referenz sein kann. Obwohl dies ungewöhnlich ist, leiden einige Kakaoklassen darunter (z . B. NSWindow ).

Wenn Sie einen ähnlichen Fehler machen, informiert Sie das Programm glücklicherweise sofort darüber, indem es eine Meldung ausgibt, wenn es abstürzt:
  objc[2478]: cannot form weak reference to instance (0x10360f000) of class NSWindow


Wenn Sie wirklich einen schwachen Verweis auf ähnliche Klassen benötigen, können Sie __unsafe_unretained anstelle von __weak verwenden . Dadurch wird eine schwache Verbindung erstellt, die NICHT zurückgesetzt wird. Sie müssen jedoch sicherstellen, dass Sie diesen Zeiger nicht verwenden (es wird empfohlen, ihn manuell zurückzusetzen), NACHDEM das Objekt, auf das er zeigt, zerstört wurde.
Seien Sie vorsichtig: Wenn Sie nicht nullbare schwache Glieder verwenden, spielen Sie mit dem Feuer.

Trotz der Möglichkeit, mit ARC Programme für Mac OS X 10.6 und iOS 4 zu schreiben, sind auf diesen Betriebssystemen keine nullbaren schwachen Links verfügbar.(Anmerkung des Übersetzers: Ich habe die Bedeutung dieser Passage im Zusammenhang mit dem ARC-Parsing nicht vollständig verstanden. Vielleicht hat der Autor einen Tippfehler gemacht, anstatt schwache Referenzen auf Null zu setzen, sollten schwache Referenzen nicht auf Null gesetzt werden.)
Alle schwachen Links sollten mit __unsafe_unretained bearbeitet werden .

Angesichts der Tatsache, dass nullbare Schwachstellen so gefährlich sind, verringert diese Einschränkung für mich die Attraktivität der Verwendung von ARC auf diesen Systemen erheblich.



Hier werde ich den ersten Teil der Übersetzung beenden, da der Text ziemlich groß ausfiel. Ich habe versucht, es so gut wie möglich ins Russische zu übersetzen, was nicht besonders einfach ist, wenn 99% der gelesenen Materialien auf Englisch sind.
Wie sie sagen, bleiben Sie dran.

Jetzt auch beliebt: