Objective-C:异步填充 UITableView - 如何做到这一点? [英] Objective-C: Asynchronously populate UITableView - how to do this?

查看:36
本文介绍了Objective-C:异步填充 UITableView - 如何做到这一点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我似乎找不到关于这个问题的任何信息,所以我想我会问社区.

基本上,我有一个 UITableView,我想在它的数据从我的服务器加载时显示一个活动指示器.

这是我正在尝试做的一些示例代码(我正在使用 ASIHttpRequest).

//self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];//这有效NSString *urlStr=[[NSString alloc] initWithFormat:@"http://www.google.com"];//一些缓慢的请求NSURL *url=[NSURL URLWithString:urlStr];__block ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];[请求 setDelegate:self];[请求 setCompletionBlock:^{self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];//这不起作用...[表重新加载数据];}];[请求 setFailedBlock:^{}];[请求开始异步];

对 google.com 的虚拟请求没有任何作用 - 它只会造成延迟,在响应中我希望用我自己网站的一些 JSON 响应重新填充表.

但是当我尝试用颜色填充表格时,没有任何反应!我只是得到一个空白表格...如果我取消注释上面的行,它可以正常工作,只是在 http 响应上对我不起作用.

非常感谢任何建议.

我做了一个 [self.tableView reloadData]; 现在它可以工作了...

解决方案

  1. 停止使用 ASIHTTPRequest.NSURLConnection 不难使用,并且会产生更好、性能更高的代码.
  2. 您的 JSON 响应应该输入到数据结构而不是 UI.我推荐核心数据.
  3. 数据结构应该提供给您的 UITableView.我再次推荐 Core Data.

我建议您查看 MVC 的工作原理,您正在使设计短路,这是核心问题.

剧透

这里有更详细的方法.首先,您希望数据检索是异步的.最简单、最可重用的方法是构建一个简单的 NSOperation 子类.

@class CIMGFSimpleDownloadOperation;@protocol CIMGFSimpleDownloadDelegate - (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;@结尾@interface CIMGFSimpleDownloadOperation : NSOperation@property (nonatomic,assign) NSInteger statusCode;- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id)delegate;@结尾

这个子类是从 URL 下载东西的最基本方法.用一个 NSURLRequest 和一个委托来构造它.它会回调成功或失败.实现只是稍微长了一点.

#import "CIMGFSimpleDownloadOperation.h"@interface CIMGFSimpleDownloadOperation()@property (nonatomic, 保留) NSURLRequest *request;@property (nonatomic, 保留) NSMutableData *data;@property (nonatomic,assign) id代表;@结尾@implementation CIMGFSimpleDownloadOperation- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id)delegate{if (!(self = [super init])) 返回零;[self setDelegate:delegate];[self setRequest:request];回归自我;}- (void)dealloc{[self setDelegate:nil];[self setRequest:nil];[self setData:nil];[超级dealloc];}- (无效)主要{[NSURLConnection connectionWithRequest:[self request] delegate:self];CFRunLoopRun();}- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)resp{[self setStatusCode:[resp statusCode]];[self setData:[NSMutableData 数据]];}- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)newData{[[自身数据] appendData:newData];}- (void)connectionDidFinishLoading:(NSURLConnection*)connection{[[self delegate] operation:self didCompleteWithData:[self data]];CFRunLoopStop(CFRunLoopGetCurrent());}- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error{[[self 委托] 操作:self didFailWithError:error];CFRunLoopStop(CFRunLoopGetCurrent());}@synthesize 委托;@合成请求;@综合数据;@synthesize statusCode;@结尾

现在这个类是非常可重用的.您可以根据需要添加 NSURLConnection 的其他委托方法.NSURLConnection 可以处理重定向、身份验证等.我强烈建议您查看其文档.

从这里您可以从 UITableViewController 或应用程序的其他部分分离 CIMGFSimpleDownloadOperation.对于这个演示,我们将在 UITableViewController 中进行.根据您的应用程序需求,您可以在任何有意义的地方开始数据下载.对于此示例,我们将在视图出现时启动它.

- (void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];NSURLRequest *request = ...;CIMGFSimpleDownloadOperation *op = [[CIMGFSimpleDownloadOperation alloc] initWithURLRequest:request andDelegate:self];[[NSOperationQueue mainQueue] addOperation:op];[self setDownloadOperation:op];//保持引用以防我们想取消它[op 发布], op = nil;}

现在,当视图出现时,异步调用将去下载 URL 的内容.在这段代码中,要么通过要么失败.失败第一:

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;{[self setDownloadOperation:nil];NSLog(@"下载失败:%@
%@", [error localizedDescription], [error userInfo]);}

如果成功,我们需要解析返回的数据.

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;{[self setDownloadOperation:nil];NSLog(@"下载完成");//1.将数据按摩成我们想要的任何东西,Core Data,一个数组,任何东西//2.使用新数据更新 UITableViewDataSource//注意:我们可能在这里处于后台线程.如果 ([NSThread isMainThread]) {[[self tableView] reloadData];} 别的 {dispatch_sync(dispatch_get_main_queue(),^{[[self tableView] reloadData];});}}

完成了.多几行代码供您编写,但它取代了使用 ASI 导入的 13,000 多行代码,从而使应用程序更小、更精简、更快.更重要的是,它是一款您理解每一行代码的应用.

I can't seem to find any info on this question, so I thought I'd ask the community.

Basically, I have a UITableView and I want to show an activity indicator while its data is loading from my server.

Here is some example code of what I'm trying to do (I'm using ASIHttpRequest).

    //self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];   //this works
    NSString *urlStr=[[NSString alloc] initWithFormat:@"http://www.google.com"];   //some slow request
    NSURL *url=[NSURL URLWithString:urlStr];

    __block ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];
    [request setDelegate:self];
    [request setCompletionBlock:^{
        self.listData = [[NSArray alloc] initWithObjects:@"Red", @"Green", @"Blue", @"Indigo", @"Violet", nil];   //this doesn't work...
        [table reloadData];

    }];
    [request setFailedBlock:^{

    }];
    [request startAsynchronous];

The dummy request to google.com does nothing - it just creates a delay and in the response I hope to repopulate the table with some JSON response from my own website.

But when I try to populate the table with the colours, nothing happens! I just get a blank table... If I uncomment the line above, it works fine, it's just on http responses things don't work for me.

Any suggestions greatly appreciated.

Edit:

I did a [self.tableView reloadData]; and now it works...

解决方案

  1. Stop using ASIHTTPRequest. NSURLConnection is not hard to use and will result in better, more performant code.
  2. Your JSON response should be fed into a data structure not the UI. I recommend Core Data.
  3. The data structure should feed your UITableView. Again, I recommend Core Data.

I would suggest reviewing how MVC works, you are short circuiting the design and that is the core problem.

SPOILER

Here is a more detailed how to. First you want the data retrieval to be async. Easiest and most reusable way to do that is build a simple NSOperation subclass.

@class CIMGFSimpleDownloadOperation;

@protocol CIMGFSimpleDownloadDelegate <NSObject>

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;

@end

@interface CIMGFSimpleDownloadOperation : NSOperation

@property (nonatomic, assign) NSInteger statusCode;

- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate;

@end

This subclass is the most basic way to download something from a URL. Construct it with a NSURLRequest and a delegate. It will call back on a success or failure. The implementation is only slightly longer.

#import "CIMGFSimpleDownloadOperation.h"

@interface CIMGFSimpleDownloadOperation()

@property (nonatomic, retain) NSURLRequest *request;
@property (nonatomic, retain) NSMutableData *data;
@property (nonatomic, assign) id<CIMGFSimpleDownloadDelegate> delegate;

@end

@implementation CIMGFSimpleDownloadOperation

- (id)initWithURLRequest:(NSURLRequest*)request andDelegate:(id<CIMGFSimpleDownloadDelegate>)delegate
{
  if (!(self = [super init])) return nil;

  [self setDelegate:delegate];
  [self setRequest:request];

  return self;
}

- (void)dealloc
{
  [self setDelegate:nil];
  [self setRequest:nil];
  [self setData:nil];

  [super dealloc];
}

- (void)main
{
  [NSURLConnection connectionWithRequest:[self request] delegate:self];
  CFRunLoopRun();
}

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSHTTPURLResponse*)resp
{
  [self setStatusCode:[resp statusCode]];
  [self setData:[NSMutableData data]];
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)newData
{
  [[self data] appendData:newData];
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
  [[self delegate] operation:self didCompleteWithData:[self data]];
  CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
  [[self delegate] operation:self didFailWithError:error];
  CFRunLoopStop(CFRunLoopGetCurrent());
}

@synthesize delegate;
@synthesize request;
@synthesize data;
@synthesize statusCode;

@end

Now this class is VERY reusable. There are other delegate methods for NSURLConnection that you can add depending on your needs. NSURLConnection can handle redirects, authentication, etc. I strongly suggest you look into its documentation.

From here you can either spin off the CIMGFSimpleDownloadOperation from your UITableViewController or from another part of your application. For this demonstration we will do it in the UITableViewController. Depending on your application needs you can kick off the data download wherever makes sense. For this example we will kick it off when the view appears.

- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];

  NSURLRequest *request = ...;
  CIMGFSimpleDownloadOperation *op = [[CIMGFSimpleDownloadOperation alloc] initWithURLRequest:request andDelegate:self];
  [[NSOperationQueue mainQueue] addOperation:op];
  [self setDownloadOperation:op]; //Hold onto a reference in case we want to cancel it
  [op release], op = nil;
}

Now when the view appears an async call will go and download the content of the URL. In this code that will either pass or fail. The failure first:

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didFailWithError:(NSError*)error;
{
  [self setDownloadOperation:nil];
  NSLog(@"Failure to download: %@
%@", [error localizedDescription], [error userInfo]);
}

On success we need to parse the data that came back.

- (void)operation:(CIMGFSimpleDownloadOperation*)operation didCompleteWithData:(NSData*)data;
{
  [self setDownloadOperation:nil];
  NSLog(@"Download complete");

  //1. Massage the data into whatever we want, Core Data, an array, whatever
  //2. Update the UITableViewDataSource with the new data

  //Note: We MIGHT be on a background thread here.
  if ([NSThread isMainThread]) {
    [[self tableView] reloadData];
  } else {
    dispatch_sync(dispatch_get_main_queue(), ^{
      [[self tableView] reloadData];
    });
  }
}

And done. A few more lines of code for you to write but it replaces 13K+ lines of code that gets imported with ASI resulting in a smaller, leaner, faster application. And more importantly it is an app that you understand every single line of code.

这篇关于Objective-C:异步填充 UITableView - 如何做到这一点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆