您的位置:首页 > 产品设计 > UI/UE

UITables With Downloaded Images - Easy Asynchronous Code <UITable 异步加载图片>

2011-06-20 13:23 471 查看
  本文翻译来自:http://www.markj.net/iphone-asynchronous-table-image/

  在开发iPho ne&iPad应用时,利用UITable从指定的URL中加载图片是经常遇到的。如果采用单线程的同步加载显然不能满足用户的体验要求,所以我们需要利用多线程的方法在应用程序后台并行的去加载图片。但是多线程编程是比较难的,你需要考虑很多线程安全方面的问题。那怎样才能在避免多线程的情况下异步加载图片了?Cocoa提供了一个精彩的设计思维:

UIView heirachy + URL loading system + delegate design =                         multi-threaded image loading with no multi-threaded coding!


  怎么样才能鱼和熊掌兼得了?每一个iPhone应用程序都是一个多线程程序,或者至少是会同多线程iPhone操作系统一起运行。在合适的地方利用恰当的代理方法,你可以有效的利用iPhone已经为你免费提供的多线程实现,你自己不需要编写任何的多线程代码,这样就可以避免一些多线程方面的bug。iPhone应用程序是一个很大的事件循环(如图,可参考Cocoa programming for max os X)——当事件发生时将触发你编写的类的方法。当你使用URL加载系统的异步API时,iPhone将使用不同的线程而不是当前运行应用程序的事件循环(event loop)线程来加载URL对应的内容。当数据加载完成后,将通过event loop产生回调。



  根据以上描述,在开发中就可以编写如下简单代码:

connection =[ [NSURLConnection alloc]
initWithRequest: request delegate:self];

-(void) connection:(NSURLConnection *)theConnection
didReceiveData:(NSData *)incrementalData


  需要注意的是,当数据从远程服务器返回时,其他正在进行下载的iPhone线程不能在你的方法正在调用的同时调用你的对象,它是把消息放入你的应用event loop中。假如它直接调用了你的应用程序,可能恰好你的应用程序正在运行UI代码或者其他一些事情,那么你就不得不去编写线程安全的代码。所以数据到达的调用是作为一个事件存在于event loop中。event loop中的事件是运行在一个单线程中,每次执行一个事件。利用这些,我们可以从Flickr那异步的加载图片而不需要我们自己编写线程安全的代码。更好的是,Cocoa的URL加载系统将并行的加载这些URL。

  但是,我们怎么在加载完图片后更新UITableViewCell了?UIImage是不可变,也就是说,当你加载完一个图片后就不能改变图片的数据了。值得庆幸的是苹果使得这项工作变得很简单。将UIView对象放入UITableViewCell中,而不是直接放入UIImage。开始时,你的View Object可以为空,或者它可以有一个虚假的图片在里面,或者你可以展示一些“something is happening”视图。当图片数据已经加载完全,创建一个UIImageView,设置图片,然后将它放入table cell中。

  作者把这些功能封装成了一个类(AsyncImageView),具体代码如下。使用很方便,只需如下几个步骤:

  1、alloc and initWithRect

  2、add it to a view, eg in a table cell's content view;

  3、send it the loadImageFromURL: message.

@interface AsyncImageView : UIView {
NSURLConnection* connection;
NSMutableData* data;
}
@end

@implementation AsyncImageView

- (void)loadImageFromURL:(NSURL*)url {
if (connection!=nil) { [connection release]; }
if (data!=nil) { [data release]; }
NSURLRequest* request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
connection = [[NSURLConnection alloc]
initWithRequest:request delegate:self];
//TODO error handling, what if connection is nil?
}

- (void)connection:(NSURLConnection *)theConnection
didReceiveData:(NSData *)incrementalData {
if (data==nil) {
data =
[[NSMutableData alloc] initWithCapacity:2048];
}
[data appendData:incrementalData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {

[connection release];
connection=nil;

if ([[self subviews] count]>0) {
[[[self subviews] objectAtIndex:0] removeFromSuperview];
}

UIImageView* imageView = [[[UIImageView alloc] initWithImage:[UIImage imageWithData:data]] autorelease];

imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.autoresizingMask = ( UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight );

[self addSubview:imageView];
imageView.frame = self.bounds;
[imageView setNeedsLayout];
[self setNeedsLayout];
[data release];
data=nil;
}

- (UIImage*) image {
UIImageView* iv = [[self subviews] objectAtIndex:0];
return [iv image];
}

- (void)dealloc {
[connection cancel];
[connection release];
[data release];
[super dealloc];
}

@end


  利用上面封装的一个类,可以编写一个UITableViewCell使用例子。代码如下。The AsyncImageView gets tagged with 999, and when it gets recycled, that 999 tagged view gets fished out and removed. So only the cell is being recycled, not the AsyncImageView object. When its removed from the cells content view it also gets released, causing dealloc, which in turn cancels the url download (if its outstanding).

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"ImageCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
cell = [[[UITableViewCell alloc]
initWithFrame:CGRectZero reuseIdentifier:CellIdentifier]
autorelease];
} else {
AsyncImageView* oldImage = (AsyncImageView*)
[cell.contentView viewWithTag:999];
[oldImage removeFromSuperview];
}

CGRect frame;
frame.size.width=75; frame.size.height=75;
frame.origin.x=0; frame.origin.y=0;
AsyncImageView* asyncImage = [[[AsyncImageView alloc]
initWithFrame:frame] autorelease];
asyncImage.tag = 999;
NSURL* url = [imageDownload
thumbnailURLAtIndex:indexPath.row];
[asyncImage loadImageFromURL:url];

[cell.contentView addSubview:asyncImage];

return cell;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: