Swift: ARC- und Speicherverwaltung

Als moderne Hochsprache kümmert sich Swift im Wesentlichen um die Speicherverwaltung in Ihren Anwendungen, um Speicher zuzuweisen und freizugeben. Dies ist auf einen Mechanismus zurückzuführen, der als automatische Referenzzählung , kurz ARC, bezeichnet wird . In diesem Handbuch erfahren Sie, wie ARC funktioniert und wie Sie den Speicher in Swift ordnungsgemäß verwalten. Wenn Sie diesen Mechanismus verstehen, können Sie die Lebensdauer von Objekten auf dem Heap ( Heap ) beeinflussen.

In diesem Handbuch lernen Sie Swift und ARC kennen, indem Sie Folgendes lernen:

  • wie ARC funktioniert
  • Was sind Referenzzyklen und wie werden sie richtig behoben?
  • So erstellen Sie eine Beispiel-Link-Schleife
  • So finden Sie Link-Loops mit den von Xcode angebotenen visuellen Tools
  • Umgang mit Referenztypen und Werttypen

Erste Schritte


Laden Sie die Quellmaterialien herunter . Öffnen Sie das Projekt im Ordner Cycles / Starter . Im ersten Teil unseres Handbuchs werden wir uns mit den wichtigsten Konzepten befassen und uns ausschließlich mit der Datei MainViewController.swif t befassen.

Fügen Sie diese Klasse am Ende von MainViewController.swift hinzu:

class User { let name: String init(name: String) { self.name = name print("User \(name) was initialized") } deinit { print("Deallocating user named: \(name)") } } 

Hier wird die User- Klasse definiert, die uns mit Hilfe von print- Anweisungen über die Initialisierung und Freigabe der Klasseninstanz informiert.

Erstellen Sie nun eine Instanz der User-Klasse oben im MainViewController.

Platzieren Sie diesen Code vor der viewDidLoad () -Methode:

 let user = User(name: "John") 

Starten Sie die App. Machen Sie die Xcode-Konsole mit Command-Shift-Y sichtbar, um die Ausgabe der print-Anweisungen anzuzeigen.

Beachten Sie, dass Benutzer John initialisiert wurde und auf der Konsole angezeigt wurde , die print-Anweisung in deinit jedoch nicht ausgeführt wurde. Dies bedeutet, dass dieses Objekt nicht freigegeben wurde, da es nicht außerhalb des Gültigkeitsbereichs lag .

Mit anderen Worten, bis der Ansichtscontroller, der dieses Objekt enthält, den Gültigkeitsbereich verlässt, wird das Objekt niemals freigegeben.

Ist er im Umfang?


Indem wir eine Instanz der User-Klasse in eine Methode einbinden, wird sie außerhalb des Gültigkeitsbereichs liegen, sodass ARC sie freigeben kann.

Lassen Sie uns die runScenario () -Methode in der MainViewController-Klasse erstellen und die Initialisierung der User-Klasseninstanz darin verschieben.

 func runScenario() { let user = User(name: "John") } 

runScenario () definiert den Bereich der Benutzerinstanz. Beim Verlassen dieser Zone muss der Benutzer freigegeben werden.

Rufen Sie nun runScenario () auf und fügen Sie dies am Ende von viewDidLoad () hinzu:

 runScenario() 

Starten Sie die App. Die Konsolenausgabe sieht nun folgendermaßen aus:

Benutzer John wurde initialisiert
Freigabe des Benutzers mit dem Namen: John

Dies bedeutet, dass Sie ein Objekt freigegeben haben, das das Sichtfeld verlassen hat.

Objektlebensdauer



Die Existenz des Objekts ist in fünf Stufen unterteilt:

  • Speicherzuordnung: vom Stapel oder vom Heap
  • Initialisierung: Code wird in init ausgeführt
  • Verwendung von
  • Deinitialisierung: Code wird innerhalb von Deinit ausgeführt
  • Freier Speicher: Der zugewiesene Speicher wird an den Stapel oder Heap zurückgegeben

Es gibt keine direkte Möglichkeit, die Schritte zum Zuweisen und Freigeben von Speicher zu verfolgen, aber Sie können den Code in init und deinit verwenden.

Referenzzählungen , auch als Nutzungszählungen bezeichnet , bestimmen, wann ein Objekt nicht mehr benötigt wird. Dieser Zähler zeigt die Anzahl derer an, die dieses Objekt "verwenden". Ein Objekt wird unnötig, wenn der Nutzungszähler Null ist. Dann wird das Objekt de-initialisiert und freigegeben.



Wenn das Benutzerobjekt initialisiert wird, beträgt sein Referenzzähler 1, da die Benutzerkonstante auf dieses Objekt verweist.

Am Ende von runScenario () verlässt der Benutzer den Gültigkeitsbereich und der Referenzzähler wird auf 0 reduziert. Infolgedessen wird der Benutzer nicht initialisiert und dann freigegeben.

Referenzzyklen


In den meisten Fällen funktioniert ARC wie es sollte. Der Entwickler muss sich normalerweise nicht um Speicherverluste kümmern, wenn nicht verwendete Objekte auf unbestimmte Zeit nicht zugewiesen werden.

Aber nicht immer! Mögliche Speicherlecks.

Wie kann das passieren? Stellen Sie sich eine Situation vor, in der zwei Objekte nicht mehr verwendet werden, sich aber jeweils auf das andere beziehen. Da jeder Referenzzähler nicht 0 ist, wird keiner von ihnen freigegeben.



Dies ist ein starker Referenzzyklus . Diese Situation verwirrt den ARC und erlaubt ihm nicht, den Speicher zu löschen.

Wie Sie sehen können, ist der Referenzzähler am Ende nicht 0, und obwohl keine Objekte mehr benötigt werden, werden Objekt1 und Objekt2 nicht freigegeben.

Schauen Sie sich unsere Links an


Um dies alles in Aktion zu testen, fügen Sie diesen Code nach der User-Klasse in MainViewController.swift hinzu:

 class Phone { let model: String var owner: User? init(model: String) { self.model = model print("Phone \(model) was initialized") } deinit { print("Deallocating phone named: \(model)") } } 

Dieser Code fügt eine neue Telefonklasse mit zwei Eigenschaften hinzu, eine für das Modell und eine für den Eigentümer sowie die Methoden init und deinit. Das Eigentum des Eigentümers ist optional, da das Telefon möglicherweise keinen Eigentümer hat.

Fügen Sie nun diese Zeile zu runScenario () hinzu:

 let iPhone = Phone(model: "iPhone Xs") 

Dadurch wird eine Instanz der Phone-Klasse erstellt.

Halten Sie das Handy


Fügen Sie diesen Code nun unmittelbar nach der Eigenschaft name zur Benutzerklasse hinzu:

 private(set) var phones: [Phone] = [] func add(phone: Phone) { phones.append(phone) phone.owner = self } 

Fügen Sie eine Reihe von Telefonen hinzu, die dem Benutzer gehören. Der Setter ist als privat markiert, daher muss add (phone :) verwendet werden.

Starten Sie die App. Wie Sie sehen können, werden Instanzen der Telefon- und Benutzerobjektklassen nach Bedarf freigegeben.

Benutzer John wurde initialisiert
Telefon iPhone XS wurde initialisiert
Freigabe des Telefons mit dem Namen: iPhone Xs
Freigabe des Benutzers mit dem Namen: John

Fügen Sie dies nun am Ende von runScenario () hinzu:
 user.add(phone: iPhone) 


Hier fügen wir unser iPhone zur Liste der Telefone hinzu, die dem Benutzer gehören , und setzen die Besitzereigenschaft des Telefons auf " Benutzer ".

Führen Sie die Anwendung erneut aus. Sie werden sehen, dass Benutzer- und iPhone-Objekte nicht freigegeben werden. Der Zyklus starker Verbindungen zwischen ihnen verhindert, dass der ARC sie freigibt.



Links schwach


Um den Kreislauf starker Verbindungen zu durchbrechen, können Sie die Beziehung zwischen Objekten als schwach festlegen.

Standardmäßig sind alle Links stark und die Zuweisung führt zu einer Erhöhung der Referenzanzahl. Bei Verwendung schwacher Referenzen erhöht sich die Referenzanzahl nicht.

Mit anderen Worten, schwache Glieder wirken sich nicht auf das Lebensmanagement eines Objekts aus . Schwache Links werden immer als optional deklariert. Auf diese Weise kann die Verbindung auf Null gesetzt werden, wenn die Anzahl der Verbindungen 0 wird.



In dieser Abbildung zeigen gestrichelte Linien schwache Glieder an. Beachten Sie, dass der Referenzzähler von Objekt1 1 ist, da Variable1 darauf verweist. Der Referenzzähler von Objekt2 ist 2, da er von Variable2 und Objekt1 referenziert wird.

Objekt2 verweist auch auf Objekt1, aber SCHWACH , was bedeutet, dass es die Referenzanzahl von Objekt1 nicht beeinflusst.

Wenn Variable1 und Variable2 freigegeben werden, hat Objekt1 einen Referenzzähler von 0, wodurch es freigegeben wird. Dies wiederum gibt einen starken Verweis auf object2 frei, was bereits zu seiner Freigabe führt.

Ändern Sie in der Phone-Klasse die Eigentümerdeklaration wie folgt:

 weak var owner: User? 

Indem wir die Eigentümer-Eigenschaftsreferenz als "schwach" deklarieren, unterbrechen wir die Schleife der starken Verbindungen zwischen den Klassen "Benutzer" und "Telefon".



Starten Sie die App. Jetzt werden Benutzer und Telefon korrekt freigegeben.

Nicht besessene Links


Es gibt auch einen anderen Link-Modifikator, der die Referenzanzahl nicht erhöht: nicht besessen .

Was ist der Unterschied zwischen nicht besessen und schwach ? Eine schwache Referenz ist immer optional und wird automatisch null, wenn das referenzierte Objekt freigegeben wird.

Aus diesem Grund sollten wir schwache Eigenschaften als optionale Variable vom Typ deklarieren: Diese Eigenschaft muss sich ändern.

Nicht besessene Links sind dagegen niemals optional. Wenn Sie versuchen, auf eine nicht besessene Eigenschaft zuzugreifen, die auf ein freigegebenes Objekt verweist, wird ein Fehler angezeigt, der wie ein Force-Unwrap mit einer Null-Variablen aussieht (Force-Unwrapping).



Lassen Sie uns versuchen, nicht besessen zu bewerben.

Fügen Sie am Ende von MainViewController.swift eine neue CarrierSubscription- Klasse hinzu:

 class CarrierSubscription { let name: String let countryCode: String let number: String let user: User init(name: String, countryCode: String, number: String, user: User) { self.name = name self.countryCode = countryCode self.number = number self.user = user print("CarrierSubscription \(name) is initialized") } deinit { print("Deallocating CarrierSubscription named: \(name)") } } 

CarrierSubscription hat vier Eigenschaften:

Name: Anbietername.
CountryCode: Ländercode.
Nummer: Telefonnummer.
Benutzer: Link zum Benutzer.

Wer ist Ihr Provider?


Fügen Sie dies nun der Benutzerklasse nach der Eigenschaft name hinzu:

 var subscriptions: [CarrierSubscription] = [] 

Hier halten wir eine Reihe von Benutzeranbietern.

Fügen Sie dies nun der Phone-Klasse nach der Eigentümer-Eigenschaft hinzu:

 var carrierSubscription: CarrierSubscription? func provision(carrierSubscription: CarrierSubscription) { self.carrierSubscription = carrierSubscription } func decommission() { carrierSubscription = nil } 

Dadurch werden die optionale CarrierSubscription-Eigenschaft und zwei Methoden zum Registrieren und Aufheben der Registrierung des Telefons beim Anbieter hinzugefügt.

Fügen Sie nun die CarrierSubscription-Klasse direkt vor der print-Anweisung in die init-Methode ein:

 user.subscriptions.append(self) 

Wir fügen CarrierSubscription dem Array der Benutzeranbieter hinzu.

Fügen Sie dies schließlich am Ende der runScenario () -Methode hinzu:

 let subscription = CarrierSubscription( name: "TelBel", countryCode: "0032", number: "31415926", user: user) iPhone.provision(carrierSubscription: subscription) 

Wir erstellen für den Benutzer ein Abonnement für den Anbieter und verbinden das Telefon damit.

Starten Sie die App. In der Konsole sehen Sie:

Benutzer John wurde initialisiert
Telefon iPhone Xs wurde initialisiert
CarrierSubscription TelBel wird initialisiert

Und wieder ein Linkzyklus! Benutzer, iPhone und Abonnement waren am Ende nicht kostenlos.

Kannst du ein Problem finden?



Die Kette brechen


Entweder muss der Link vom Benutzer zum Abonnement oder der Link vom Abonnement zum Benutzer nicht besessen sein, um die Schleife zu durchbrechen. Die Frage ist, welche Option zu wählen ist. Schauen wir uns die Strukturen an.

Ein Benutzer besitzt ein Abonnement für einen Anbieter, aber umgekehrt - nein, ein Abonnement für einen Anbieter besitzt keinen Benutzer.

Darüber hinaus macht es keinen Sinn, CarrierSubscription ohne Bezugnahme auf den Benutzer zu besitzen, dem es gehört.

Daher muss der Benutzerlink nicht besessen sein.

Ändern Sie die Benutzerdeklaration in CarrierSubscription:

 unowned let user: User 

Jetzt Benutzer nicht besessen, wodurch die Schleife der Links unterbrochen wird und Sie alle Objekte freigeben können.



Loop-Links in Verschlüssen


Verknüpfungszyklen für Objekte treten auf, wenn Objekte Eigenschaften haben, die aufeinander verweisen. Verschlüsse sind wie Objekte ein Referenztyp und können zu Referenzschleifen führen. Verschlüsse erfassen Objekte, die sie verwenden.

Wenn Sie beispielsweise einer Eigenschaft einer Klasse einen Abschluss zuweisen und dieser Abschluss Eigenschaften derselben Klasse verwendet, erhalten wir eine Verknüpfungsschleife. Mit anderen Worten, das Objekt enthält eine Verknüpfung zum Abschluss über die Eigenschaft. Der Verschluss enthält einen Verweis auf das Objekt durch den erfassten Wert des Selbst.



Fügen Sie diesen Code unmittelbar nach der Benutzereigenschaft zu CarrierSubscription hinzu:

 lazy var completePhoneNumber: () -> String = { self.countryCode + " " + self.number } 

Dieser Abschluss berechnet und gibt die vollständige Telefonnummer zurück. Die Eigenschaft wird als faul deklariert, sie wird bei der ersten Nutzung zugewiesen.

Dies ist erforderlich, da self.countryCode und self.number verwendet werden, die erst verfügbar sind, wenn der Initialisierungscode ausgeführt wird.

Fügen Sie am Ende runScenario () hinzu:

 print(subscription.completePhoneNumber()) 

Durch Aufrufen von completePhoneNumber () wird der Abschluss ausgeführt.

Starten Sie die Anwendung und Sie werden sehen, dass Benutzer und iPhone freigegeben sind, CarrierSubscription jedoch nicht, da starke Verknüpfungen zwischen dem Objekt und dem Abschluss bestehen.



Erfassungslisten


Swift bietet eine einfache und elegante Möglichkeit, die Schleife starker Glieder in Verschlüssen zu durchbrechen. Sie deklarieren eine Erfassungsliste, in der Sie die Beziehung zwischen dem Abschluss und den erfassten Objekten definieren.

Beachten Sie den folgenden Code, um die Erfassungsliste zu demonstrieren:

 var x = 5 var y = 5 let someClosure = { [x] in print("\(x), \(y)") } x = 6 y = 6 someClosure() // Prints 5, 6 print("\(x), \(y)") // Prints 6, 6 

x befindet sich in der Abschlusserfassungsliste, daher wird der Wert von x in die Abschlussdefinition kopiert. Es wird nach Wert erfasst.

y ist nicht in der Erfassungsliste enthalten, sondern wird als Referenz erfasst. Dies bedeutet, dass der Wert von y so ist, wie er zum Zeitpunkt des Aufrufs der Schaltung war.

Sperrlisten helfen dabei, schwache oder nicht besessene Interaktionen in Bezug auf Objekte zu identifizieren, die in der Schleife erfasst wurden. In unserem Fall ist die entsprechende Auswahl nicht im Besitz, da ein Abschluss nicht vorhanden sein kann, wenn die CarrierSubscription-Instanz freigegeben wird.

Ergreifen Sie sich


Ersetzen Sie die vollständige Telefonnummer-Definition durch CarrierSubscription ::

 lazy var completePhoneNumber: () -> String = { [unowned self] in return self.countryCode + " " + self.number } 

Wir fügen der Liste der Abschlusserfassungen [nicht besessenes Selbst] hinzu . Dies bedeutet, dass wir uns selbst als nicht besessenes Glied anstatt als starkes gefangen genommen haben.

Starten Sie die Anwendung und Sie werden sehen, dass CarrierSubscription jetzt freigegeben ist.

Tatsächlich ist die obige Syntax eine Kurzform einer längeren und vollständigeren, in der eine neue Variable erscheint:

 var closure = { [unowned newID = self] in // Use unowned newID here... } 

Hier ist newID eine nicht im Besitz befindliche Kopie von self. Über die Schließung hinaus bleibt das Selbst sich selbst. In der zuvor angegebenen Kurzform erstellen wir eine neue Selbstvariable , die das vorhandene Selbst innerhalb des Verschlusses verdeckt.

Unbesessen vorsichtig verwenden


In Ihrem Code wird die Beziehung zwischen self und completePhoneNumber als nicht besessen festgelegt.

Wenn Sie sicher sind, dass das im Abschluss verwendete Objekt nicht freigegeben wird, können Sie unbesessen verwenden. Wenn er es tut, sind Sie in Schwierigkeiten!

Fügen Sie diesen Code am Ende von MainViewController.swift hinzu:

 class WWDCGreeting { let who: String init(who: String) { self.who = who } lazy var greetingMaker: () -> String = { [unowned self] in return "Hello \(self.who)." } } 

Hier ist das Ende von runScenario ():

 let greetingMaker: () -> String do { let mermaid = WWDCGreeting(who: "caffeinated mermaid") greetingMaker = mermaid.greetingMaker } print(greetingMaker()) // ! 

Starten Sie die Anwendung und Sie werden einen Absturz und ähnliches in der Konsole sehen:

Benutzer John wurde initialisiert
Telefon iPhone XS wurde initialisiert
CarrierSubscription TelBel wird initialisiert
0032 31415926
Schwerwiegender Fehler: Es wurde versucht, eine nicht besessene Referenz zu lesen, das Objekt 0x600000f0de30 wurde jedoch bereits freigegeben

Eine Ausnahme trat auf, weil die Schließung darauf wartet, dass self.who existiert, aber sie wurde freigegeben, sobald die Meerjungfrau am Ende des do-Blocks den Gültigkeitsbereich verlassen hat.

Dieses Beispiel mag von einem Finger gesaugt aussehen, aber solche Dinge passieren. Wenn wir beispielsweise Schließungen verwenden, um etwas viel später zu starten, beispielsweise nachdem der asynchrone Anruf im Netzwerk beendet wurde.

Entschärfe die Falle


Ersetzen Sie greetingMaker in der WWDCGreeting-Klasse durch Folgendes:

 lazy var greetingMaker: () -> String = { [weak self] in return "Hello \(self?.who)." } 

Wir haben zwei Dinge getan: Erstens haben wir unbesessen durch schwach ersetzt. Zweitens, da das Selbst schwach geworden ist, greifen wir über das Selbst auf das Wer-Eigentum zu. Wer. Ignorieren Sie die Xcode-Warnung, wir werden sie bald beheben.

Die Anwendung stürzt nicht mehr ab, aber wenn Sie sie ausführen, erhalten wir ein lustiges Ergebnis: "Hallo Null."

Vielleicht ist das Ergebnis durchaus akzeptabel, aber oft müssen wir etwas tun, wenn das Objekt freigegeben wurde. Dies kann mithilfe der Guard-Anweisung erfolgen.

Ersetzen Sie den Schließungstext durch:

 lazy var greetingMaker: () -> String = { [weak self] in guard let self = self else { return "No greeting available." } return "Hello \(self.who)." } 

Die Wachaussage weist das Selbst dem schwachen Selbst zu. Wenn self null ist, gibt der Abschluss "Keine Begrüßung verfügbar" zurück. Andernfalls wird das Selbst zu einer starken Referenz, sodass das Objekt garantiert bis zum Ende des Verschlusses lebt.

Suche nach Link-Loops in Xcode 10


Nachdem Sie nun verstanden haben, wie ARC funktioniert, was Verbindungsschleifen sind und wie sie unterbrochen werden, ist es Zeit, ein Beispiel für eine echte Anwendung zu sehen.

Öffnen Sie das Starter-Projekt im Ordner "Kontakte".

Starten Sie die App.



Dies ist der einfachste Kontaktmanager. Klicken Sie auf einen Kontakt und fügen Sie einige neue hinzu.

Dateizuordnung:

ContactsTableViewController: Zeigt alle Kontakte an.
DetailViewController: Zeigt die detaillierten Informationen des ausgewählten Kontakts an.
NewContactViewController: Ermöglicht das Hinzufügen eines neuen Kontakts.
ContactTableViewCell: Tabellenzelle mit Kontaktdetails.
Kontakt: Kontaktmodell.
Nummer: Telefonnummer Modell.

Bei diesem Projekt ist jedoch alles schlecht: Es gibt einen Zyklus von Links. Zunächst werden Benutzer aufgrund der geringen Größe des undichten Speichers keine Probleme bemerken. Aus dem gleichen Grund ist es schwierig, das Leck zu finden.

Glücklicherweise verfügt Xcode 10 über integrierte Tools, um den kleinsten Speicherverlust zu finden.

Starten Sie die Anwendung erneut. Löschen Sie 3-4 Kontakte mit dem Wischen nach links und der Schaltfläche Löschen. Es scheint, als ob sie vollständig verschwinden, oder?



Wo fließt es?


Wenn die Anwendung ausgeführt wird, klicken Sie auf die Schaltfläche Debug Memory Graph:



Beachten Sie die Laufzeitprobleme im Debug-Navigator. Sie sind mit lila Quadraten mit einem weißen Ausrufezeichen gekennzeichnet:



Wählen Sie im Navigator eines der problematischen Kontaktobjekte aus. Der Zyklus ist deutlich sichtbar: Die aufeinander bezogenen Objekte Contact und Number halten.



Sieht so aus, als sollten Sie sich den Code ansehen. Denken Sie daran, dass ein Kontakt ohne Nummer existieren kann, aber nicht umgekehrt.

Wie würden Sie diese Schleife lösen? Link von Kontakt zu Nummer oder von Nummer zu Kontakt? schwach oder nicht besessen? Probieren Sie es zuerst selbst aus!

Wenn Sie Hilfe brauchten ...
Es gibt zwei mögliche Lösungen: entweder einen Link von Kontakt zu Nummer schwach machen oder von Nummer zu Kontakt nicht besitzen.

In der Dokumentation von Apple wird empfohlen, dass das übergeordnete Objekt einen starken Verweis auf "untergeordnetes Objekt" enthält - nicht umgekehrt. Dies bedeutet, dass wir Contact einen starken Verweis auf Number und Number geben - einen nicht besessenen Link zu Contact:

 class Number { unowned var contact: Contact // Other code... } class Contact { var number: Number? // Other code... } 


Bonus: Schleifen mit Referenztypen und Werttypen.


Swift verfügt über Referenztypen (Klassen und Abschlüsse) und Werttypen (Strukturen, Aufzählungen). Der Werttyp wird kopiert, wenn er übergeben wird, und Referenztypen haben über den Link denselben Wert.

Dies bedeutet, dass es bei Werttypen keine Zyklen geben kann. Damit eine Schleife auftritt, benötigen wir mindestens 2 Referenztypen.

Kehren wir zum Cycles-Projekt zurück und fügen diesen Code am Ende von MainViewController.swift hinzu:

 struct Node { // Error var payload = 0 var next: Node? } 

Wird nicht funktionieren! Struktur ist ein Werttyp und kann keine Rekursion für eine Instanz von sich selbst haben. Andernfalls hätte eine solche Struktur eine unendliche Größe.

Ändern Sie die Struktur in eine Klasse.

 class Node { var payload = 0 var next: Node? } 

Der Verweis auf sich selbst ist für Klassen (Referenztyp) durchaus akzeptabel, sodass der Compiler keine Probleme hat.

Fügen Sie dies nun am Ende von MainViewController.swift hinzu:

 class Person { var name: String var friends: [Person] = [] init(name: String) { self.name = name print("New person instance: \(name)") } deinit { print("Person instance \(name) is being deallocated") } } 

Und das ist am Ende von runScenario ():

 do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(bert) // Not deallocated bert.friends.append(ernie) // Not deallocated } 

Starten Sie die App. Bitte beachten Sie: Weder ernie noch bert werden veröffentlicht.

Link und Bedeutung


Dies ist ein Beispiel für eine Kombination aus einem Referenztyp und einem Werttyp, die zu einer Verbindungsschleife geführt hat.

ernie und bert bleiben unveröffentlicht und halten sich gegenseitig in den Arrays ihrer Freunde, obwohl die Arrays selbst Werttypen sind.

Versuchen Sie, das Archiv der Freunde als nicht besessen zu machen, und Xcode zeigt einen Fehler an: nicht besessen gilt nur für Klassen.

Um diese Schleife zu beheben, müssen wir ein Wrapper-Objekt erstellen und damit dem Array Instanzen hinzufügen.

Fügen Sie vor der Person-Klasse die folgende Definition hinzu:

 class Unowned<T: AnyObject> { unowned var value: T init (_ value: T) { self.value = value } } 

Ändern Sie dann die Definition von Freunden in der Person-Klasse:

 var friends: [Unowned<Person>] = [] 

Ersetzen Sie abschließend den Inhalt des do-Blocks in runScenario ():

 do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(Unowned(bert)) bert.friends.append(Unowned(ernie)) } 

Starten Sie die Anwendung, jetzt sind ernie und bert korrekt freigegeben!

Das Friends-Array ist keine Sammlung von Personenobjekten mehr. Dies ist jetzt eine Sammlung von nicht besessenen Objekten , die als Wrapper für Personeninstanzen dienen.

Verwenden Sie die value-Eigenschaft, um Personenobjekte von Unowners abzurufen:

 let firstFriend = bert.friends.first?.value // get ernie 

Fazit


Sie haben jetzt ein gutes Verständnis der Speicherverwaltung in Swift und wissen, wie ARC funktioniert. Ich hoffe, die Veröffentlichung hat Ihnen geholfen.

Apple: Automatische Referenzzählung

Source: https://habr.com/ru/post/de451130/


All Articles