
Benutzer erwarten, dass das Netzwerk „magisch“ und unbemerkt funktioniert. Diese Magie hängt von den Entwicklern des Systems und der Anwendungen ab. Es ist schwierig, das System zu beeinflussen, daher beschränken wir uns auf die Anwendung.
Dieses Thema ist komplex und es gibt unzählige Probleme. Wir werden diejenigen diskutieren, denen wir in den letzten Monaten begegnet sind. Ich entschuldige mich sofort für die Lautstärke. Kurz gesagt, keineswegs zu viele kleine Dinge, die es wert sind, beachtet zu werden.
Zunächst beschäftigen wir uns mit der Terminologie.
Die Datenübertragung erfolgt in zwei Richtungen:
- Herunterladen (Herunterladen, Herunterladen von Daten vom Server),
- Upload (Senden von Daten an den Server).
Die Anwendung ist möglicherweise aktiv, arbeitet jedoch möglicherweise im Hintergrund. Formal hat er andere Zustände , aber wir interessieren uns nur für diese:
- Hintergrund (wenn die Anwendung minimiert ist),
- aktiv (wenn die Anwendung aktiv ist, auf dem Bildschirm).
Nützliche Muster: Rückruf , Delegat ( Cocoa Design Patterns , über Rückruf auf Wikipedia ). Sie müssen auch wissen, URLSession
(in dem Artikel erwähnt der Link auch die Hintergrundarbeit mit dem Netzwerk, aber im Vorbeigehen).
Alle Beispiele sind in Swift 5 geschrieben , funktionieren unter iOS 11 und neuer (getestet unter iOS 11 und 12) und setzen die Verwendung regulärer HTTP-Anforderungen voraus. Zum größten Teil wird dies alles ab iOS 9 funktionieren, aber es gibt "Nuancen".
Das allgemeine Schema der Arbeit mit dem Netzwerk. URLSession
Die Arbeit mit dem Netzwerk ist nicht besonders schwierig:
- Erstellen Sie die
URLSessionConfiguration
Konfiguration. - Erstellen Sie eine Konfigurationsinstanz von
URLSession
. - Erstellen Sie eine Aufgabe (mit
session.dataTask(…)
und ähnlichen Methoden). - Task-Updates abonnieren. Aktualisierungen erfolgen asynchron, sie können an den Delegaten gesendet werden, der beim Erstellen der Sitzung registriert wird, oder sie befinden sich im Rückruf, der beim Erstellen der Aufgabe erstellt wird.
- Als wir sahen, dass die Aufgabe abgeschlossen ist, kehren wir zur Anwendungslogik zurück.
Ein einfaches Beispiel sieht so aus:
let session = URLSession(configuration: .default) let url = URL(...) let dataTask = session.dataTask(with: url) { data, response, error in ...
Dieses Schema ist für verschiedene Aufgaben ähnlich, nur die kleinen Dinge ändern sich. Und bis wir nicht weiter mit dem Netzwerk arbeiten müssen, nachdem der Benutzer die Anwendung geschlossen hat, ist alles relativ einfach.
Ich stelle sofort fest, dass es auch in diesem Szenario viele interessante Dinge gibt. Manchmal müssen Sie mit kniffligen Weiterleitungen arbeiten, manchmal benötigen Sie eine Autorisierung, SSL-Fixierung oder alles gleichzeitig. Sie können viel darüber lesen. Aus irgendeinem Grund wird das Arbeiten mit dem Netzwerk im Hintergrund viel weniger beschrieben.
Erstellen einer Sitzung für die Arbeit im Hintergrund
Was ist der Unterschied zwischen der URLSession im Hintergrund und der üblichen? Es funktioniert außerhalb des Anwendungsprozesses, irgendwo innerhalb des Systems. Daher "stirbt" es nicht, wenn der Bewerbungsprozess abgeschlossen ist. Es wird als Hintergrundsitzung bezeichnet (ebenso wie der etwas verwirrende Status der Anwendung) und erfordert bestimmte Einstellungen. Zum Beispiel das:
let configuration = URLSessionConfiguration.background(withIdentifier: "com.my.app") configuration.sessionSendsLaunchEvents = true configuration.isDiscretionary = true configuration.allowsCellularAccess = true configuration.shouldUseExtendedBackgroundIdleMode = true configuration.waitsForConnectivity = true URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
Die Konfiguration enthält viele andere Parameter, die sich jedoch direkt auf Hintergrundsitzungen beziehen:
- bezeichner (im Initialisierer übergeben) ist eine Zeichenfolge, die verwendet wird, um Hintergrundsitzungen abzugleichen, wenn die Anwendung neu gestartet wird. Wenn die Anwendung neu gestartet wird und Sie eine Hintergrundsitzung mit einer ID erstellen, die bereits in einer anderen Hintergrundsitzung verwendet wird, kann die neue auf die Aufgaben der vorherigen zugreifen. Die Schlussfolgerung daraus ist einfach. Für einen ordnungsgemäßen Betrieb muss diese Kennung für Ihre Anwendung eindeutig und dauerhaft sein (Sie können beispielsweise eine Ableitung von BundleId- Anwendungen verwenden).
- sessionSendsLaunchEvents gibt an, ob die Hintergrundsitzung die Anwendung starten soll, wenn die Datenübertragung abgeschlossen ist. Wenn dieser Parameter auf
false,
der Trigger nicht auf und die Anwendung empfängt beim nächsten Start alle Ereignisse. Wenn der Parameter true
ist, startet das System nach Abschluss der Datenübertragung die Anwendung und ruft die entsprechende AppDelegate: application(_:handleEventsForBackgroundURLSession:completionHandler:)
Methode auf AppDelegate: application(_:handleEventsForBackgroundURLSession:completionHandler:)
; - Mit isDiscretionary kann das System Aufgaben seltener planen. Dies verbessert einerseits die Batterielebensdauer und kann andererseits die Aufgabe verlangsamen. Oder vielleicht beschleunigen. Wenn beispielsweise ein großes Volumen heruntergeladen wird, kann das System die Aufgabe anhalten, bis eine Verbindung zum WLAN hergestellt ist, und dann schnell alles herunterladen, ohne langsames mobiles Internet ausgeben zu müssen (wenn dies überhaupt zulässig ist, wie geht es weiter). Wenn die Aufgabe erstellt wird, während sich die Anwendung bereits im Hintergrund befindet, wird dieser Parameter automatisch auf
true
. - allowCellularAccess - Ein Parameter, der angibt , dass Sie die Mobilfunkkommunikation für die Arbeit mit dem Netzwerk verwenden können. Ich habe nicht sorgfältig mit ihm gespielt, aber laut Rezensionen sind dort (zusammen mit einem ähnlichen Systemschalter) eine große Anzahl von Rechen ausgelegt;
- shouldUseExtendedBackgroundIdleMode. Ein nützlicher Parameter, der anzeigt, dass das System länger eine Verbindung zum Server aufrechterhalten sollte, wenn die Anwendung in den Hintergrund tritt. Andernfalls wird die Verbindung unterbrochen.
- waitsForConnectivity Auf einem mobilen Gerät kann die Kommunikation für kurze Zeit unterbrochen werden. Die zu diesem Zeitpunkt erstellten Aufgaben können entweder angehalten werden, bis eine Verbindung angezeigt wird, oder es wird sofort ein Fehler "Keine Verbindung" zurückgegeben. Mit dem Parameter können Sie dieses Verhalten steuern. Wenn es
false,
bricht die Task ohne Kommunikation sofort mit einem Fehler ab. Wenn dies true
, warten Sie, bis ein Link angezeigt wird. - Die letzte Zeile (Session Initializer) enthält einen wichtigen Parameter, delegate. Über ihn - ein bisschen mehr.
Delegiert gegen Rückrufe
Wie oben erwähnt, gibt es zwei Möglichkeiten, Ereignisse aus einer Aufgabe / aus einer Sitzung abzurufen. Der erste ist Rückruf:
session.dataTask(with: request) { data, response, error in ... }
In diesem Fall wird das Ereignis zur Beendigung der Aufgabe an den Abschluss gesendet, wo Sie überprüfen müssen, ob ein Fehler vorliegt, was in der Antwort enthalten ist und welche Daten eingetroffen sind.
Die zweite Möglichkeit, mit einer Sitzung zu arbeiten, besteht darin, sie zu delegieren. In diesem Fall müssen wir eine Klasse erstellen, die die URLSessionDataDelegate
Protokolle und (oder) andere Protokolle in der URLSessionDataDelegate
implementiert (für verschiedene Aufgabentypen unterscheiden sich die Protokolle geringfügig). Ein Verweis auf eine Instanz dieser Klasse befindet sich in einer Sitzung und ihre Methoden werden aufgerufen, wenn Ereignisse an den Delegaten übergeben werden. Der Link kann vom Initialisierer in der Sitzung registriert werden. Im Beispiel wird self.
URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
Für reguläre Sitzungen stehen beide Methoden zur Verfügung. Hintergrundsitzungen können nur von einem Delegierten verwendet werden.
Also haben wir die Sitzung eingerichtet, erstellt und schauen uns an, wie man etwas herunterlädt.
Allgemeines Schema zum Herunterladen von Daten im Hintergrund
Um Daten herunterzuladen, müssen Sie normalerweise eine (URLRequest)
, die erforderlichen Parameter / Header / Daten darin registrieren, eine URLSessionDownloadTask
erstellen und zur Ausführung ausführen. Ungefähr so:
var request = URLRequest(...)
Derzeit unterscheidet sich nichts wesentlich von der üblichen Download-Aufgabe. Zwar erschienen zwei Parameter countOfBytesClientExpectsToSend / countOfBytesClientExpectsToReceive , sie zeigen die Datenmenge an, die wir in der Anfrage senden und in der Antwort zurückerhalten möchten . Dies ist erforderlich, damit das System die Arbeit mit der Aufgabe korrekter planen und schneller herunterladen kann, ohne dass es zu Überarbeitungen kommt. Diese Werte müssen nicht genau sein.
Nach resume()
Aufgabe ausgeführt. Während der Datenübertragung wird der Fortschritt übertragen (dazu - siehe unten, dort gibt es auch Optionen), und nach Abschluss werden mehrere Delegierungsmethoden ausgeführt. Unter ihnen ist eines sehr wichtig:
urlSession(_:downloadTask:didFinishDownloadingTo:)
Tatsache ist, dass der Download in einer temporären Datei stattfindet, wonach die Anwendung die Möglichkeit hat, diese Datei an einen anderen Ort zu verschieben oder etwas anderes damit zu tun. Diese temporäre Datei ist nur innerhalb dieser Methode verfügbar. Nach dem Verlassen wird die Datei gelöscht und kann nicht mehr verwendet werden.
Nach dieser wichtigen Methode wird eine andere Methode aufgerufen, bei der der Fehler abfällt, wenn er auftritt. Wenn kein error
, ist der error
nil.
urlSession(_:task:didCompleteWithError:)
Und was passiert am Ende, wenn die Bewerbung in den Hintergrund tritt oder abgeschlossen ist? Wie rufe ich Delegate-Methoden auf? Hier ist es nicht einfach.
Wenn der Download von etwas, das von der Anwendung gestartet wurde, beendet wurde und sich das Flag sessionSendsLaunchEvents
in der Sitzungskonfiguration befindet, startet das System die Anwendung (im Hintergrund) und ruft die Methode application (_: handleEventsForBackgroundURLSession: completionHandler :) in AppDelegate,
.
Bei dieser Methode sollte die Anwendung:
- save
completionHandler
(muss nach einiger Zeit asynchron und im Haupt-Thread aufgerufen werden); - Erstellen Sie eine Hintergrundsitzung mit demselben Bezeichner wie zuvor neu (und der bei mehreren Hintergrundsitzungen an diese Methode übergeben wird).
- In einer neu erstellten Sitzung treffen Ereignisse beim Delegaten ein (insbesondere die sehr wichtige
urlSession(_:downloadTask:didFinishDownloadingTo:)
). Sie müssen sie verarbeiten und die Dateien kopieren, wo Sie möchten. - Nachdem alle Methoden aufgerufen wurden, wird eine andere
urlSessionDidFinishEvents(forBackgroundURLSession:)
aufgerufen, die urlSessionDidFinishEvents(forBackgroundURLSession:)
und in der Sie den completionHandler.
aufrufen müssen, der zuvor gespeichert wurde completionHandler.
Es ist wichtig. Es ist erforderlich, den completionHandler
im Hauptthread mit DispatchQueue.main.async(...)
aufzurufen.
Gleichzeitig müssen Sie bedenken, dass dies alles in einer Anwendung geschieht, die im Hintergrund arbeitet. Und das bedeutet, dass die Ressourcen (Ausführungszeit) begrenzt sind. Speichern Sie Dateien schnell an den gewünschten Stellen, ändern Sie die erforderlichen Status in der Anwendung und fahren Sie sie herunter - das ist fast alles, was getan werden kann. Wenn Sie mehr tun möchten, können Sie UIApplication.beginBackgroundTask()
oder die neuen BackgroundTasks verwenden .
Allgemeines Schema zum Senden von Hintergrunddaten
Das Hochladen von Dateien auf den Server funktioniert ebenfalls mit Einschränkungen. Alles beginnt jedoch auf ähnliche Weise: Wir bilden eine Anfrage, erstellen eine Aufgabe (jetzt ist es URLSessionUploadTask)
und führen die Aufgabe aus. Was ist das Problem?
Das Problem ist, wie wir die Anfrage erstellen. Normalerweise bilden wir die gesendeten Daten als Data
. Hintergrund URLSession,
weiß nicht, wie man damit arbeitet. Und bei einer Streaming-Anfrage ( uploadTask(withStreamedRequest:)
) weiß auch keiner wie. Es ist notwendig, alles zu schreiben, was an eine Datei gesendet werden muss, und eine Sendeaufgabe aus der Datei zu erstellen. Es stellt sich irgendwie so heraus:
var fileUrl = methodThatSavesFileAndRetursItsUrl(...) var request = URLRequest(...) let task = session.uploadTask(with: request, fromFile: fileUrl) task.resume()
Es ist jedoch nicht erforderlich, die Größe zu registrieren. URLSession
kann sie selbst URLSession
. Nach dem Senden wird dieselbe Delegate-Methode urlSession(_:task:didCompleteWithError:)
wie beim Herunterladen. Und genau so kommt die Anwendung application(_:handleEventsForBackgroundURLSession:completionHandler:),
an, wenn die Anwendung während des application(_:handleEventsForBackgroundURLSession:completionHandler:),
wurde oder in den Hintergrund trat. application(_:handleEventsForBackgroundURLSession:completionHandler:),
muss genau nach den gleichen Regeln wie beim Herunterladen von Daten verarbeitet werden.
Was ist eine Bewerbung vollständig?
Um das Herunterladen und Senden im Hintergrund zu testen, müssen Sie die Fertigstellung der Anwendung simulieren (die Hintergrundarbeit mit dem Netzwerk wurde speziell entwickelt, um dies zu überstehen). Wie kann man das machen? Anfangs - auf keinen Fall. Das heißt, es gibt keine reguläre (autorisierte, öffentliche) Methode, mit der dies möglich wäre. Mal sehen, wo der Rechen ist.
- Erstens funktioniert das Schließen der Anwendung (durch Drücken der Home-Taste oder durch Ausführen einer entsprechenden Geste) nicht. Dadurch wird die Anwendung nicht beendet, sondern nur in den Hintergrund gesendet. Die Arbeit mit einer Hintergrundsitzung hat die Bedeutung, dass sie auch dann funktioniert, wenn die Anwendung "vollständig, vollständig" beendet wurde.
- Zweitens ist es nicht möglich, dass ein Debugger (AppCode, Xcode oder nur LLDB) verbunden ist. Er lässt die Anwendung auch einige Zeit nach dem "Schließen" nicht sterben.
- Drittens können Sie die Anwendung nicht über die Taskleiste beenden (Task-Manager, Double Home oder langsames Wischen "nach oben"). Daher wird eine abgebrochene Anwendung als "dauerhaft" abgebrochen betrachtet, und das System stoppt zusammen mit einer solchen Aktion die mit der Anwendung verbundenen Hintergrundsitzungen.
- viertens müssen Sie diesen Prozess auf einem realen Gerät testen. Es gibt keine Probleme mit der Protokollierung (siehe unten) und es wird mehr debuggt. Es wird argumentiert, dass der Simulator auch so funktionieren sollte, wie er sollte. Aber ich bemerkte unerklärliche Kuriositäten, die ich nur mit den Störungen des Simulators erklären konnte. Testen Sie im Allgemeinen das Gerät.
- Die einzige sinnvolle Möglichkeit, das zu tun, was Sie wollen, ist die
exit(int)
-Funktion. Wie jeder weiß, können Sie es nicht auf den Server hochladen ( dies widerspricht direkt den Anforderungen ), aber wir testen es gerade - es ist nicht beängstigend. Ich kenne zwei sinnvolle Möglichkeiten, um diese Funktion zu nutzen:
- Rufen Sie es automatisch in der
AppDelegate.applicationDidEnterBackground(_:)
-Methode auf, damit die Anwendung sofort nach dem Beenden des Springboards beendet wird. - Erstellen Sie eine Komponente in der Benutzeroberfläche (z. B. eine Schaltfläche oder hängen Sie eine Aktion an eine Geste), indem Sie auf die Komponente klicken und
exit(...).
In diesem Fall wird die Anwendung beendet und die Hintergrundarbeit mit dem Netzwerk sollte fortgesetzt werden. Und nach einiger Zeit sollten wir einen Aufruf an application(_:handleEventsForBackgroundURLSession:completionHandler:).
Wie wird die Anwendung protokolliert, wenn Sie die Xcode-Debug-Konsole nicht verwenden können?
Das ist unmöglich. Sie können, wenn Sie wirklich wollen. Sie können nicht von Xcode aus starten. Wenn die Anwendung beispielsweise aufgrund eines Systemereignisses bereits neu gestartet wurde, können Sie sie an die Anwendung anhängen (an den Prozess anhängen) und aus der Warteschlange entfernen. Aber diese Lösung ist so lala, dass Sie den Neustartprozess selbst irgendwie testen müssen.
Sie können Protokolle (Protokolle, Protokolle) verwenden . Es gibt mehrere Optionen für ihre Implementierung:
print.
Es wird oft verwendet, um "etwas schnell rauszubekommen". In unserem Fall ist es unmöglich zu verwenden, da wir keinen Zugriff auf die Konsole auf dem Gerät haben, wird die Anwendung beendet.NSLog.
Es wird funktionieren, da es die dritte Methode verwendet.os_log.
Die korrekteste Methode, mit der Sie die Protokolle ordnungsgemäß konfigurieren, mit dem gewünschten Typ versehen, nach dem Debuggen deaktivieren, ohne den Code selbst zu schneiden usw.
Achtung! Mit os_log
gibt es Probleme (zum Beispiel das Fehlen von Debug-Protokollen), die nur im Simulator, aber nicht auf diesem Gerät abgespielt werden. Verwenden Sie das Gerät.
os_log,
zur Verwendung von os_log,
Sie in der Apple-Dokumentation . Insbesondere sollten Sie debug
und info
Protokolle aktivieren, da diese standardmäßig ausgeblendet sind.
Verfolgen des Fortschritts beim Herunterladen oder Senden von Daten
Während der Datenübertragung möchte ich verstehen, wie viel bereits gesendet wurde und wie viel noch übrig ist. Hierfür gibt es zwei Möglichkeiten. Der erste ist die Verwendung von Delegate-Methoden:
- Zum Senden müssen Sie
urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)
- Es gibt eine ähnliche
urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
Methode zum Herunterladen urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
Diese Methoden werden jedes Mal aufgerufen, wenn das nächste Datenelement heruntergeladen oder gesendet wird. Sie stimmen nicht unbedingt mit den Methoden zum Abschluss des Vorgangs überein, sondern können auch aufgerufen werden, nachdem die Daten vollständig heruntergeladen oder gesendet wurden. Daher kann nicht festgestellt werden, dass „alles fertig ist“.
Die zweite Methode ist interessanter. Fakt ist, dass jede Task ein Objekt vom Typ Progress
(liegt im Feld task.progress
) bereitstellt, mit dem ein beliebiger Prozess einschließlich des Datenübertragungsprozesses überwacht werden kann. Wie ist er interessant? Zwei Dinge:
- Aus den
Progress
können Sie einen Aufgabenausführungsbaum erstellen, dessen Knoten anzeigen, wie fortgeschritten alle darin enthaltenen Aufgaben sind. Wenn Sie beispielsweise fünf Dateien senden müssen, können Sie den Fortschritt für jede Datei erfassen, allgemeine Fortschritte erzielen, fünf weitere hinzufügen und den Fortschritt einer übergeordneten Datei überwachen und die Aktualisierungen mit einem Schnittstellenelement verknüpfen. - Sie können diesem Baum Ihren Fortschritt hinzufügen und die mit dem hinzugefügten Fortschritt verbundenen Aktionen anhalten und abbrechen.
In welcher Beziehung steht dies zum Herunterladen oder Senden von Daten im Hintergrund? Auf keinen Fall. Delegatmethoden werden nicht aufgerufen und Fortschrittsobjekte sterben ab, wenn die Anwendung beendet wird. Für Hintergrundsitzungen ist diese Methode nicht geeignet.
Übertragen Sie Aufgaben von einer regulären Sitzung in eine Hintergrundsitzung
Nun, es ist schwieriger, mit einer Hintergrundsitzung zu arbeiten. Das ist aber praktisch! Es geht keine einzige Aufgabe verloren. Werden wir jemals alle angeforderten Daten erhalten, warum nicht immer die Hintergrundsitzung verwenden?
Leider hat sie Fehler und schwerwiegende. Beispielsweise ist eine Hintergrundsitzung langsamer. In meinen Experimenten variierte die Geschwindigkeit mehrmals. Zweitens kann die Ausführung einer Aufgabe im Hintergrund verzögert werden (insbesondere wenn der Parameter isDiscretionary
ist, was, wie bereits erwähnt, immer für Aufgaben gilt, die erstellt werden, während die Anwendung im Hintergrund ausgeführt wird.
Daher müssen Sie jedes Mal, wenn Sie eine Aufgabe erstellen, genau verstehen, nach welchen Kriterien sie ausgeführt wird, wo sie zu einer regulären Sitzung oder einer Hintergrundsitzung hinzugefügt werden soll. Normal läuft schneller, startet sofort. Hintergrund - länger, nicht sofort, wird aber nicht beendet, wenn der Benutzer die Anwendung schließt.
Wenn es kein offensichtliches Verständnis dafür gibt, dass die Aufgabe in der Hintergrundsitzung ausgeführt werden sollte (z. B. unkritische Übertragung einer sehr großen Datenmenge, z. B. Synchronisierung oder Sicherung), sollten Sie wie folgt vorgehen:
- Starten Sie die Aufgabe in einer regulären Sitzung. Führen Sie in diesem Fall backgroundTask aus, damit das System versteht, dass wir Zeit benötigen, um die Aufgabe abzuschließen. Dies gibt einige Zeit (bis zu mehreren Minuten, aber in iOS 13 ist etwas kaputt gegangen und es ist nicht klar, was damit passiert), damit die Aufgabe abgeschlossen werden kann.
- Wenn es keine Zeit hat, übertragen wir die Aufgabe am Ende von backgroundTask von einer regulären Sitzung in eine Hintergrundsitzung, in der sie weiterarbeitet und endet, wenn dies möglich ist.
Wie überweisen? Auf keinen Fall. Beenden Sie einfach die übliche Aufgabe (brechen Sie sie ab) und erstellen Sie einen ähnlichen Hintergrund (mit derselben Anforderung). Warum nennt man das eine „Überweisung“? Und warum in Anführungszeichen?
Es erfolgt keine Übertragung zum Senden von Daten. Es ist genau das, was beschrieben wird. Sie haben eine Aufgabe erledigt, eine andere gestartet, alle Daten, die zum ersten Mal gesendet wurden, gingen verloren.
Beim Herunterladen ist die Situation anders. Das System weiß, in welche Datei die Anforderung heruntergeladen wird. Wenn Sie beispielsweise mehrere Tasks ausführen, um dieselbe URL herunterzuladen, wird die Anforderung nicht mehrmals ausgeführt. Die Daten werden einmal heruntergeladen. Anschließend wird die endgültige Delegatmethode (oder der Rückruf) mehrmals ausgeführt. Hier wird ein Experiment beschrieben , das dies bestätigt. Höchstwahrscheinlich wird Standard-HTTP-Caching verwendet, genau wie in Browsern.
Hier ist ein Beispielcode, der dies tut:
let request = URLRequest(url: url) let task = foregroundSession.downloadTask(with: request) let backgroundId = UIApplication.shared.beginBackgroundTask { task.cancel() let task = backgroundSession.downloadTask(with: request) task.resume() } task.resume()
Wenn die Aufgabe beendet ist, bevor expirationHandler
funktioniert, müssen Sie daran denken, UIApplication.shared.endBackgroundTask(backgroundId)
. Dies ist in der Dokumentation näher beschrieben.
Damit das System den Download fortsetzt (z. B. kann durch Abbrechen die temporäre Datei gelöscht werden, bevor der Download im Hintergrund fortgesetzt wird), gibt es spezielle Methoden:
let request = URLRequest(url: url) let task = foregroundSession.downloadTask(with: request) let backgroundId = UIApplication.shared.beginBackgroundTask { task.cancel { data in let task: URLSessionDownloadTask if let data = data { task = backgroundSession.downloadTask(withResumeData: data) } else { task = backgroundSession.downloadTask(with: request) } task.resume() } }
Den Rechen, auf den ich getreten bin
Protokolle
Das Schwierigste dabei ist, genau zu verstehen, was passiert. Exzellente Protokollierung ist die erste Aufgabe, die sofort angegangen werden muss. Das Verhalten von Hintergrundsitzungen kann nur mit normalen Protokollen getestet werden.
, , background -, , , ( UI, ). , , — . , — , , os_log.
( NSLog)
-
- , . , - . , , , ( ) . , , -, , . — — , . — , - ( ), , .
. ( ), . , , , .
Einschränkungen
:
- ,
(task.taskIdentifier)
, (Dictionary). , 1, . - ,
URLSession.getAllTasks
. , background . , . , . ¯\_(ツ)_/¯ - , , , , .
, background , . , - . : https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1 . , :
If your app extension initiates a background NSURLSession task, you must also set up a shared container that both the extension and its containing app can access. Use the sharedContainerIdentifier property of the NSURLSessionConfiguration class to specify an identifier for the shared container so that you can access it later.