Rekursive / Iterative NSURLSessionDataTask verursacht Speicherverlust

8

Ich habe ein Problem mit Speicherverlust in meinem Code, ich muss viele URLs in schneller Folge abholen, jedes GET wird durch das Ergebnis des vorherigen GET beeinflusst. Der Zweck besteht darin, nach einem bestimmten Inhalt in der Antwort zu suchen.

Ich fand den saubersten Weg, um dies zu implementieren, rekursiv, da ich dieselbe Methode verwenden kann, um festzustellen, ob der gewünschte Wert in der Antwort vorhanden ist. Funktional funktioniert es sehr gut, aber es verliert Speicher wie unten beschrieben. Ich habe auch die gleiche Funktionalität in einer iterativen Weise implementiert, und dies führt auch zu Speicherverlust.

Meiner Meinung nach ist die API NSURLSession für das Lecken dieses Speichers zuständig und tritt nur dann auf, wenn mehrere Aufrufe in sehr kurzer Zeit erfolgen. Allerdings würde ich mich freuen, wenn irgendjemand offensichtliche Fehler aufzeigen könnte, die ich mache.

Update 10/09/14:

Wurde aktualisiert, um einen Rekursionszähler hinzuzufügen. Dies zeigt, dass das Leck immer noch auftritt, auch wenn der Code nicht unendlich oft ausgeführt wird. Außerdem wurde die Implementierung leicht aufgeräumt und die Eigenschaften NSURLSession und NSURLSessionConfiguration als Eigenschaften im View-Controller wiederverwendet.

Beispielcode:

%Vor%

Der Speicher leckt wie von den Instrumenten gemeldet. (NB: Diese variieren leicht jedes Mal, aber in den meisten Fällen enthalten die gleichen Lecks, nur mehr oder weniger der gleichen Lecks):

Weitere Aktualisierung:

Also habe ich den gleichen Code iterativ implementiert, und der Speicherleck tritt immer noch auf. In diesem Beispiel habe ich einen Loop-Limiter eingebaut, damit er nicht für immer ausgeführt wird. Kann mir jemand helfen, herauszufinden, was auf der Welt hier vor sich geht?

%Vor%

Update 10/09/14:

Dies tritt immer noch auf iOS 8 für alle Google-Nutzer auf, die ihren Weg hierher gefunden haben könnten. Soweit es mich betrifft, ist dies ein Fehler in iOS.

    
Sammio2 08.09.2014, 10:08
quelle

4 Antworten

4

- Update 9/12/2014

Lösung: warte auf iOS8.

- Update 9/10/2014

Whoa, das geht in eine N-te Dimension der Komplexität: P. Ich hoffe auf die eine oder andere Weise, dass Sie hier schnell eine Pause bekommen.

Ich habe ein paar andere Dinge für Sie zu versuchen.

1) Könnten Sie sicherstellen, dass NSZombies ausgeschaltet ist? In Xcode, Produkt- & gt; Schema- & gt; Bearbeiten Scheme ...- & gt; Zombie-Objekte aktivieren (NICHT angekreuzt).

2) Probieren Sie auch cachePolicy:NSURLCacheStorageNotAllowed für Ihre NSMutableURLRequest .

3) Können Sie sehen, ob Sie mit einem Fehler fertig sind? Legen Sie das einfach um Ihre Body-String-Zuordnung ...

%Vor%

4) Könnten Sie sehen, ob Sie nicht den Status 200 erhalten?

%Vor%

5) Es ist schwierig sich genau vorzustellen, wie Ihr Projekt aufgebaut ist. Ich hätte eine NSObject-Typklasse, die die NSURLSession-Methoden enthält, die von der UIViewController-Klasse getrennt ist, aus der sie aufgerufen wird. Der Timer oder eine andere Rekursionsmethode, die Sie auswählen möchten, ruft dann die mit der URL-Sitzung verknüpften Methoden von UIViewController auf.

- Update 9.9.2014

Sie haben meine Frage richtig beantwortet (2). Die Datenaufgabe wird vor der Fertigstellung fortgesetzt und nachdem die Datenaufgabe abgeschlossen ist, wird die Sitzung ungültig gemacht. Ich habe es nicht so gesehen, aber es macht Sinn. Gerade getestet an meinem Ende, keine Lecks in Bezug auf [Sitzung invalidateAndCancel] ...

Könnten Sie überprüfen, ob Ihr Beendigungshandler ausgeführt wird? Vielleicht nicht und die Sitzung wird nie abgebrochen, bevor eine neue Aufgabe gestartet wird?

Ich bemerke, dass es im Report "Geräte-Lecks" einige Verweise auf HTTP-Header gibt. Wenn Sie beispielsweise keine [urlRequest setHTTPMethod: @ "GET"] angeben, fehlen einige grundlegende Header?

(Ich werde bearbeiten, nachdem wir die Lösung gefunden haben, also sieht das nicht wie eine Diskussion aus).

- Original 9/8/2014

Interessante Frage! Ich habe Probleme mit NSURLSessions. Definitiv @autoreleasepool {} und andere sind gute Vorschläge, um so weit zu versuchen ... Aber!

Ich fürchte, das Ding, von dem Sie uns aufgefordert haben, vorbeizuschauen, könnte hier der Schuldige sein.

Nur ein paar Beobachtungen zuerst:

1) Es ist mir nicht klar, warum du das Selbst hier schwächen musst. Was ist der Retain-Zyklus, den Sie vermeiden möchten? Vielleicht ist das klarer in dem Code, den Sie tatsächlich neben Ihrer "Probe" verwenden.

2) Was ist der Grund für den Aufruf, die Sitzung ungültig zu machen, bevor die Datenaufgabe, die dieser Sitzung zugeordnet ist, überhaupt eine Chance hat, abzuschließen, geschweige denn fortzusetzen? Der Daten-Task befindet sich im angehaltenen Zustand, bis er fortgesetzt wird.

3) Wenn Sie eine Methode wie diese rekursiv ausführen, dann ist es wichtig, dass Sie angeben oder zumindest überlegen, welche Delegate-Warteschlange, andernfalls auf Null gesetzt ist, wird sie standardmäßig in die Warteschlange für serielle Operationen verschoben. Was passiert, wenn der Delegat vor Abschluss des Completion-Handlers in einer Endlosschleife - höchstwahrscheinlich ein großer Stapel - aufruft.

-

Ich glaube, dass das Hauptproblem hier ist, dass Sie eine neue starten oder die NSURLSessionDataTask abbrechen, bevor sie abgeschlossen werden kann. Schau dir + sessionWithConfiguration an:

(Entschuldigung, Bilder können noch nicht eingefügt werden, hoffentlich nach dieser Antwort)

Ссылка :

Der Punkt ist hier ...

  

Wichtig

     

Das Sitzungsobjekt behält eine starke Referenz auf den Delegaten   bis Ihre App die Sitzung explizit ungültig macht. Wenn Sie nicht   nullen Sie die Sitzung durch Aufruf von invalidateAndCancel oder   resetWithCompletionHandler: Methode, Ihre App verliert Speicher.

Mein Vorschlag zu versuchen ist ...

%Vor%

In der Theorie sollte dies verhindern, dass neue Sitzungen vor dem Abschluss beginnen, gemäß der Beschreibung "... neue Aufgaben können nicht in der Sitzung erstellt werden, aber bestehende Aufgaben werden bis zum Abschluss fortgesetzt. Nach dem letzten Vorgang und der Sitzung Der letzte Delegat-Aufruf, Verweise auf die Delegate und Callback-Objekte sind gebrochen ... "

Ich bin mir immer noch nicht sicher, ob ich die Sitzung für ungültig erklären soll, bevor ich sie wieder aufnehme.

Ich hoffe, das hilft. Viel Glück.

    
serge-k 09.09.2014 08:16
quelle
3

Eine Entwickler-Support-Anfrage an Apple zeigt, dass dies ein Fehler in iOS 7 ist. Es gibt keinen Fehler mit dem oben genannten Code-Beispiel (entweder rekursiv oder iterativ) und es wurde Berichten zufolge in der iOS 8 GM-Version behoben.

Aktualisierung:

Dies geschieht immer noch in iOS 8.1

    
Sammio2 11.09.2014 12:11
quelle
1

Ich hatte viele Probleme mit dem Speicher von NSURLSession und habe es schließlich behoben, indem ich keine neue Sitzung für jede Anfrage verwendete. Sitzungen sind in der Regel auf Wikipedia definiert als:

  

ein semi-permanenter interaktiver Informationsaustausch

Damit gibt Apples Convenience-Class-Methode [NSURLSession sharedSession] einen Hinweis darauf, wie NSURLSession-Objekte verwendet werden sollen: Als semipermanente Objekte werden keine einzelnen Objekte für jede Anfrage neu erstellt, wie Sie es tun.

Sie erstellen ein neues Sitzungsobjekt pro Anforderung für eine Vielzahl von Anforderungen, die aus Sicht des Servers alle Teil einer einzelnen Sitzung mit einem einzelnen Client sind.

Ich tat dasselbe, bis ich realisierte, dass dies die Quelle meiner Leiden war. Ich fand die Dokumentation von Apple zu diesem Thema nicht klar, aber nachdem ich den Fehler meiner Wege erkannt hatte, machte es bestimmte Dinge in der Dokumentation plötzlich sinnvoller, wie warum es eine sharedSession Singleton Convenience-Methode von NSURLSession gibt, warum das Wort "Aufgaben" ist plural in finishTasksAndInvalidate , warum sie es eine "Sitzung" nannten, warum es einen Cache hat, usw. (Wenn es nur für eine Anfrage war, warum sollte es eine "Sitzung" sein "Und was nützt ein" Cache "?"

Es hilft zu wissen, wie ein Browser wie Safari in einer Sitzung aussieht. Eine neue Sitzung beginnt, wenn Sie das erste Mal eine Verbindung zu einem bestimmten Server herstellen. Das Einrichten der Sitzung umfasst das Erstellen eines Caches von SSL-Zertifikaten, das Einrichten von Authentifizierung, Handshake usw. Es wäre außerordentlich ineffizient, all dies jedes Mal zu tun, wenn JavaScript auf einer Seite eine neue Anfrage an den gleichen Server stellt, insbesondere seit modernen Web-Apps ständig Anfragen mit Rückrufen etc. machen. Deshalb wird eine einzige Sitzung für eine ganze Menge von Anfragen und Antworten eingerichtet - eine Konversation , wenn Sie so wollen, zwischen dem Client und Server. Schließlich läuft eine Sitzung ab, aber normalerweise geschieht dies nach mehreren Minuten, nicht nach einer Anfrage !

Der Punkt ist, wie Sie NSURLSession-Objekte verwenden sollten, um ein Singleton mit einem stark referenzierten NSURLSession-Objekt als Eigenschaft zu erstellen. Tun Sie dies, wenn Sie die Konfiguration der Sitzung anpassen müssen (z. B. Deaktivieren des Caching usw.). Wenn Sie es jedoch nicht anpassen müssen, verwenden Sie einfach Apples sharedSession .

Wenn Sie für eine benutzerdefinierte Klasse ein Singleton verwenden, müssen Sie nie die% s% -Zeit% oder invalidateAndCancel eingeben, wenn Sie die Sitzungseigenschaft nie auf null setzen müssen. Stattdessen nur finishTasksAndInvalidate oder resetWithCompletionBlock , um Verbindungscaches regelmäßig zu löschen.

Wenn Sie Singletons hassen, können Sie immer noch eine Sitzung als Eigenschaft verwenden, stellen Sie sicher, dass flushWithCompletionBlock oder invalidateAndCancel die Sitzung ist, bevor der letzte Besitzer von der ARC-Laufzeit freigegeben wird.

Beachten Sie auch, dass das Festlegen der Eigenschaft finishTasksAndInvalidate des NSURLSession-Objekts auf URLCache die richtige Methode zum Abschalten des Caching ist. Das sagt Apple für nil .

Siehe meine anderen Antworten zu diesem Thema hier und hier .

    
CommaToast 05.03.2016 20:43
quelle
-1

Die einzigen Vorschläge, die ich habe, sind vielleicht ein @autoreleasepool{} und die Umwandlung von __weak id self nach __block id self . Ich denke nicht, dass __block vs. __weak etwas anderes machen, aber versuchen Sie es.

Ich bin nicht sicher, was man mit ARC erwarten sollte, wenn man etwas asynchron UND rekursiv ausführt. Wenn Sie andere Fragen mit asynchronen rekursiven Aufrufen und ARC betrachten, gibt es keine konsistente Lösung. Schauen Sie sich hier zum Beispiel an.

    
navkast 08.09.2014 15:07
quelle