wake-up-neo.net

iOS Herunterladen und Speichern von Bildern in der App

Kann ich ein Bild von der Website herunterladen und dauerhaft in meiner App speichern? Ich habe wirklich keine Ahnung, aber es würde eine schöne Funktion für meine App ergeben.

76
Samuli Lehtonen
51
Jhaliya

Obwohl es wahr ist, dass die anderen Antworten hier funktionieren werden, es sind wirklich keine Lösungen, die jemals im Produktionscode verwendet werden sollten. (Zumindest nicht ohne Modifikation)

Probleme

Das Problem bei diesen Antworten ist, dass sie, wenn sie so implementiert werden, wie sie sind, und nicht von einem Hintergrundthread aufgerufen werden, den Hauptthread blockieren, während sie das Bild herunterladen und speichern. Das ist schlecht.

Wenn der Hauptthread blockiert ist, werden Benutzeroberflächenaktualisierungen erst durchgeführt, wenn das Herunterladen/Speichern des Bildes abgeschlossen ist. Nehmen wir als Beispiel an, Sie fügen Ihrer App eine UIActivityIndicatorView hinzu, um dem Benutzer zu zeigen, dass der Download noch nicht abgeschlossen ist (ich werde dies in dieser Antwort als Beispiel verwenden).

  1. Das Objekt, das für den Start des Downloads verantwortlich ist, wird geladen.
  2. Weisen Sie die Aktivitätsanzeige an, mit der Animation zu beginnen.
  3. Starten Sie den synchronen Download-Vorgang mit +[NSData dataWithContentsOfURL:]
  4. Speichern Sie die gerade heruntergeladenen Daten (Bild).
  5. Weisen Sie die Aktivitätsanzeige an, die Animation zu beenden.

Das mag nach einem vernünftigen Kontrollfluss aussehen, verschleiert aber ein kritisches Problem.

Wenn Sie die startAnimating-Methode des Aktivitätsindikators im Hauptthread (UI) aufrufen, werden die UI-Aktualisierungen für dieses Ereignis erst dann ausgeführt, wenn die Hauptlaufschleife das nächste Mal aktualisiert wird Hier liegt das erste große Problem.

Bevor dieses Update durchgeführt werden kann, wird der Download ausgelöst. Da es sich um einen synchronen Vorgang handelt, wird der Hauptthread blockiert, bis der Download abgeschlossen ist (beim Speichern tritt dasselbe Problem auf). Dadurch wird tatsächlich verhindert, dass die Aktivitätsanzeige ihre Animation startet. Danach rufen Sie die stopAnimating-Methode des Aktivitätsindikators auf und erwarten, dass alles gut ist, aber dies ist nicht der Fall.

An diesem Punkt werden Sie sich wahrscheinlich fragen:.

Warum wird meine Aktivitätsanzeige nie angezeigt?

Nun, denk mal so darüber nach. Sie teilen dem Indikator mit, dass er gestartet werden soll, aber er bekommt keine Chance, bevor der Download beginnt. Nachdem der Download abgeschlossen ist, weisen Sie die Anzeige an, die Animation zu beenden. Da der Hauptthread während des gesamten Vorgangs blockiert war, weist das tatsächlich angezeigte Verhalten eher den Indikator an, zu starten und ihn dann sofort anzuhalten, obwohl dazwischen eine (möglicherweise) große Download-Aufgabe stattgefunden hat.

Nun, im Best-Case-Szenario ist alles, was dies bewirkt, eine schlechte Benutzererfahrung (immer noch sehr schlecht). Selbst wenn Sie der Meinung sind, dass dies keine große Sache ist, weil Sie nur ein kleines Bild herunterladen und der Download fast augenblicklich erfolgt, ist dies nicht immer der Fall. Einige Ihrer Benutzer haben möglicherweise eine langsame Internetverbindung oder auf der Serverseite ist ein Fehler aufgetreten, der verhindert, dass der Download sofort oder überhaupt startet.

In beiden Fällen ist die App nicht in der Lage, Benutzeroberflächenaktualisierungen zu verarbeiten oder Ereignisse zu berühren, während Ihre Download-Aufgabe darauf wartet, dass der Download abgeschlossen wird oder der Server auf seine Anforderung reagiert.

Dies bedeutet, dass Sie durch synchrones Herunterladen vom Haupt-Thread möglicherweise nichts implementieren können, was dem Benutzer anzeigt, dass gerade ein Download ausgeführt wird. Und da Berührungsereignisse auch auf dem Haupt-Thread verarbeitet werden, können auch beliebige Abbrechen-Schaltflächen hinzugefügt werden.

Dann erhalten Sie im Worst-Case-Szenario die folgenden Absturzberichte.

Ausnahmetyp: 00000020 Ausnahmecodes: 0x8badf00d

Diese sind leicht durch den Ausnahmecode 0x8badf00d zu identifizieren, der als "schlechtes Essen gegessen" bezeichnet werden kann. Diese Ausnahme wird vom Watchdog-Timer ausgelöst, dessen Aufgabe es ist, nach lang laufenden Aufgaben zu suchen, die den Hauptthread blockieren, und die anstößige App zu beenden, wenn dies zu lange dauert. Vermutlich ist dies immer noch ein schlechtes Benutzererlebnis, aber wenn dies eintritt, hat die App die Grenze zwischen schlechtem Benutzererlebnis und schrecklichem Benutzererlebnis überschritten.

Im Folgenden finden Sie weitere Informationen dazu, was dazu führen kann, dass dies in den technischen Fragen und Antworten von Apple zum synchronen Netzwerk (der Kürze halber gekürzt) geschieht.

Die häufigste Ursache für Watchdog-Timeout-Abstürze in einer Netzwerkanwendung ist die synchrone Vernetzung im Hauptthread. Hierzu tragen vier Faktoren bei:

  1. synchrones Netzwerk - Hier stellen Sie eine Netzwerkanforderung und blockieren das Warten auf die Antwort.
  2. haupt-Thread - Synchrone Netzwerke sind im Allgemeinen weniger als ideal, verursachen jedoch spezifische Probleme, wenn Sie sie im Haupt-Thread ausführen. Denken Sie daran, dass der Haupt-Thread für die Ausführung der Benutzeroberfläche verantwortlich ist. Wenn Sie den Haupt-Thread für längere Zeit blockieren, reagiert die Benutzeroberfläche nicht mehr inakzeptabel.
  3. lange Zeitüberschreitungen - Wenn das Netzwerk gerade nicht erreichbar ist (z. B. wenn sich der Benutzer in einem Zug befindet, der in einen Tunnel fährt), schlägt eine ausstehende Netzwerkanforderung erst nach Ablauf einer Zeitüberschreitung fehl.

...

  1. watchdog - Um die Benutzeroberfläche ansprechend zu halten, enthält iOS einen Watchdog-Mechanismus. Wenn Ihre Anwendung nicht rechtzeitig auf bestimmte Ereignisse der Benutzeroberfläche (Starten, Anhalten, Fortsetzen, Beenden) reagiert, bricht der Watchdog Ihre Anwendung ab und generiert einen Watchdog-Timeout-Absturzbericht. Die Zeit, die der Watchdog Ihnen zur Verfügung stellt, ist nicht offiziell dokumentiert, aber es ist immer weniger als ein Netzwerk-Timeout.

Ein heikler Aspekt dieses Problems ist, dass es stark von der Netzwerkumgebung abhängt. Wenn Sie Ihre Anwendung immer in Ihrem Büro testen, in dem die Netzwerkkonnektivität gut ist, wird dieser Absturztyp nie auftreten. Sobald Sie jedoch mit der Bereitstellung Ihrer Anwendung für Endbenutzer beginnen, die sie in allen möglichen Netzwerkumgebungen ausführen, kommt es häufig zu solchen Abstürzen.

An dieser Stelle höre ich auf, darüber nachzudenken, warum die angegebenen Antworten problematisch sein könnten, und biete einige alternative Lösungen an. Beachten Sie, dass ich in diesen Beispielen die URL eines kleinen Bildes verwendet habe und Sie einen größeren Unterschied feststellen werden, wenn Sie ein Bild mit höherer Auflösung verwenden.


Lösungen

Zu Beginn zeige ich eine sichere Version der anderen Antworten sowie Informationen zum Umgang mit Benutzeroberflächenaktualisierungen. Dies ist das erste von mehreren Beispielen, bei denen alle davon ausgegangen werden, dass die Klasse, in der sie implementiert sind, gültige Eigenschaften für eine UIImageView, eine UIActivityIndicatorView sowie die Methode documentsDirectoryURL für den Zugriff auf das Dokumentenverzeichnis aufweist. Im Produktionscode möchten Sie möglicherweise Ihre eigene Methode implementieren, um auf das Dokumentenverzeichnis als Kategorie in NSURL zuzugreifen, um die Wiederverwendbarkeit des Codes zu verbessern. In diesen Beispielen ist dies jedoch in Ordnung.

- (NSURL *)documentsDirectoryURL
{
    NSError *error = nil;
    NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
                                                        inDomain:NSUserDomainMask
                                               appropriateForURL:nil
                                                          create:NO
                                                           error:&error];
    if (error) {
        // Figure out what went wrong and handle the error.
    }

    return url;
}

In diesen Beispielen wird auch davon ausgegangen, dass der Thread, mit dem sie beginnen, der Hauptthread ist. Dies ist wahrscheinlich das Standardverhalten, es sei denn, Sie starten Ihre Download-Aufgabe von einem Ort aus, wie dem Rückrufblock einer anderen asynchronen Aufgabe. Wenn Sie den Download an einem typischen Ort wie einer Lebenszyklusmethode eines Ansichtscontrollers (z. B. viewDidLoad, viewWillAppear: usw.) starten, führt dies zu dem erwarteten Verhalten.

In diesem ersten Beispiel wird die +[NSData dataWithContentsOfURL:] -Methode verwendet, jedoch mit einigen wesentlichen Unterschieden. Zum einen werden Sie feststellen, dass in diesem Beispiel der erste Aufruf, den wir ausführen, darin besteht, die Aktivitätsanzeige anzuweisen, mit der Animation zu beginnen. Dann gibt es einen unmittelbaren Unterschied zwischen diesem und den synchronen Beispielen. Sofort verwenden wir dispatch_async (), um die Ausführung in den Hintergrund-Thread zu verschieben.

Zu diesem Zeitpunkt haben Sie Ihre Download-Aufgabe bereits erheblich verbessert. Da jetzt alles innerhalb des dispatch_async () -Blocks außerhalb des Hauptthreads geschieht, wird Ihre Benutzeroberfläche nicht mehr blockiert und Ihre App kann auf Berührungsereignisse reagieren.

Was hier zu beachten ist, ist, dass der gesamte Code in diesem Block im Hintergrundthread ausgeführt wird, bis zu dem Punkt, an dem das Herunterladen/Speichern des Bildes erfolgreich war. An diesem Punkt möchten Sie die Aktivitätsanzeige möglicherweise anweisen, stopAnimating zu beenden oder wenden Sie das neu gespeicherte Bild auf eine UIImageView an. In beiden Fällen handelt es sich um Aktualisierungen der Benutzeroberfläche, dh Sie müssen den Hauptthread mit dispatch_get_main_queue () zurücksenden, um sie auszuführen. Andernfalls tritt ein undefiniertes Verhalten auf, das dazu führen kann, dass die Benutzeroberfläche nach einem unerwarteten Zeitraum aktualisiert wird oder sogar einen Absturz verursacht. Stellen Sie immer sicher, dass Sie zum Haupt-Thread zurückkehren, bevor Sie Benutzeroberflächenaktualisierungen durchführen.

// Start the activity indicator before moving off the main thread
[self.activityIndicator startAnimating];
// Move off the main thread to start our blocking tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Create the image URL from a known string.
    NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];

    NSError *downloadError = nil;
    // Create an NSData object from the contents of the given URL.
    NSData *imageData = [NSData dataWithContentsOfURL:imageURL
                                              options:kNilOptions
                                                error:&downloadError];
    // ALWAYS utilize the error parameter!
    if (downloadError) {
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
            NSLog(@"%@",[downloadError localizedDescription]);
        });
    } else {
        // Get the path of the application's documents directory.
        NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
        // Append the desired file name to the documents directory path.
        NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"GCD.png"];

        NSError *saveError = nil;
        BOOL writeWasSuccessful = [imageData writeToURL:saveLocation
                                                options:kNilOptions
                                                  error:&saveError];
        // Successful or not we need to stop the activity indicator, so switch back the the main thread.
        dispatch_async(dispatch_get_main_queue(), ^{
            // Now that we're back on the main thread, you can make changes to the UI.
            // This is where you might display the saved image in some image view, or
            // stop the activity indicator.

            // Check if saving the file was successful, once again, utilizing the error parameter.
            if (writeWasSuccessful) {
                // Get the saved image data from the file.
                NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                // Set the imageView's image to the image we just saved.
                self.imageView.image = [UIImage imageWithData:imageData];
            } else {
                NSLog(@"%@",[saveError localizedDescription]);
                // Something went wrong saving the file. Figure out what went wrong and handle the error.
            }

            [self.activityIndicator stopAnimating];
        });
    }
});

Bedenken Sie nun, dass die oben gezeigte Methode immer noch keine ideale Lösung ist ​​da sie nicht vorzeitig abgebrochen werden kann, gibt sie keinen Hinweis auf den Fortschritt des Downloads und kann keine verarbeiten Art der Authentifizierung Herausforderung, kann es nicht ein bestimmtes Zeitlimit usw. gegeben werden (viele, viele Gründe). Im Folgenden werden einige der besseren Optionen beschrieben.

In diesen Beispielen werde ich nur Lösungen für Apps behandeln, die auf iOS 7 und höher abzielen, da iOS 8 (zum Zeitpunkt des Schreibens) die aktuelle Hauptversion ist. Apple empfiehlt, nur die Versionen N und N zu unterstützen -1 . Wenn Sie ältere iOS-Versionen unterstützen müssen, sollten Sie sich die NSURLConnection -Klasse sowie die 1.0-Version von AFNetworking ansehen. Wenn Sie sich den Versionsverlauf dieser Antwort ansehen, finden Sie grundlegende Beispiele unter Verwendung von NSURLConnection und ASIHTTPRequest . Es ist jedoch zu beachten, dass ASIHTTPRequest nicht mehr verwaltet wird. nicht ​​für neue Projekte verwendet werden.


NSURLSession

Beginnen wir mit NSURLSession , das in iOS 7 eingeführt wurde, und das die Vernetzung in iOS erheblich vereinfacht. Mit NSURLSession können Sie problemlos asynchrone HTTP-Anforderungen mit einem Rückrufblock ausführen und Authentifizierungsprobleme mit seinem Stellvertreter lösen. Das Besondere an dieser Klasse ist jedoch, dass Download-Aufgaben weiterhin ausgeführt werden können, selbst wenn die Anwendung in den Hintergrund gestellt wird, beendet wird oder sogar abstürzt. Hier ist ein grundlegendes Beispiel für die Verwendung.

// Start the activity indicator before starting the download task.
[self.activityIndicator startAnimating];

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use a session with a custom configuration
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create the download task passing in the URL of the image.
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    // Get information about the response if neccessary.
    if (error) {
        NSLog(@"%@",[error localizedDescription]);
        // Something went wrong downloading the image. Figure out what went wrong and handle the error.
        // Don't forget to return to the main thread if you plan on doing UI updates here as well.
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.activityIndicator stopAnimating];
        });
    } else {
        NSError *openDataError = nil;
        NSData *downloadedData = [NSData dataWithContentsOfURL:location
                                                       options:kNilOptions
                                                         error:&openDataError];
        if (openDataError) {
            // Something went wrong opening the downloaded data. Figure out what went wrong and handle the error.
            // Don't forget to return to the main thread if you plan on doing UI updates here as well.
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"%@",[openDataError localizedDescription]);
                [self.activityIndicator stopAnimating];
            });
        } else {
            // Get the path of the application's documents directory.
            NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
            // Append the desired file name to the documents directory path.
            NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"NSURLSession.png"];
            NSError *saveError = nil;

            BOOL writeWasSuccessful = [downloadedData writeToURL:saveLocation
                                                          options:kNilOptions
                                                            error:&saveError];
            // Successful or not we need to stop the activity indicator, so switch back the the main thread.
            dispatch_async(dispatch_get_main_queue(), ^{
                // Now that we're back on the main thread, you can make changes to the UI.
                // This is where you might display the saved image in some image view, or
                // stop the activity indicator.

                // Check if saving the file was successful, once again, utilizing the error parameter.
                if (writeWasSuccessful) {
                    // Get the saved image data from the file.
                    NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
                    // Set the imageView's image to the image we just saved.
                    self.imageView.image = [UIImage imageWithData:imageData];
                } else {
                    NSLog(@"%@",[saveError localizedDescription]);
                    // Something went wrong saving the file. Figure out what went wrong and handle the error.
                }

                [self.activityIndicator stopAnimating];
            });
        }
    }
}];

// Tell the download task to resume (start).
[task resume];

Daran erkennen Sie, dass die downloadTaskWithURL: completionHandler: -Methode eine Instanz von NSURLSessionDownloadTask zurückgibt, für die eine Instanzmethode -[NSURLSessionTask resume] aufgerufen wird. Dies ist die Methode, mit der der Download-Task tatsächlich gestartet werden soll. Dies bedeutet, dass Sie Ihre Download-Aufgabe starten und bei Bedarf den Startvorgang unterbrechen können. Dies bedeutet auch, dass Sie, solange Sie einen Verweis auf die Aufgabe speichern, die Methoden cancel und suspend verwenden können, um die Aufgabe bei Bedarf abzubrechen oder anzuhalten.

Was an NSURLSessionTasks wirklich cool ist, ist, dass Sie mit ein wenig KVO die Werte der Eigenschaften countOfBytesExpectedToReceive und countOfBytesReceived überwachen und diese Werte einem NSByteCountFormatter , und erstellen Sie einfach eine Download-Fortschrittsanzeige für Ihren Benutzer mit von Menschen lesbaren Einheiten (z. B. 42 KB von 100 KB).

Bevor ich mich jedoch von NSURLSession entferne, möchte ich darauf hinweisen, dass die Hässlichkeit vermieden werden kann, an mehreren Stellen im Rückrufblock des Downloads_async an die Hauptthreads zurücksenden zu müssen. Wenn Sie sich für diese Route entschieden haben, können Sie die Sitzung mit ihrem Initialisierer initialisieren, mit dem Sie den Delegaten sowie die Delegatenwarteschlange angeben können. Hierfür müssen Sie das Delegate-Muster anstelle der Rückrufblöcke verwenden. Dies kann jedoch von Vorteil sein, da nur so Hintergrund-Downloads unterstützt werden.

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                      delegate:self
                                                 delegateQueue:[NSOperationQueue mainQueue]];

AFNetworking 2.0

Wenn Sie noch nie von AFNetworking gehört haben, ist es meiner Meinung nach das Ende aller Netzwerkbibliotheken. Es wurde für Objective-C erstellt, funktioniert aber auch in Swift. In den Worten seines Autors:

AFNetworking ist eine wunderbare Netzwerkbibliothek für iOS und Mac OS X. Sie basiert auf dem Foundation URL Loading System und erweitert die leistungsstarken, in Cocoa integrierten Netzwerkabstraktionen auf hoher Ebene. Es verfügt über eine modulare Architektur mit gut gestalteten, funktionsreichen APIs, deren Verwendung Spaß macht.

AFNetworking 2.0 unterstützt iOS 6 und höher. In diesem Beispiel verwende ich jedoch die AFHTTPSessionManager-Klasse, für die iOS 7 und höher erforderlich ist, da alle neuen APIs der NSURLSession-Klasse verwendet werden. Dies wird deutlich, wenn Sie das folgende Beispiel lesen, das viel Code mit dem obigen NSURLSession-Beispiel gemeinsam hat.

Es gibt jedoch ein paar Unterschiede, auf die ich hinweisen möchte. Anstatt Ihre eigene NSURLSession zu erstellen, erstellen Sie zunächst eine Instanz von AFURLSessionManager, die eine NSURLSession intern verwaltet. Auf diese Weise können Sie einige seiner praktischen Methoden wie -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:] nutzen. Das Interessante an dieser Methode ist, dass Sie eine Download-Aufgabe mit einem bestimmten Zieldateipfad, einem Abschlussblock und einer Eingabe für einen NSProgress -Zeiger ziemlich genau erstellen können Sie können Informationen über den Fortschritt des Downloads beobachten. Hier ist ein Beispiel.

// Use the default session configuration for the manager (background downloads must use the delegate APIs)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use AFNetworking's NSURLSessionManager to manage a NSURLSession.
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];

// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:@"http://www.google.com/images/srpr/logo3w.png"];
// Create a request object for the given URL.
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
// Create a pointer for a NSProgress object to be used to determining download progress.
NSProgress *progress = nil;

// Create the callback block responsible for determining the location to save the downloaded file to.
NSURL *(^destinationBlock)(NSURL *targetPath, NSURLResponse *response) = ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    // Get the path of the application's documents directory.
    NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
    NSURL *saveLocation = nil;

    // Check if the response contains a suggested file name
    if (response.suggestedFilename) {
        // Append the suggested file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
    } else {
        // Append the desired file name to the documents directory path.
        saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:@"AFNetworking.png"];
    }

    return saveLocation;
};

// Create the completion block that will be called when the image is done downloading/saving.
void (^completionBlock)(NSURLResponse *response, NSURL *filePath, NSError *error) = ^void (NSURLResponse *response, NSURL *filePath, NSError *error) {
    dispatch_async(dispatch_get_main_queue(), ^{
        // There is no longer any reason to observe progress, the download has finished or cancelled.
        [progress removeObserver:self
                      forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];

        if (error) {
            NSLog(@"%@",error.localizedDescription);
            // Something went wrong downloading or saving the file. Figure out what went wrong and handle the error.
        } else {
            // Get the data for the image we just saved.
            NSData *imageData = [NSData dataWithContentsOfURL:filePath];
            // Get a UIImage object from the image data.
            self.imageView.image = [UIImage imageWithData:imageData];
        }
    });
};

// Create the download task for the image.
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request
                                                         progress:&progress
                                                      destination:destinationBlock
                                                completionHandler:completionBlock];
// Start the download task.
[task resume];

// Begin observing changes to the download task's progress to display to the user.
[progress addObserver:self
           forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
              options:NSKeyValueObservingOptionNew
              context:NULL];

Da wir die Klasse, die diesen Code enthält, als Beobachter zu einer der Eigenschaften der NSProgress-Instanz hinzugefügt haben, müssen Sie natürlich die -[NSObject observeValueForKeyPath:ofObject:change:context:] -Methode implementieren. In diesem Fall habe ich ein Beispiel angegeben, wie Sie eine Fortschrittsanzeige aktualisieren können, um den Fortschritt des Downloads anzuzeigen. Das ist ganz einfach. NSProgress verfügt über eine Instanzmethode localizedDescription, die Fortschrittsinformationen in einem lokalisierten, für Menschen lesbaren Format anzeigt.

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    // We only care about updates to fractionCompleted
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))]) {
        NSProgress *progress = (NSProgress *)object;
        // localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
        self.progressLabel.text = progress.localizedDescription;
    } else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

Vergessen Sie nicht, wenn Sie AFNetworking in Ihrem Projekt verwenden möchten, müssen Sie die Installationsanweisungen befolgen und sicherstellen, dass #import <AFNetworking/AFNetworking.h> eingestellt ist.

Alamofire

Zum Schluss möchte ich ein letztes Beispiel mit Alamofire geben. Dies ist eine Bibliothek, die das Networking in Swift zum Kinderspiel macht. Ich habe keine Lust mehr, mich ausführlich mit dem Inhalt dieses Beispiels zu befassen, aber es funktioniert fast genauso wie in den letzten Beispielen, nur auf eine wohl schönere Art und Weise.

// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
    var error: NSError?
    // Get the documents directory
    let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
        inDomain: .UserDomainMask,
        appropriateForURL: nil,
        create: false,
        error: &error
    )

    if let error = error {
        // This could be bad. Make sure you have a backup plan for where to save the image.
        println("\(error.localizedDescription)")
    }

    // Return a destination of .../Documents/Alamofire.png
    return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}

Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
    .validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
    .validate(contentType: ["image/png"]) // Require the content type to be image/png.
    .progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
        // Create an NSProgress object to represent the progress of the download for the user.
        let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
        progress.completedUnitCount = totalBytesRead

        dispatch_async(dispatch_get_main_queue()) {
            // Move back to the main thread and update some progress label to show the user the download is in progress.
            self.progressLabel.text = progress.localizedDescription
        }
    }
    .response { (request, response, _, error) in
        if error != nil {
            // Something went wrong. Handle the error.
        } else {
            // Open the newly saved image data.
            if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
                dispatch_async(dispatch_get_main_queue()) {
                    // Move back to the main thread and add the image to your image view.
                    self.imageView.image = UIImage(data: imageData)
                }
            }
        }
    }
92
Mick MacCallum

Sie können nichts innerhalb des App-Bundles speichern. Sie können jedoch +[NSData dataWithContentsOfURL:] verwenden, um das Bild im Dokumentenverzeichnis Ihrer App zu speichern, z.

NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];

Nicht genau permanent , aber es bleibt mindestens so lange bestehen, bis der Benutzer die App löscht.

38
user142019

Das ist das Hauptkonzept. Habe Spaß ;)

NSURL *url = [NSURL URLWithString:@"http://example.com/yourImage.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingString:@"/yourLocalImage.png"];
[data writeToFile:path atomically:YES];
13
cem

Da wir uns jetzt auf IO5 befinden, müssen Sie nicht mehr notwendigerweise Images auf die Festplatte schreiben.
Sie können jetzt "Erlauben von externem Speicher" für ein Coredata-Binärattribut festlegen.

Kleine Datenwerte wie Bildminiaturen können effizient in einem .__ gespeichert werden. Datenbank, aber große Fotos oder andere Medien werden am besten direkt von .__ bearbeitet. das Dateisystem. Sie können jetzt den Wert eines verwalteten .__ angeben. Objektattribut kann als externer Datensatz gespeichert werden - siehe setAllowsExternalBinaryDataStorage: Wenn diese Option aktiviert ist, entscheidet heuristisch für Core Data, ob Es sollte die Daten direkt in der Datenbank speichern oder einen URI in einer .__ speichern. separate Datei, die für Sie verwaltet wird. Sie können nicht basierend auf der .__-Abfrage abfragen. Inhalt einer binären Dateneigenschaft, wenn Sie diese Option verwenden.

7
Alexander

Wie andere Leute sagten, gibt es viele Fälle, in denen Sie ein Bild im Hintergrund-Thread herunterladen sollten, ohne die Benutzeroberfläche zu blockieren

In diesen Fällen ist es meine Lieblingslösung, eine praktische Methode mit Blöcken wie dieser zu verwenden: (credit -> iOS: So laden Sie Bilder asynchron herunter (und machen Sie Ihre UITableView Scroll Fast) )

- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                               if ( !error )
                               {
                                   UIImage *image = [[UIImage alloc] initWithData:data];
                                   completionBlock(YES,image);
                               } else{
                                   completionBlock(NO,nil);
                               }
                           }];
}

Und nennen Sie es gerne

NSURL *imageUrl = //...

[[MyUtilManager sharedInstance] downloadImageWithURL:[NSURL URLWithString:imageURL] completionBlock:^(BOOL succeeded, UIImage *image) {
    //Here you can save the image permanently, update UI and do what you want...
}];
3
andreacipriani

So lade ich ein Werbebanner herunter. Am besten machen Sie das im Hintergrund, wenn Sie ein großes Bild oder eine Reihe von Bildern herunterladen.

- (void)viewDidLoad {
    [super viewDidLoad];

    [self performSelectorInBackground:@selector(loadImageIntoMemory) withObject:nil];

}
- (void)loadImageIntoMemory {
    NSString *temp_Image_String = [[NSString alloc] initWithFormat:@"http://yourwebsite.com/MyImageName.jpg"];
    NSURL *url_For_Ad_Image = [[NSURL alloc] initWithString:temp_Image_String];
    NSData *data_For_Ad_Image = [[NSData alloc] initWithContentsOfURL:url_For_Ad_Image];
    UIImage *temp_Ad_Image = [[UIImage alloc] initWithData:data_For_Ad_Image];
    [self saveImage:temp_Ad_Image];
    UIImageView *imageViewForAdImages = [[UIImageView alloc] init];
    imageViewForAdImages.frame = CGRectMake(0, 0, 320, 50);
    imageViewForAdImages.image = [self loadImage];
    [self.view addSubview:imageViewForAdImages];
}
- (void)saveImage: (UIImage*)image {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent: @"MyImageName.jpg" ];
    NSData* data = UIImagePNGRepresentation(image);
    [data writeToFile:path atomically:YES];
}
- (UIImage*)loadImage {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* path = [documentsDirectory stringByAppendingPathComponent:@"MyImageName.jpg" ];
    UIImage* image = [UIImage imageWithContentsOfFile:path];
    return image;
}
1
Bobby

Hier ist der Code, mit dem Sie ein Bild asynchron von der URL herunterladen und an der gewünschten Stelle in object-c speichern können: ->

    + (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
        {
            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
            [NSURLConnection sendAsynchronousRequest:request
                                               queue:[NSOperationQueue mainQueue]
                                   completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                                       if ( !error )
                                       {
                                           UIImage *image = [[UIImage alloc] initWithData:data];
                                           completionBlock(YES,image);
                                       } else{
                                           completionBlock(NO,nil);
                                       }
                                   }];
        }
1
Mohd. Asif

Sie können ein Bild herunterladen, ohne die Benutzeroberfläche mit NSURLSessionDataTask zu blockieren.

+(void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
 {
 NSURLSessionDataTask*  _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (error != nil)
        {
          if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
                {
                    completionBlock(NO,nil);
                }
         }
    else
     {
      [[NSOperationQueue mainQueue] addOperationWithBlock: ^{
                        dispatch_async(dispatch_get_main_queue(), ^{
                        UIImage *image = [[UIImage alloc] initWithData:data];
                        completionBlock(YES,image);

                        });

      }];

     }

                                            }];
    [_sessionTask resume];
}
0
Mohd. Asif

Wenn Sie die AFNetworking-Bibliothek zum Herunterladen von Bildern verwenden und diese Bilder in UITableview verwendet werden, können Sie den folgenden Code in cellForRowAtIndexPath verwenden

 [self setImageWithURL:user.user_ProfilePicturePath toControl:cell.imgView]; 
 
-(void)setImageWithURL:(NSURL*)url toControl:(id)ctrl
{
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        if (image) {
            if([ctrl isKindOfClass:[UIButton class]])
            {
                UIButton btn =(UIButton)ctrl;
                [btn setBackgroundImage:image forState:UIControlStateNormal];
            }
            else
            {
                UIImageView imgView = (UIImageView)ctrl;
                imgView.image = image;
            }

} } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"No Image"); }]; [operation start];}
0
ASHISHT

Hier ist eine Swift 5 Lösung zum Herunterladen und Speichern eines Bildes oder allgemein einer Datei in das Dokumentenverzeichnis mit Alamofire:

func dowloadAndSaveFile(from url: URL) {
    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        documentsURL.appendPathComponent(url.lastPathComponent)
        return (documentsURL, [.removePreviousFile])
    }
    let request = SessionManager.default.download(url, method: .get, to: destination)
    request.validate().responseData { response in
        switch response.result {
        case .success:
            if let destinationURL = response.destinationURL {
                print(destinationURL)
            }
        case .failure(let error):
            print(error.localizedDescription)
        }
    }
}
0
tanaschita