使用 UIImage 从 URL 异步加载图像时出现明显延迟

发布于 2024-10-04 17:01:29 字数 681 浏览 6 评论 0原文

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

    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 中,它似乎几乎会立即更新图像。

延迟并不完全是由缓慢的网络服务器引起的...我可以在 Safari 中加载图像 URL,加载时间不到一秒,或者我可以使用相同的代码加载它而不使用 NSOperationQueue,并且加载速度要快得多。

有什么方法可以减少显示图像之前的延迟,但继续使用 NSOperationQueue 吗?

I am trying to write an iPad app that loads an image from a URL. I am using the following image loading code:

    url = [NSURL URLWithString:theURLString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");

All of that code gets added to a NSOperationQueue as an operation so it will load asynchronously and not cause my app to lock up if the image's websever is slow. I added the NSLog line so I could see in the console when this code finished executing.

I have noticed consistently that the image is updated in my app about 5 seconds AFTER the code finishes executing. However if I use this code on it's own without putting it in the NSOperationQUeue it seems to update the image almost immediately.

The lag is not caused entirely by a slow web server... I can load the image URL in Safari and it takes less than a second to load, or I can load it with the same code without the NSOperationQueue and it loads much more quickly.

Is there any way to reduce the lag before my image is displayed but keep using a NSOperationQueue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

爱她像谁 2024-10-11 17:01:29

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

如果您想保持电池效率,Apple 强烈建议不要使用线程来执行异步 URL 获取。相反,您应该使用 NSURLConnection 并允许运行循环组织异步行为。编写一个快速方法并不难,该方法只需将数据累积到 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”是 NSOperation 隐式创建的线程中池中的自动释放对象。因为您需要它在使用之前保持有效,所以我使用了 waitUntilDone:YES。另一种方法是让数据成为您明确拥有的东西,并让主线程方法释放它。

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

According to the documentation, the code you have written is invalid. UIKit objects may not be called anywhere but on the main thread. I'll bet that what you're doing happens to work in most respects but doesn't successfully alter the display, with the screen being updated by coincidence for some other reason.

Apple strongly recommend that threads are not the way to perform asynchronous URL fetches if you want to remain battery efficient. Instead you should be using NSURLConnection and allowing the runloop to organise asynchronous behaviour. It's not that hard to write a quick method that just accumulates data to an NSData as it comes then posts the whole thing on to a delegate when the connection is complete but assuming you'd rather stick with what you've got I'd recommend:

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 does what the name says — the object is sent to will schedule the selector requested with the object given as a single parameter on the main thread as soon as the run loop can get to it. In this case 'data' is an autoreleased object on the pool in the thread implicitly created by the NSOperation. Because you need it to remain valid until it has been used, I've used waitUntilDone:YES. An alternative would be to make data something that you explicitly own and have the main thread method release it.

The main disadvantage of this method is that if the image returns in a compressed form (such as a JPEG or a PNG), it'll be decompressed on the main thread. To avoid that without making empirical guesses about the behaviour of UIImage that go above and beyond what is documented to be safe, you'd need to drop to the C level and use CoreGraphics. But I'm taking it as given that doing so is beyond the scope of this question.

岁月无声 2024-10-11 17:01:29

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

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

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

Tommy is correct about needing to do all UIKit stuff on the main thread. However, if you're running the fetch on a background operation queue, there's no need to use the NSURLConnection asynchronous loading. Also, by keeping the image decoding work on the background operation, you'll keep the main thread from blocking while decoding the image.

You should be able to use your original code as is, but just change [imgView setImage:img] to:

[imageView performSelectorOnMainThread:@selector(setImage:)
                          withObject:img
                       waitUntilDone:NO];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文