IOS 下载并保存应用程序中的图像

我是否有可能从网站下载一张图片并将其永久保存在我的应用程序中?我真的不知道,但它会成为我的应用程序的一个很好的功能。

141367 次浏览

你不能在应用程序包中保存任何东西,但是你可以使用 +[NSData dataWithContentsOfURL:]将图像存储在应用程序的文档目录中,例如:

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

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

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

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

由于我们现在在 IO5上,您不再需要将映像写入磁盘。
您现在可以在一个 coredata 二进制属性上设置“ allow foreign Storage”。 根据苹果发行说明,它的意思是:

图像缩略图等小数据值可以有效地存储在 数据库,但大型照片或其他媒体最好直接处理 现在可以指定托管的 对象属性可以作为外部记录存储-参见 SetAllowsExternalBinaryDataStorage: 当启用时,核心数据启发式地根据每个值决定是否 它应该将数据直接保存在数据库中,或者将 URI 存储到 它为您管理的独立文件。您不能基于 如果使用此选项,则为二进制数据属性的内容。

虽然这里的其他答案确实会起作用,但是 它们真的不是应该在生产代码中使用的解决方案(至少不会没有修改)

问题

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

如果主线程被阻塞,UI 更新将不会发生,直到图像的下载/保存完成。举个例子来说明这意味着什么,假设你在你的应用程序中添加了一个 UIActivityIndicatorView 来向用户展示下载仍然在进行中(我将在整个回答中用这个例子) ,并使用以下的粗略控制流程:

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

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

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

在这个更新发生之前,下载被触发,由于这是一个同步操作,它会阻塞主线程直到完成下载(保存也有同样的问题)。这实际上会阻止活动指示器启动它的动画。之后,调用活动指示器的 stop Animating 方法,并期望一切正常,但事实并非如此。

在这一点上,您可能会发现自己想知道以下内容。

为什么我的活动指标一直没有显示出来?

这么想吧。您告诉指示器启动,但是在下载开始之前它没有机会。下载完成后,告诉指示器停止动画。由于主线程在整个操作过程中被阻塞,所以您实际看到的行为更像是告诉指示器启动,然后立即告诉它停止,即使在这两者之间有一个(可能)大的下载任务。

现在,在 最好的情况中,所有这些都会导致糟糕的用户体验(仍然非常糟糕)。即使你认为这没什么大不了的,因为你只是下载了一个很小的图片,而且下载几乎是瞬间发生的,但事实并不总是如此。您的一些用户可能有缓慢的 Internet 连接,或者服务器端出了问题,导致无法立即/根本无法开始下载。

在这两种情况下,应用程序将不能处理 UI 更新,甚至触摸事件,而您的下载任务坐在那里无所事事,等待下载完成或服务器响应其请求。

这意味着从主线程同步下载将阻止您可能实现任何向用户表明下载当前正在进行的东西。由于触摸事件也是在主线程上处理的,这就排除了添加任何类型的取消按钮的可能性。

然后在 最坏的情况中,您将开始收到下列崩溃报告。

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

这些都很容易识别的异常代码 0x8badf00d,这可以解读为“吃了坏食物”。这个异常是由看门狗定时器抛出的,它的任务是监视阻塞主线程的长时间运行的任务,如果这个过程持续太久,就会关闭有问题的应用程序。可以说,这仍然是一个糟糕的用户体验问题,但如果这种情况开始发生,应用程序已经越过了糟糕的用户体验和糟糕的用户体验之间的界限。

这里有一些关于 苹果的技术问答中关于同步网络的更多信息,这些信息可能导致这种情况的发生(简称 苹果的技术问答)。

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

  1. 同步网络ーー这是您发出网络请求并阻塞等待响应的地方。
  2. 主线程ーー同步网络通常不太理想,但是如果在主线程上进行同步网络,则会导致特定的问题。请记住,主线程负责运行用户界面。如果阻塞主线程的时间过长,则用户界面将变得无法接受的无响应。
  3. 长时间超时ーー如果网络刚刚消失(例如,用户正在进入隧道的列车上) ,任何挂起的网络请求都不会失败,直到超时过期... ..。

...

  1. 看门狗ー为了保持用户界面的响应,iOS 包括一个看门狗机制。如果您的应用程序未能及时响应某些用户界面事件(启动、暂停、恢复、终止) ,看门狗将杀死您的应用程序并生成一个看门狗超时崩溃报告。看门狗给你的时间并没有正式的文档记录,但是它总是比网络超时少。

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

现在,我将不再喋喋不休地解释为什么提供的答案可能有问题,而是开始提供一些替代解决方案。请记住,我在这些示例中使用了一个小图像的 URL,当使用高分辨率图像时,您会注意到一个更大的差异。


解决方案

我将从展示其他答案的安全版本开始,并介绍如何处理 UI 更新。这将是几个示例中的第一个,所有示例都假定实现它们的类具有 UIImageView、 UIActivityIndicatorView 以及访问文档目录的 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:]方法,但是有一些关键的区别。首先,您会注意到在这个示例中,我们做的第一个调用是告诉活动指示器开始动画,然后在这个示例和同步示例之间有一个直接的区别。紧接着,我们使用  传递全局并发队列将执行转移到后台线程。

此时,您已经大大改进了下载任务。现在,由于递增异步()块中的所有事情都将发生在主线程之外,您的界面将不再锁定,您的应用程序将可以自由地响应触摸事件。

这里需要注意的是,这个代码块中的所有代码都将在后台线程上执行,直到图像的下载/保存成功,这时你可能需要告诉活动指示器停止动画,或者将新保存的图像应用到 UIImageView。不管是哪种方式,这些都是对 UI 的更新,这意味着您必须使用 patch _ get _ main _ queue ()分派回主线程来执行这些更新。如果不这样做,就会出现未定义行为问题,这可能会导致用户界面在一段意想不到的时间后更新,甚至可能导致崩溃。在执行 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];
});
}
});

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

在这些例子中,我将只讨论针对 iOS7及以上版本的应用程序的解决方案,考虑到(在撰写本文时) iOS8是当前的主要版本,而 苹果建议只支持 N 和 N-1版本。如果你需要支持旧的 iOS 版本,我建议查看 NSULConnection类,以及 AFNetworking 的1.0版本。。如果你查看这个答案的修订历史,你可以找到使用 NSURLConnection 和 ASIHTTPRequest的基本例子,尽管应该注意到 ASIHTTPRequest 已经不再被维护,没有应该被用于新的项目。


会议

让我们从 iOS7中引入的 会议开始,它大大提高了在 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:方法返回一个 NSULSessionDownloadTask 的实例,在该实例上调用了一个实例方法 -[NSURLSessionTask resume]。这个方法实际上告诉下载任务启动。这意味着您可以启动您的下载任务,如果需要,可以推迟启动它(如果需要)。这也意味着,只要存储对任务的引用,就可以根据需要利用其 cancelsuspend方法取消或暂停任务。

NSULSessionTasks 真正酷的地方在于,只需要一点点 KVO,你就可以监控它的 count OfBytesExpectedToRecept 和 count OfBytesRecected 属性的值,把这些值提供给一个 NSByteCountFormatter,然后用人类可读的单位(例如42KB 的100KB)轻松地为用户创建一个下载进度指示器。

但是,在我离开 NSURLSession 之前,我想指出的是,在下载的回调块的几个不同的点上,不得不把异步分派回主线程的丑陋之处是可以避免的。如果选择此路由,则可以使用允许指定委托和委托队列的初始化器初始化会话。这将要求您使用委托模式而不是回调块,但这可能是有益的,因为这是支持后台下载的唯一方法。

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

AFNetworking 2.0

如果您从未听说过 AFNetworking,恕我直言,它是网络库的终极。它是为 Objective-C 创建的,但它也可以在 Swift 中工作。用作者的话来说:

AFNetworking 是一个适用于 iOS 和 Mac OS X 的令人愉快的网络库。它构建在 Foundation URL 加载系统之上,扩展了 Cocoa 内置的强大的高级网络抽象。它有一个模块化架构,其中包含设计良好、功能丰富的 API,使用起来非常方便。

AFNetworking 2.0支持 iOS 6及以上版本,但是在这个例子中,我将使用它的 AFHTTPSessionManager 类,它需要 iOS 7及以上版本,因为它使用了所有新的 API,包括 NSURLSession 类。当您阅读下面的示例时,这将变得非常明显,它与上面的 NSULSession 示例共享了大量代码。

不过,我想指出一些不同之处。首先,您将创建一个 AFURLSessionManager 的实例,它将在内部管理一个 NSULSession,而不是创建您自己的 NSULSession。这样做允许您利用它的一些便利方法,比如 -[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的最后一个例子。这个图书馆让斯威夫特的人际交往变得轻而易举。关于这个示例的内容,我已经没有字符可以详细说明了,但是它的功能与上面的示例几乎相同,只不过是以一种可以说是更加漂亮的方式。

// 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 库下载图像,并且图像正在 UITableview 中使用,那么您可以在 cellForRowAtIndexPath 中使用以下代码

[ self setImageWithURL: user.user _ ProfilePicturePath to Control: cell.imgView ] ;
 

- (void) setImageWithURL: (NSURL *) url toControl: (id) ctrl
{
NSULRequest * request = [ NSULRequest requestWithURL: url ] ;
AFImageRequestOperation * operation = [ AFImageRequestOperation imageRequestOperationWithRequest: request imageProcessingBlock: nil Success: ^ (NSURLRequest * request,NSHTTPURLResponse * response,UIImage * image){
如果(图片){
如果([ ctrl isKindOfClass: [ UIButton class ]])
{
UIButton Btn = (UIButton) ctrl;
[ btn setBackground Image: image forState: UIControlStateNormal ] ;
}
别的
{
UIImageView ImgView = (UIImageView) ctrl;
图片 = 图像;
}

} } 故障: ^ (NSULRequest * request,NSHTTPURLResponse * response,NSERerror * error){ NSLog (@“ No Image”) ; }]; [操作开始] ; }

下面是我如何下载广告横幅。如果你正在下载一张大图片或一堆图片,最好是在背景中进行。

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

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

在这种情况下,我最喜欢的解决方案是使用带块的方便方法,如下所示: (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...
}];

下面的代码可以从 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);
}
}];
}

您可以使用 NSULSessionDataTask 下载图像而无需阻塞 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];
}

下面是使用 Alamofire下载和保存图像或通常将文件保存到 document 目录的 Swift 5解决方案:

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