iOS下载并在应用程序内保存图像


Answers:



92

尽管这里的其他答案确实是可行的,但它们实际上并不是生产代码中应使用的解决方案。(至少不是没有修改)

问题

这些答案的问题在于,如果按原样实现并且未从后台线程调用它们,则它们将在下载和保存图像时阻塞主线程。这不好

如果主线程被阻止,则在图像的下载/保存完成之前,不会进行UI更新。举例来说,假设您向应用程序添加了一个UIActivityIndi​​catorView,以向用户显示下载仍在进行中(我将在整个答案中以该示例为例),并具有以下大致控制流程:

  1. 已加载负责开始下载的对象。
  2. 告诉活动指示器开始动画。
  3. 使用开始启动同步下载过程 +[NSData dataWithContentsOfURL:]
  4. 保存刚刚下载的数据(图像)。
  5. 告诉活动指示器停止动画。

现在,这似乎是合理的控制流程,但却掩盖了一个关键问题。

当您在主(UI)线程上调用活动指示器的startAnimating方法时,此事件的UI更新实际上不会发生,直到下一次主运行循环更新为止,这是第一个主要问题所在。

在此更新发生之前,将触发下载,并且由于这是同步操作,因此它将阻塞主线程,直到完成下载为止(保存也有同样的问题)。这实际上将阻止活动指示器开始其动画。之后,您调用活动指示器的stopAnimating方法并期望一切都很好,但事实并非如此。

此时,您可能会发现自己想知道以下内容。

为什么我的活动指示器没有显示?

好吧,这样想。您告诉指示器启动,但是在开始下载之前没有机会。下载完成后,您告诉指示器停止动画制作。由于主线程在整个操作过程中均被阻塞,因此您实际上看到的行为更多是指示指示器启动,然后立即指示其停止,即使它们之间(可能有)大型下载任务也是如此。

现在,在最佳情况下,所有这一切都会导致不良的用户体验(仍然非常糟糕)。即使您认为这没什么大不了,因为您只下载一个小图像,并且下载几乎是瞬间完成的,但情况并非总是如此。您的某些用户的互联网连接速度可能很慢,或者服务器端出现了某些问题,导致下载无法立即开始或完全停止。

在这两种情况下,当您的下载任务停滞不前时,应用程序将无法处理UI更新,甚至无法触摸事件,等待下载完成或服务器响应其请求。

这意味着从主线程同步下载使您无法执行任何操作来向用户指示当前正在进行下载。并且由于触摸事件也在主线程上处理,因此排除了添加任何种类的取消按钮的可能性。

然后,在最坏的情况下,您将开始接收崩溃报告,说明以下内容。

异常类型:00000020异常代码:0x8badf00d

这些可以通过异常代码轻松识别0x8badf00d,可以将其理解为“吃不好的食物”。看门狗计时器抛出此异常,该计时器的工作是监视长时间运行的任务,该任务阻塞了主线程,并且如果持续时间太长,则杀死有问题的应用程序。可以说,这仍然是一个糟糕的用户体验问题,但是如果这种情况开始发生,则表明该应用已经跨越了糟糕的用户体验和糟糕的用户体验之间的界限。

这是有关Apple的有关同步网络的技术问答的简短信息(为简洁起见)。

网络应用程序中看门狗超时崩溃的最常见原因是主线程上的同步网络。这里有四个促成因素:

  1. 同步网络-在这里您可以发出网络请求并阻止等待响应。
  2. 主线程—同步联网通常不理想,但是如果在主线程上执行同步网络,则会导致特定的问题。请记住,主线程负责运行用户界面。如果您在较长时间内阻塞了主线程,则用户界面将无法响应,这是令人无法接受的。
  3. 长超时-如果网络刚刚消失(例如,用户在火车上并进入隧道),则直到某个超时到期之前,任何待处理的网络请求都不会失败。

...

  1. 看门狗—为了保持用户界面的响应速度,iOS包括看门狗机制。如果您的应用程序未能及时响应某些用户界面事件(启动,挂起,恢复,终止),则监视程序将终止您的应用程序并生成监视程序超时崩溃报告。看门狗给您的时间没有正式记录,但始终少于网络超时。

这个问题的一个棘手的方面是它高度依赖于网络环境。如果您总是在网络连接良好的办公室中测试应用程序,那么您将永远不会看到这种崩溃。但是,一旦开始将应用程序部署到最终用户(他们将在各种网络环境中运行最终用户),这种崩溃就将变得很普遍。

现在,在这一点上,我将不再讨论为什么所提供的答案可能有问题,并将开始提供一些替代解决方案。请记住,我在这些示例中使用的是小图片的网址,使用高分辨率图片时,您会注意到其中的较大差异。


解决方案

我将首先显示其他答案的安全版本,以及如何处理UI更新的内容。这将是几个示例中的第一个,所有这些示例均假定实现它们的类具有UIImageView,UIActivityIndi​​catorView以及documentsDirectoryURL访问文档目录的方法的有效属性。在生产代码中,您可能希望实现自己的方法以将文档目录作为NSURL上的类别进行访问,以实现更好的代码可重用性,但是对于这些示例,这将很好。

- (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;
}

这些示例还将假定它们开始的线程是主线程。除非您从某个其他异步任务的回调块之类的地方启动下载任务,否则这可能是默认行为。如果在典型的地方开始下载,例如视图控制器的生命周期方法(即viewDidLoad,viewWillAppear:等),这将产生预期的行为。

第一个示例将使用该+[NSData dataWithContentsOfURL:]方法,但有一些关键区别。首先,您会注意到,在此示例中,我们进行的第一个调用是告诉活动指示器开始进行动画处理,然后此示例与同步示例之间立即存在差异。立即,我们使用dispatch_async(),传入全局并发队列以将执行移至后台线程。

至此,您已经大大改善了下载任务。由于dispatch_async()块中的所有内容现在都将发生在主线程之外,因此您的界面将不再锁定,并且您的应用将可以自由地响应触摸事件。

这里要注意的重要一点是,此块中的所有代码都将在后台线程上执行,直到成功下载/保存图像为止,此时您可能要告诉活动指示器stopAnimating ,或将新保存的图像应用于UIImageView。无论哪种方式,这些都是对UI的更新,这意味着您必须使用dispatch_get_main_queue()调度回主线程才能执行它们。否则,将导致未定义的行为,这可能导致UI在意外的时间段后更新,甚至可能导致崩溃。在执行UI更新之前,请始终确保移回主线程。

// 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];
        });
    }
});

现在请记住,考虑到无法过早取消上述方法,它仍然不是理想的解决方案,它无法指示下载进度,无法处理任何身份验证挑战,它可以没有指定特定的超时间隔等(很多原因)。我将在下面介绍一些更好的选择。

在这些示例中,我将仅讨论针对iOS 7及更高版本的应用程序的解决方案,并考虑(在撰写本文时)iOS 8是当前的主要版本,而Apple建议仅​​支持版本N和N-1。如果您需要支持较旧的iOS版本,建议您查看 NSURLConnection类以及AFNetworking1.0版本。如果您查看此答案的修订历史,则可以使用NSURLConnection和ASIHTTPRequest找到基本示例,尽管应注意,不再维护ASIHTTPRequest,并且不应其用于新项目。


NSURLSession

让我们从iOS 7中引入的NSURLSession开始,它大大提高了在iOS中进行联网的便利性。使用NSURLSession,您可以轻松地通过回调块执行异步HTTP请求,并通过其委托来处理身份验证挑战。但是,使该类真正与众不同的是,即使该应用程序被发送到后台,被终止甚至崩溃,它也允许下载任务继续运行。这是其用法的基本示例。

// 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];

由此您将注意到该downloadTaskWithURL: completionHandler:方法返回了NSURLSessionDownloadTask的实例,在该实例-[NSURLSessionTask resume]上调用了一个实例方法。这实际上是告诉下载任务开始的方法。这意味着您可以加快下载任务的速度,如果需要,可以推迟启动它(如果需要)。这也意味着,只要存储对任务的引用,就可以根据需要使用其cancelsuspend方法来取消或暂停任务。

NSURLSessionTasks真正酷的地方在于,只需一点点KVO,您就可以监视其countOfBytesExpectedToReceive和countOfBytesReceived属性的值,将这些值提供给NSByteCountFormatter,并使用易于阅读的单位(例如42)轻松为用户创建下载进度指示器100 KB)。

不过,在我离开NSURLSession之前,我想指出,可以避免在下载的回调块中的多个不同点不得不将dispatch_async回到主线程的麻烦。如果选择走这条路线,则可以使用其初始化程序初始化会话,该初始化程序允许您指定委托以及委托队列。这将要求您使用委托模式而不是回调块,但这可能是有益的,因为它是支持后台下载的唯一方法。

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

AFNetworking 2.0

如果您从未听说过AFNetworking,那么恕我直言,这是网络库的全部。它是为Objective-C创建的,但也可以在Swift中使用。用其作者的话说:

AFNetworking是一个适用于iOS和Mac OS X的令人愉悦的网络库。它建立在Foundation URL Loading System的基础上,扩展了Cocoa中内置的强大的高级网络抽象。它具有模块化的体系结构,具有精心设计的功能丰富的API,使用起来很有趣。

AFNetworking 2.0支持iOS 6及更高版本,但在此示例中,我将使用其AFHTTPSessionManager类,由于它使用了NSURLSession类周围的所有新API,因此它需要iOS 7及更高版本。当您阅读下面的示例时,这将变得显而易见,该示例与上面的NSURLSession示例共享许多代码。

不过,我还是要指出一些差异。首先,您将创建一个AFURLSessionManager实例,而不是创建自己的NSURLSession,该实例将在内部管理一个NSURLSession。这样做可以让您利用其一些便捷方法,例如-[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]。此方法的有趣之处在于,它使您可以相当简洁地创建一个下载任务,该任务具有给定的目标文件路径,一个完成块和一个NSProgress指针的输入,您可以在该指针上观察有关下载进度的信息。这是一个例子。

// 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];

当然,由于我们已将包含该代码的类作为观察者添加到NSProgress实例的属性之一,因此您必须实现该-[NSObject observeValueForKeyPath:ofObject:change:context:]方法。在这种情况下,我提供了一个示例,说明如何更新进度标签以显示下载进度。真的很简单。NSProgress具有一个实例方法localizedDescription,该方法将以本地化的人类可读格式显示进度信息。

- (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];
    }
}

别忘了,如果要在项目中使用AFNetworking,则需要遵循其安装说明,并确保遵循#import <AFNetworking/AFNetworking.h>

Alamofire

最后,我想用Alamofire给出最后一个示例。这是一个库,它使Swift中的联网变得轻而易举。我没有足够的字符来详细介绍此示例的内容,但它与上一个示例几乎一样,只是可以说是更漂亮的方式。

// 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)
                }
            }
        }
    }

您如何建议我们获取AFNetworking解决方案的documentDirectoryUrl?
SleepsOnNewspapers

2
@HomelessPeopleCanCode在“解决方案”标题下,我回答的顶部附近包括了该方法,并在我的所有Objective-C示例中都使用了该方法,但是还有更多可用的选项。其中大多数将以NSString的形式为您提供到文档目录的路径,因此您需要将其转换为NSURL以便能够在我的示例中使用它们而不必修改它们,这可以像这个:NSURL *filePathURL = [NSURL fileURLWithPath:filePathString];
米克·麦卡勒姆

很好的解释。如何在照片中保存?通过Alamofire。在“目标”中作为参数传递什么?
khunshan 2015年

哇!辉煌的答案,非常有启发性。谢谢!
Koushik Ravikumar

我在该网站上读到的最大答案之一。非常有用,内容丰富。感谢您抽出宝贵的时间来教育我们;)
Alexandre G.

39

您无法在应用程序的捆绑包中保存任何内容,但是可以+[NSData dataWithContentsOfURL:]用来将图像存储在应用程序的文档目录中,例如:

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

并不是完全永久的,但至少在用户删除应用程序之前,它会一直存在。


3
这个答案比公认的要好,因为如果使用UIImage UIImageJPEGRepresentation或UIImagePNGRepresentation将其另存为PNG或JPEG,则iPhone磁盘上的数据大小将是原始磁盘的两倍。使用此代码,您只需存储原始数据。
jcesarmobile'9

13

这是主要概念。玩得开心 ;)

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];

7

由于我们现在使用的是IO5,因此您不再需要将映像写入磁盘。
现在,您可以在coredata二进制属性上设置“允许外部存储”。根据苹果发布说明,它的含义如下:

小数据值(如图像缩略图)可以有效地存储在数据库中,但是大照片或其他媒体最好由文件系统直接处理。现在,您可以指定托管对象属性的值可以存储为外部记录-请参见setAllowsExternalBinaryDataStorage: 启用后,Core Data会根据每个值试探性地决定是否应将数据直接保存在数据库中或存储URI。到一个由您管理的单独文件。如果使用此选项,则不能基于二进制数据属性的内容进行查询。


3

就像其他人所说的,在很多情况下,您应该在后台线程中下载图片而不阻塞用户界面

在这种情况下,我最喜欢的解决方案是使用带有块的便捷方法,例如:(credit-> iOS:如何异步下载图像(并使UITableView快速滚动)

- (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);
                               }
                           }];
}

并称它为

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...
}];

1

这是我下载广告横幅的方法。如果要下载大图像或一堆图像,最好在后台执行此操作。

- (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

这是从url异步下载图像,然后将其保存在Objective-c中的代码:->

    + (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);
                                       }
                                   }];
        }

0

如果您正在使用AFNetworking库下载图像并且该图像正在UITableview中使用,则可以在cellForRowAtIndexPath中使用以下代码

 [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

您可以使用NSURLSessionDataTask下载图像而不会阻止UI。

+(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

这是一个Swift 5解决方案,用于通过使用Alamofire以下命令将图像或一般文件下载并保存到documents目录:

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)
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.