0

我设置了一个 UITableView,它应该在从网站加载某些数据后显示一些信息。为了更新 UI 并完成加载数据,我使用了以下方法:

performSelectorOnMainThread:@selector(getBooks) withObject:nil waitUntilDone:YES  

-(void)getBooks{

NSMutableString *theURL = [[NSMutableString alloc] initWithString:@"theURL"];

[theURL appendFormat:@"&UserID=%@", _appDel.userID];

NSLog(@"%@\n", theURL);
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:theURL]
                                            cachePolicy:NSURLRequestUseProtocolCachePolicy
                                        timeoutInterval:10.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
    receivedData = [NSMutableData data];
} else {
    // Inform the user that the connection failed.
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"An Error Occured" message:@"Couldn't make a connection to the server. Please make sure your internet connection is on." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles: nil];
    [alert show];
}

但是,当我运行此程序时,尽管我将 waitUntilDone 设置为 YES,但程序仍会继续执行。我的印象是 waitUntilDone 会等待选择器,直到它完成执行。谁能解释可能的问题是什么?谢谢你的帮助。

4

1 回答 1

2

如果您查看Apple 文档,他们会说:

等待

一个布尔值,指定当前线程是否阻塞,直到在主线程上的接收器上执行指定的选择器之后。指定 YES 来阻塞这个线程;否则,指定 NO 使该方法立即返回。如果当前线程也是主线程,并且您为此参数指定 YES,则消息将立即传递和处理。

因此,如果您从主线程调用此方法,那么它将立即执行。

但是,这样做真的没有任何意义。如果您在主线程上,并希望在getBooks主线程上执行,那么只需执行以下操作:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self getBooks];
}

如果您向我们展示您在内部的实际getBooks操作,我们可能会提供更多帮助。例如,如果getBooks正在发出远程 HTTP 请求,以获取在线图书数据,那么您根本不想使用performSelectorOnMainThread:。你想在后台线程上运行它,然后只在网络请求完成后回调主线程来更新你的 UI。


更新:

有很多方法可以检索 Web 内容。如果你打算NSURLRequest直接使用,那么你应该确保这个类实现了NSURLConnectionDelegate协议:

@interface MyViewController: UIViewController<NSURLConnectionDelegate> {

然后按照 Apple 示例实现其方法

- (void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
    // this method is called when the server has determined that it
    // has enough information to create the NSURLResponse

    // it can be called multiple times, for example in the case of a
    // redirect, so each time we reset the data.
    [receivedData setLength:0];
}

- (void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)data
{
    // append the new data to the receivedData
    [receivedData appendData:data];
}

- (void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
    // release the connection, and the data object
    [connection release];
    [receivedData release];

    // inform the user
    UIAlertView* netAlert = [[UIAlertView alloc] initWithTitle:@"" message:@"Oops!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [netAlert show];
    [netAlert release];
}

- (void) connectionDidFinishLoading:(NSURLConnection *) connection
{   
    if ([receivedData length] > 0) {
        // do something with the data, and then tell the UI to update
        [self performSelectorOnMainThread: @selector(updateUI) withObject: nil waitUntilDone: NO];
    }

    // release the connection, and the data object
    [connection release];
    [receivedData release];
    receivedData = nil;
}

-(void) updateUI {
    [tableView reloadData];
    someLabel.text = @"New Status";
    // whatever else needs updating
}

注意:上面的代码是在引入 ARC 之前编写的。如果您的项目使用 ARC,则需要删除所有带有release调用的行。但是,建议确保您receivedData的正确保留,因为您将在多个呼叫中使用它。

另一个注意事项:NSURLConnection可以多种方式使用。它可以同步使用,也可以异步使用。而且,您始终可以决定在哪个线程上启动它。如果你保持这个:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self getBooks];
}

然后连接将在主线程上启动。但是,它仍然是一个异步操作。换句话说,viewDidLoad在网络请求完成之前它不会阻塞。但是,如果用户在下载发生时对 UI 做任何重要的事情(例如滚动),那么您可能会发现您的 UI 变得不那么响应。如果是这种情况,您可能想要这样做,或者将网络操作强制到后台线程上。为此,请从以下内容开始:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self performSelectorInBackground: @selector(getBooksInBackground) withObject: nil];
}

然后,我上面的NSURLConnectionDelegate代码的唯一问题是后台线程的寿命不够长,无法通过委托回调传递响应。为了让它活着,

-(void) getBooksInBackground {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [self getBooks];

    CFRunLoopRun(); // Avoid thread exiting
    [pool release];
}

然后您必须将此调用添加到两者的末尾connectionDidFinishLoadingconnection:didFailWithError清理此后台运行循环:

    CFRunLoopStop(CFRunLoopGetCurrent());

再次,旧代码,所以使用

    @autoreleasepool {
    }

对于弧。

于 2012-07-31T08:54:11.193 回答