如何在UIImageView中asynchronous加载图像?
我有一个UIImageView子视图的UIView。 我需要在UIImageView中加载图像而不会阻塞UI。 阻止调用似乎是: UIImage imageNamed:
这是我想解决这个问题:
-(void)updateImageViewContent { dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ UIImage * img = [UIImage imageNamed:@"background.jpg"]; dispatch_sync(dispatch_get_main_queue(), ^{ [[self imageView] setImage:img]; }); }); }
图像很小(150×100)。
但是,加载图像时UI仍然被阻塞。 我错过了什么?
这是一个小的代码示例,performance出这种行为:
创build一个基于UIImageView的新类,将其用户交互设置为YES,在UIView中添加两个实例,并实现其touchesBegan方法,如下所示:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (self.tag == 1) { self.backgroundColor= [UIColor redColor]; } else { dispatch_async(dispatch_get_main_queue(), ^{ [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; }); [UIView animateWithDuration:0.25 animations: ^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}]; } }
将标签1分配给其中一个imageViews。
当你几乎同时点击两个视图时,会发生什么情况,从加载图像的视图开始? UI是否被阻塞,因为它正在等待[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
回来 ? 如果是这样,我怎么可以asynchronous做到这一点?
这是一个项目在github与ipmcc代码
使用长按,然后拖动以绘制黑色方块周围的矩形。 据我了解他的回答,理论上说,白色的select矩形不应该在第一次加载图像时被阻挡,但实际上是这样。
项目中包括两个图像(一个小:woodenTile.jpg和一个大:bois.jpg)。 结果与两者相同。
图像格式
我真的不知道这是如何与第一次加载图像时UI被阻塞的问题相关的,但是PNG图像解码时不会阻塞UI,而JPG图像会阻止UI。
事件的年表
用户界面的屏蔽从这里开始
并在这里结束。
AFNetworking解决scheme
NSURL * url = [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"]; NSURLRequest * request = [NSURLRequest requestWithURL:url]; [self.imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:@"placeholder.png"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { NSLog(@"success: %@", NSStringFromCGSize([image size])); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"failure: %@", response); }]; // this code works. Used to test that url is valid. But it's blocking the UI as expected. if (false) if (url) { [self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
大多数情况下,它会logging: success: {512, 512}
它也偶尔logging日志: success: {0, 0}
有时: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
但图像从未改变。
问题是, UIImage
实际上并没有读取和解码图像,直到第一次实际使用/绘制。 为了强制这个工作发生在后台线程上,你必须在执行主线程之前在后台线程上使用/绘制图像-setImage:
. 这对我工作:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ UIImage * img = [UIImage imageNamed:@"background.jpg"]; // Make a trivial (1x1) graphics context, and draw the image into it UIGraphicsBeginImageContext(CGSizeMake(1,1)); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]); UIGraphicsEndImageContext(); // Now the image will have been loaded and decoded and is ready to rock for the main thread dispatch_sync(dispatch_get_main_queue(), ^{ [[self imageView] setImage: img]; }); });
编辑:用户界面不被阻止。 你已经专门设置了它,使用UILongPressGestureRecognizer
,默认情况下等待UILongPressGestureRecognizer
,然后再做任何事情。 主线程仍在处理事件,但在GR超时之前不会发生任何事情。 如果你这样做:
longpress.minimumPressDuration = 0.01;
…你会注意到它变得更快捷。 图像加载不是这里的问题。
编辑2:我已经看了代码,发布到github上,在iPad 2上运行,我根本没有得到你所描述的打嗝。 事实上,这很平稳。 以下是在CoreAnimation仪器中运行代码的截图:
正如你可以在顶部的图表看到的,FPS直到60FPS,并保持整个手势。 在底部的图表中,您可以在大约16秒处看到图像首次加载的位置,但是您可以看到帧速率没有下降。 仅仅通过视觉检查,我发现select层相交,并且在第一个交点和图像的外观之间有一个小的但可观察的延迟。 据我所知,后台加载代码正在按预期工作。
我希望我可以帮助你,但我只是没有看到这个问题。
您可以使用AFNetworking库,其中通过导入类别
“UIImageView + AFNetworking.m”并使用如下方法:
[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"] placeholderImage:[UIImage imageNamed:@"static_local_image.png"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { //ON success perform } failure:NULL];
希望这可以帮助 。
我有一个非常类似的问题,我的应用程序,我不得不下载很多图像,并伴随着我的用户界面不断更新。 以下是解决我的问题的简单教程链接:
NSOperations&NSOperationQueues教程
这是好方法:
-(void)updateImageViewContent { dispatch_async(dispatch_get_main_queue(), ^{ UIImage * img = [UIImage imageNamed:@"background.jpg"]; [[self imageView] setImage:img]; }); }
为什么不像AsyncImageView那样使用第三方库? 使用这个,你所要做的就是声明你的AsyncImageView对象,并传递你想要加载的url或者image。 活动指示器将在图像加载期间显示,没有任何东西会阻止用户界面。
– (void)touchesBegan:在主线程中调用。 通过调用dispatch_async(dispatch_get_main_queue),您可以将该块放入队列中。 当队列准备好时(即系统结束处理触摸),该块将由GCD处理。 这就是为什么你看不到你的woodenTile加载并分配给self.image,直到你释放你的手指,让GCD处理在主队列中排队的所有块。
replace:
dispatch_async(dispatch_get_main_queue(), ^{ [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]]; });
通过:
[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
应该解决您的问题…至less为展示它的代码。
考虑使用SDWebImage :它不仅在后台下载和caching图像,还加载和渲染它。
我已经在桌面视图中使用了很好的结果,即使在下载之后也有很大的图像加载缓慢。
https://github.com/nicklockwood/FXImageView
这是一个可以处理背景加载的图像视图。
用法
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)]; imageView.contentMode = UIViewContentModeScaleAspectFit; imageView.asynchronous = YES; //show placeholder imageView.processedImage = [UIImage imageNamed:@"placeholder.png"]; //set image with URL. FXImageView will then download and process the image [imageView setImageWithContentsOfURL:url];
要获得文件的URL,您可能会发现以下有趣的内容:
在应用程序启动时获取包文件引用/path
在应用程序中使用AFNetwork时,您不需要使用任何块来加载图像,因为AFNetwork为其提供了解决scheme。 如下:
#import "UIImageView+AFNetworking.h"
和
Use **setImageWithURL** function of AFNetwork....
谢谢
我实现它的一个方法是:(虽然我不知道它是否是最好的)
首先,我使用串行asynchronous或并行队列创build一个队列
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);** or queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
哪一个你可能会find更好的您的需求。
之后:
dispatch_async( queue, ^{ // Load UImage from URL // by using ImageWithContentsOfUrl or UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // Then to set the image it must be done on the main thread dispatch_sync( dispatch_get_main_queue(), ^{ [page_cover setImage: imagename]; imagename = nil; }); });