NSURLConnection不会跨多个视图调用完成

今天早些时候,我问了以下问题: 推送视图时,iOS块被阻止

我提到的操作(OP1)实际上是使用NSURLConnection到我的服务器的“http get”。
经过更多调查,我发现这个街区并没有真正的“死亡”。 真正发生的是,即使在推送视图(通过[NSThread sleep:10]validation)之后,请求实际上是SENT(服务器端logging它)。 服务器响应,但没有任何事情发生在应用程序端,如果view2已被推动! 就好像连接失去了代表一样! 我看到的另一个可能性是“NSURLConnection与rsMainLoop相关的事实?

谁能帮忙?

请不要忘记:
0.只要视图2没有被推到操作完成,一切都可以正常工作。
1.请求被发送asynchronous
2.我设置委托,只要视图不改变,它就会工作
3.视图1使用“单独对象引用”属性“OP1Completed”开始操作
4.视图2通过“单体对象引用”上的属性检查OP1的完成情况,
5.视图2通过转到“singleton.OP1Result”属性获取“结果”

编辑1:
好吧,让我们有一些代码。 首先这里是我的单身人士(名为“交互”)的相关代码:

-(void)loadAllContextsForUser:(NSString *)username{ userNameAux = username; _loadingContextsCompleted = NO; if (contextsLoaderQueue == NULL) { contextsLoaderQueue = dispatch_queue_create("contextsLoaderQueue", NULL); } dispatch_async(contextsLoaderQueue, ^{ NSLog(@"Loading all contexts block started"); [self requestConnectivity]; dispatch_async(dispatch_get_main_queue(), ^{ [Util Get:[NSString stringWithFormat:@"%@/userContext?username=%@", Util.azureBaseUrl, [username stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] successBlock:^(NSData *data, id jsonData){ NSLog(@"Loading all contexts block succeeded"); if([userNameAux isEqualToString:username]){ _allContextsForCurrentUser = [[NSSet alloc]initWithArray: jsonData]; } } errorBlock:^(NSError *error){ NSLog(@"%@",error); } completeBlock:^{ NSLog(@"load all contexts for user async block completed."); _loadingContextsCompleted = YES; [self releaseConnectivity]; }]; }); while (!_loadingContextsCompleted) { NSLog(@"loading all contexts block waiting."); [NSThread sleepForTimeInterval:.5]; } }); NSLog(@"Load All Contexts Dispatched. It should start at any moment if it not already."); } 

这里是Util类,它实际上处理请求/响应

 -(id)initGet:(NSString *)resourceURL successBlock:(successBlock_t)successBlock errorBlock:(errorBlock_t)errorBlock completeBlock:(completeBlock_t)completeBlock;{ if(self=[super init]){ _data=[[NSMutableData alloc]init]; } _successBlock = [successBlock copy]; _completeBlock = [completeBlock copy]; _errorBlock = [errorBlock copy]; NSURL *url = [NSURL URLWithString:resourceURL]; NSMutableURLRequest *request = [NSURLRequest requestWithURL:url]; [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; //[_conn scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //[_conn start]; NSLog(@"Request Started."); return self; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [_data setLength:0]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [_data appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { id jsonObjects = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableContainers error:nil]; id key = [[jsonObjects allKeys] objectAtIndex:0]; id jsonResult = [jsonObjects objectForKey:key]; _successBlock(_data, jsonResult); _completeBlock(); } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { _errorBlock(error); _completeBlock(); } 

最后这里是相关部分VC1(推入VC2)

 - (IBAction)loginClicked { NSLog(@"login clicked. Preparing to exibit next view"); UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil]; AuthenticationViewController *viewController = (AuthenticationViewController *)[storyboard instantiateViewControllerWithIdentifier:@"ContextSelectionView"]; NSLog(@"Preparation completed. pushing view now"); [self presentViewController:viewController animated:YES completion:nil]; } 

您可能会感到惊讶,但有几个解决scheme – 其中一些是非常普遍的,可以很容易地实现;)即使这个答案是荒谬的详细说明 ,你的问题的实际解决scheme将不会超过几行代码。 🙂

你遇到了一个典型的“asynchronous问题” – 好吧,这不是一个问题,而是一个典型的编程任务。

你有什么是一个asynchronous任务,OP1。 这将从ViewController 1(VC1)开始,并且在稍后的某个不确定的时间,它最终会产生一个结果或一个错误。

OP1的最终结果应该在VC2中稍后处理。

客户端如何获得最终结果有几种方法,例如:通过KVO,委托方法,完成块,callback函数,将来或承诺和每个通知。

上面的这些方法有一个共同的特性:呼叫站点被asynchronous结果提供者通知(反之亦然)。

轮询结果直到可用,是一个不好的方法。 Likewse悬挂在信号量中,并阻塞当前线程,直到结果“发信号”为止,同样是不理想的。

您可能熟悉完成块。 当结果可用时通知呼叫站点的典型asynchronous方法如下所示:

 typedef void (^completion_block_t)(id result); - (void) doSomethingAsyncWithCompletion:(completion_block_t)completionHandler; 

注意: 调用站点提供了完成处理程序,而asynchronous任务在完成时调用该块,并将结果(或错误)传递给块的结果参数。 除非另有说明,否则块的执行上下文 (即线程或调度队列或NSOperationQueue)将不会被执行。

但是当考虑到你的问题时,一个简单的asynchronous函数和一个完成处理程序不会产生一个可行的解决scheme。 你不能将这个“方法”从VC1轻松地传递给VC2,然后稍后“附加”VC2中的完成块。

幸运的是,任何asynchronous任务都可以封装到NSOperationNSOperation有一个完成块,可以通过调用站点或其他地方设置属性。 而一个NSOperation对象可以很容易地从VC1传递给VC2。 VC2只是将一个完成块添加到操作中,最终在完成时得到通知,并且结果可用。

但是,虽然这对您的问题是一个可行的解决scheme,但实际上这种方法存在一些问题 – 我不想详细说明,而是提出一个更好的scheme:“承诺”。

“Promise”表示asynchronous任务的最终结果 。 也就是说,即使asynchronous任务的结果尚未评估,承诺也会存在。 Promise是一个可以发送消息的普通对象。 因此,承诺可以像NSOperations一样传递。 Promise是asynchronous方法/函数的返回值

 -(Promise*) doSomethingAsync; 

不要将Promise与asynchronous函数/方法/任务/操作不匹配 – promise只是任务结果的表示。

一个Promise务必最终由asynchronous任务来解决 ,也就是说,任务必须发送一个“履行”消息以及结果值的承诺,或者必须发送承诺“拒绝”消息以及一个错误。 承诺保留从任务传递的结果值的引用。

一个承诺只能解决一次!

为了获得最终结果 ,客户可以“注册” 成功处理程序error handling程序 。 成功处理程序将在任务满足承诺(即成功)时被调用,并且当任务拒绝作为错误对象传递原因的承诺时,将调用error handling程序。

假设承诺的特定实现,解决承诺可能如下所示:

 - (Promise*) task { Promise* promise = [Promise new]; dispatch_async(private_queue, ^{ ... if (success) { [promise fulfillWithValue:result]; } else { NSError* error = ...; [promise rejectWithReason:error]; } }); return promise; } 

客户“注册”处理程序以获得最终结果如下:

 Promise* promise = [self fetchUsers]; promise.then( <success handler block>, <error handler block> ); 

成功处理程序和error handling程序块声明如下:

 typedef id (^success_handler_block)(id result); typedef id (^error_handler_block)(NSError* error); 

为了“注册” 成功处理程序 (对于这种情况,asynchronous任务“成功返回”),可以这样写:

 promise.then(^id(id users) { NSLog(@"Users:", users); return nil; }, nil); 

如果任务成功,处理程序将被调用 – 将用户打印到控制台。 当任务失败时,成功处理程序将不会被调用。

为了“注册” error handling程序 (对于这种情况,asynchronous任务失败),可以这样写:

 promise.then(nil, ^id(NSError* error) { NSLog(@"ERROR:", error); return nil; }, nil); 

如果任务成功,error handling程序将不会被调用。 只有当任务失败(或任何子任务)时,才会调用此error handling程序。

当asynchronous任务的结果最终可用时,处理程序内的代码将被执行“在某些未指定的执行上下文中”。 这意味着,它可以在任何线程上执行。 (注意:有些方法可以指定执行上下文,比如说主线程)。

承诺可以注册多个处理器对。 您可以根据需要添加尽可能多的处理程序,以及您想要的地点时间 。 现在,您应该了解与您的实际问题的联系:

您可以在VC1中启动一个asynchronous任务,并获得承诺。 然后把这个承诺传给VC2。 在VC2中,您可以添加您的处理程序,当结果最终可用时,该处理程序将被调用。

当将承诺传递给VC2时,也就是在承诺已经解决时,不要担心结果是否已经可用。 你仍然可以添加处理程序,并立即被正确解雇。

你也可以“连锁”多个任务 – 也就是说,当task1完成时调用task2一次。 四个asynchronous任务的“链”或“延续”如下所示:

 Promise* task4Promise = [self task1] .then(^id(id result1){ return [task2WithInput:result1]; }, nil) .then(^id(id result2){ return [task3WithInput:result2]; }, nil) .then(^id(id result3){ return [task4WithInput:result3]; }, nil); 

task4Promise表示task4WithInput的最终结果task4WithInput:

也可以并行执行任务,如taskB和taskC,taskA成功完成后并行启动:

 Promise* root = [self taskA]; root.then(^id(id result){ return [self taskB]; }, nil); root.then(^id(id result){ return [self taskC]; }, nil); 

通过这个scheme,可以定义一个任务的非循环图,每个图都依赖于其后继者(“父”)的成功执行。 “错误”将传递到根,并由最后一个error handling程序(如果有)处理。

Objective-C有几个实现。 我自己写了一个:“RXPromise”(在GitHub上可用)。 其中最强大的function之一是“取消” – 这不是承诺的标准function,而是在RXPromise中实现。 有了这个,你可以有select地取消一个asynchronous任务树。

承诺还有很多。 你可以search网页,特别是在JavaScript社区。

我不确定我是否理解第一个控制器中的工作stream程 – 具体地说,用户做了什么来启动下载,以及在下一个控制器出现之前(以及控制器何时实例化)他还做了什么。 当我在过去需要从多个类下载应用程序时,我创build了一个创buildNSURLConnection的下载类,并实现了所有的callback。 它有一个委托协议方法将数据(原始数据或错误对象)发送回其委托。

我做了一个简单的testing用例,用两个button来模拟我的工作stream程。 一个实例化一个Downloader类实例,创build下一个控制器,将其设置为下载器的代理,并开始下载。 第二个button是对第二个控制器的推送。 这是有效的,不pipe推送什么时候发生,但是我不知道是否与你的情况有关(我使用Network Link Conditioner来模拟慢速连接)。

第一个控制器:

 #import "ViewController.h" #import "ReceivingViewController.h" #import "Downloader.h" @interface ViewController () @property (strong,nonatomic) ReceivingViewController *receiver; @end @implementation ViewController -(IBAction)buttonClicked:(id)sender { Downloader *loader = [Downloader new]; self.receiver = [self.storyboard instantiateViewControllerWithIdentifier:@"Receiver"]; loader.delegate = self.receiver; [loader startLoad]; } -(IBAction)goToReceiver:(id)sender { [self.navigationController pushViewController:self.receiver animated:YES]; } 

下载类.h:

 @protocol DownloadCompleted <NSObject> -(void)downloadedFinished:(id) dataOrError; @end @interface Downloader : NSObject @property (strong,nonatomic) NSMutableData *receivedData; @property (weak,nonatomic) id <DownloadCompleted> delegate; -(void)startLoad; 

Downloader .m:

 -(void)startLoad { NSLog(@"start"); NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10]; NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self]; if (connection) self.receivedData = [NSMutableData new]; } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { self.receivedData.length = 0; } -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.receivedData appendData:data]; } -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self.delegate downloadedFinished:error]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { [self.delegate downloadedFinished:self.receivedData]; } -(void)dealloc { NSLog(@"In Downloader dealloc. loader is: %@",self); } 

第二个控制器:

 @interface ReceivingViewController () @property (strong,nonatomic) NSData *theData; @end @implementation ReceivingViewController -(void)downloadedFinished:(id)dataOrError { self.theData = (NSData *)dataOrError; NSLog(@"%@",self.theData); } -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"%@",self.theData); } 

所以,这是我认为可以肯定的工作:

将标志传递给新的控制器。 如果标志未完成,则重新加载新的VC,并确保没有数据显示出来,直到完成加载。

我认为这是奇怪的线程停止,虽然新的VC被推,因为当我用AFNetworking派遣asynchronous调用,它甚至在一个新的VC被推后仍然继续加载。 也许如果你使用不同的框架,你应该使用AFNetworking。

所以,如果你的线程实际上在新的VC被推后继续(因为我怀疑它确实 – 你只是认为它不会继续,因为它会崩溃的代码),然后尝试以下操作:

a)通过标志,如果操作完成,正常进行
b)如果没有,请不要加载任何东西,并在两者之间调用某种委托方法来检查标志是否设置,如果是,则返回数据。

如果您对如何设置代表有任何疑问,只需询问我可以填写一些细节。

正如你在第一个问题中已经提到的那样:你可能有两个问题:

  1. 一个devise问题
  2. 代码问题,导致块。 (但没有代码,这是很难弄清楚)。

让我们提出一个实用的方法:

说,我们的单身人士是一些执行HTTP请求的“Loader”类。 而不是轮询决定networking请求状态的属性,你应该返回一些你可以要求的状态的对象,甚至更好的地方是VC2可以注册一个完成块 ,在请求完成被调用。

NSOperation可以被“使用”来表示asynchronousnetworking请求的最终结果。 但是这有点难以处理 – 假设我们有一个子类RequestOperation:

 RequestOperation* requestOp = [[Loader sharedLoader] fetchWithURL:url]; 

现在,“requestOp”代表您的networking请求,包括最终的结果。

你可以在VC1中获得这个操作。

您可能不想问共享加载器有关特定的操作,因为它可能是无状态的 – 也就是说,它本身不跟踪请求操作。 考虑一下,你想多次使用类Loader来启动networking请求 – 可能是并行的。 那么,当你询问Loader 一个属性告诉你一些关于请求状态的信息时,你的意思是什么? (它不会工作)。

所以,再次回到一个工作的方法和VC1:

假设在VC1中获得了NSOperation的子类RequestOperation对象。 假设RequestOperation有一个属性responseBody – 它是一个NSData对象,表示请求操作的最终响应数据。

为了获得请求的最终响应主体,你不能仅仅询问属性:连接可能仍然在运行 – 你将得到nil或者垃圾,否则你可能会阻塞这个线程。 行为取决于RequestOperation的实现。

解决scheme如下:

在VC2:

我们假设,VC1已经将请求传递给了VC2(例如在prepareForSegue:sender: )。

为了以asynchronous正确的方式检索响应正文,您需要一些额外的步骤:

创build一个NSBlockOperation ,它执行一个处理响应主体的块,例如:

 NSBlockOperation* handlerOp = [NSBlockOperation blockOperationWithBlock:^{ NSData* body = requestOp.responseBody; dispatch_async(dispatch_get_main_queue(), ^{ self.model = body; [self.tableView reloadData]; }); }]; 

然后,使handlerOp依赖于requestOp – 也就是说,当requestOp完成时,开始执行handlerOp

 [handlerOP addDependency:requestOp]; 

handlerOp添加到队列中,以执行:

 [[NSOperation mainQueue] addOperation:handlerOp]; 

这仍然需要你“asynchronous”思考 – 这是没有办法的。 最好的是,习惯实用的模式和习语。


另一种方法是使用RXPromise(来自第三方库):

在VC1:

 requestPromise = [Loader fetchWithURL:url]; 

现在,在VC2:

我们假设,VC1已经将请求提交“通过”给了VC2(例如在prepareForSegue:sender: )。

例如在viewDidLoad

 requestPromise.thenOn(dispatch_get_main_queue(), ^id(id responseBody){ // executes on main thread! self.model = responseBody; [self.tableView reloadData]; return nil; }, nil); 

奖金:

如果需要的话,可以通过发送cancel来随时取消networking请求:

 - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.requestPromise cancel]; self.requestPromise = nil; } 

我已经知道了。 在我的第二个视图(我为操作完成的w8)我不能w8使用ThreadSleep! 我必须使用[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];