IOS如何等待`didReceiveData`处理程序被调用

时间:2012-11-10 18:24:09

标签: ios multithreading

我正在开发从Web服务器获取数据的ios应用程序。我希望其他所有内容都等待,直到调用并完成此类的一个处理程序。我知道通过使用调度/线程是可能的,但我无法弄清楚如何。

-(void)callWebService:(NSString*)URL:(NSString*)SOAP{
     NSURL *url = [NSURL URLWithString:URL];
     NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];

     [req setHTTPMethod:@"POST"];
     [req setHTTPBody:[SOAP dataUsingEncoding:NSUTF8StringEncoding]];


     NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:req delegate:self];
     if(con){
         [con start];
     }
}

并在此方法结束时继续此类之外的代码。但我想等到这个处理程序被调用(并完成):

-(void)connection:(NSURLConnection *)c didReceiveData:(NSData *)data{
    NSString *res = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",res);
    Ukol_Parser *parser = [Ukol_Parser alloc];
    [parser parseUkol:res];
}

因为这里的解析器将数据放入sqlite db并且正在读取此类数据之外。但是"外面"代码正在执行得比我得到响应更快,并且处理程序被调用....

5 个答案:

答案 0 :(得分:2)

如果你想要“等待其他一切”,那么听起来你真正想做的就是同步请求。

查看[NSURLConnection sendSynchronousRequest:returningResponse:error:]

但是,请确保在后台线程上执行此操作,因为如果您在主线程上执行此操作,您的UI将会阻止,您的应用将看起来对用户触摸或其他任何内容都没有响应。

答案 1 :(得分:1)

我很紧张您从后台队列接受了关于sendSynchronousRequest的答案,因为从实际角度来看,这与基于didReceiveData的实施没有什么不同。具体来说,如果您确实从后台队列执行同步请求,它将使“其他所有等待”。 但是如果你忽略了从后台队列执行此同步请求(即如果你是从主线程执行),那么最终会出现一个可怕的用户体验(应用程序被冻结,用户想知道应用程序是否已崩溃),以及更糟糕的是,如果需要太长时间,你的应用程序可能被iOS“监视程序”杀死。

完全遵循各种答案,在后台队列上发送同步请求与现有的基于NSURLConnectionDataDelegate的方法无法区分。你真正需要做的是接受应用程序的其余部分不会冻结的事实,因此只需更新UI以让用户知道发生了什么,即(a)提供应用程序未死的视觉提示; (b)阻止用户与您现有的UI进行交互。

例如,在发出网络请求之前,添加一个视图,该视图将覆盖/调暗UI的其余部分,阻止用户与现有UI交互,并添加微调器以让用户知道应用正在忙着做某事。因此,为UIView定义一个类属性,该属性将使UI的其余部分变暗:

@property (nonatomic, strong) UIView *dimView;

然后,在网络请求之前,更新UI:

// add a view that dims the rest of the UI, so the user has some visual cue that they can't use the UI yet
// by covering the whole UI, you're effectively locking the user out of using the app
// until the network request is done

self.dimView = [[UIView alloc] initWithFrame:self.view.bounds];
self.dimView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
self.dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.dimView];

// add a spinner that shows the user that something is really happening

UIActivityIndicatorView *indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicatorView.center = self.dimView.center;
indicatorView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
[indicatorView startAnimating];
[self.dimView addSubview:indicatorView];

[self callWebService:URL withSOAP:SOAP];

然后,在connectionDidFinishLoading方法中(或者如果您在后台队列中使用sendSynchronousRequest,在后一部分代码中调度到该后台队列),在完成数据解析后,你想:

[self.dimView removeFromSuperview];
self.dimView = nil;

// now do what ever to need do to update your UI, e.g.:
//
// [self.tableView reloadData] 

但关键是你绝对不想从主队列发出同步网络请求。您应该(a)异步执行网络请求(在后台队列上同步,或者在最初实现时),以便iOS监视程序进程不会终止您的应用程序; (b)让用户知道应用正在发出一些网络请求并且没有冻结它们(例如UIActivityIndicatorView;以及(c)请求完成后,删除这些“应用正在进行网络请求” UI元素,并在网络请求完成后刷新UI的其余部分。

最后,在测试您的应用时,请确保在真实的网络环境中进行测试。我建议您安装硬件IO工具(可在Xcode菜单中,在“Open Developer Tools” - “More Developer Tools”下找到)并查看网络链接调节器。这使您可以在iOS模拟器上模拟真实的网络情况(例如,糟糕的3G或Edge网络状况)。当我们在具有理想网络连接的典型开发环境中测试我们的应用程序时,我们会陷入虚假的性能感。 “野外”设备遭受各种恶化的网络环境,最好在类似的,次优的网络环境中测试您的应用。

答案 2 :(得分:0)

有点疯狂的解决方案,但这实际上有效https://gist.github.com/62940:D

答案 3 :(得分:0)

使用同步通话。但它改变了你的类的设计,因为同步调用将阻止并让应用程序挂起

答案 4 :(得分:0)

从你的didReceiveData:方法发布通知,并让你的其他类观察该通知(或者你可以使用委托设置,如果很容易从另一个类获得对这个类的引用,那么你可以将你的其他类设置为这一个的代表)。在通知的选择器方法中,开始执行其余代码。