Cocoa touch 使用UIImage从URL异步加载图像时存在明显延迟

Cocoa touch 使用UIImage从URL异步加载图像时存在明显延迟,cocoa-touch,image,uiimage,imageview,nsoperationqueue,Cocoa Touch,Image,Uiimage,Imageview,Nsoperationqueue,我正在尝试编写一个iPad应用程序,从URL加载图像。我正在使用以下图像加载代码: url = [NSURL URLWithString:theURLString]; NSData *data = [NSData dataWithContentsOfURL:url]; img = [[UIImage alloc] initWithData:data]; [imageView setImage:img]; [img release]; NSLog(@

我正在尝试编写一个iPad应用程序,从URL加载图像。我正在使用以下图像加载代码:

    url = [NSURL URLWithString:theURLString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");
所有这些代码都会作为一个操作添加到NSOperationQueue中,因此它将异步加载,并且不会在图像的速度较慢时导致我的应用程序锁定。我添加了NSLog行,以便在控制台中看到代码何时完成执行

我一直注意到,在代码执行完毕大约5秒后,图像在我的应用程序中被更新。但是,如果我单独使用此代码而不将其放入NSOperationQUeue中,它似乎会立即更新映像

延迟不完全是由缓慢的web服务器造成的。。。我可以在Safari中加载图像URL,加载只需不到一秒钟,也可以使用相同的代码加载图像URL,而不使用NSOperationQueue,加载速度更快


有没有办法在显示我的图像之前减少延迟,但继续使用NSOperationQueue?

根据文档,您编写的代码无效。UIKit对象不能在任何地方调用,只能在主线程上调用。我敢打赌,你正在做的事情在大多数方面都能正常工作,但并没有成功地改变显示,因为某些其他原因,屏幕被巧合地更新了

苹果强烈建议,如果你想保持电池效率,线程不是执行异步URL获取的方式。相反,您应该使用NSURLConnection并允许runloop组织异步行为。编写一个快速方法并不难,该方法只需在连接完成时将数据累积到NSData,然后将整个过程发布到代理上,但假设您愿意坚持现有的方法,我建议:

url = [NSURL URLWithString:theURLString];
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:@selector(setImageViewImage:) withObject:data waitUntilDone:YES];

...

- (void)setImageViewImage:(NSData *)data
{
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");
}
performSelectorOnMainThread
执行名称所述的操作-一旦运行循环到达该对象,该对象将被发送到主线程,并将该对象作为单个参数在主线程上调度所请求的选择器。在本例中,“data”是NSO操作隐式创建的线程中池上的自动释放对象。因为您需要它在被使用之前保持有效,所以我使用了
waituntldone:YES
。另一种方法是使数据成为您显式拥有的数据,并让主线程方法释放它


这种方法的主要缺点是,如果图像以压缩形式返回(如JPEG或PNG),它将在主线程上解压缩。为了避免这种情况,在没有对UIImage的行为进行经验猜测的情况下,您需要降低到C级别并使用CoreGraphics。但是我认为这样做超出了这个问题的范围。

Tommy关于需要在主线程上完成所有UIKit工作的说法是正确的。但是,如果在后台操作队列上运行fetch,则无需使用NSURLConnection异步加载。另外,通过在后台操作中保持图像解码工作,可以在解码图像时防止主线程阻塞

您应该能够按原样使用原始代码,但只需将[imgView setImage:img]更改为:

[imageView performSelectorOnMainThread:@selector(setImage:)
                          withObject:img
                       waitUntilDone:NO];

谢谢,汤米!今晚我会看一看,看看从你告诉我的事情中我能得到什么。事实上,今天早上我正在阅读Coacoa中下载图像的不同方式,我自己发现,当我使用NSUrlRequest和NSURLConnection重新编写整个图像处理代码时,它似乎像我预期的那样加载了图像。我仍然不确定我是否会使用我编写的新方法或您刚才向我展示的代码,但有选择是很好的,并且双向编写是一种很好的学习体验。再次感谢您的帮助,感谢您容忍像我这样的新手!:)顺便说一句,如果有人读过这个问题,我发现这篇文章对于实现基于NSUrlRequest的图像加载器非常有帮助。哇,这解决了问题。非常感谢!那太简单了!但我仍然不完全确定为什么。我得仔细阅读一下。现在,我已经有了一个完全实现且工作正常的NSUrlConnection方法(它本身是异步的)和一个使用NSObjectQueue for Asynchronousity方法的NSData/dataWithContentsOfURL方法,您推荐我使用哪种方法?使用NSUrlConnection和NSData以及NSUrlConnection和NSData的优势是什么?我听说NSUrlConnection在缓存方面具有更大的灵活性,可以更好地处理错误。这是真的吗?再次感谢!NSURLConnection确实为您提供了更大的灵活性。但是在后台操作中使用NSURLConnection的异步加载是相当棘手的,因为您需要设置自己的运行循环。您可以使用
-[NSURLConnection sendSynchronousRequest:returningResponse:error://code>方法来获得NSURLConnection(请参阅NSMutableURLRequest)提供的一些灵活性,同时保持同步。除非您有复杂的HTTP重定向/身份验证/缓存需求,否则我可能会这么做。至于“为什么”这个解决方案有效,是因为在主线程上运行的视图绘制代码需要知道您刚刚更改了图像视图的图像。通过在后台线程中调用setImage:可以推测,主线程永远不会被通知要绘制新内容(直到其他内容导致视图刷新)。在后台线程上这样做可能会造成更坏的行为——例如,如果你在主线程处于绘制中间的时候改变了图像。只是一个澄清——我的NStATE方法使用NSOperationQueue(不是NSbObjtReals.OOP)。但是,我的NSUrlRequest方法不使用NSObjectQueue,据我所知,默认情况下它是异步的。至少在我的应用程序执行完毕之前,它不会停止运行。也许我对什么是同步vs。