iOS NSURLConnection 和 dispatch_async 错误的使用方法,导致回调方法无法调用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlStr] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:CONNECTIONT_TIMEOUT]; NSString *encodedStr = [body stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSData *sendData = [encodedStr dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; NSString *dataLengthStr =[NSString stringWithFormat:@"%lu",(unsigned long)[sendData length]]; [request setValue:dataLengthStr forHTTPHeaderField:@"Content-Length"]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:sendData]; self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; });
这是一个在主线程中执行的代码,执行结果有问题,无法执行网络回调函数。
原因如下:对于connectionWithRequest这个函数在官方文档下有以下说明:
Delegate methods are called on the same thread that called this method.
这就是说,delegate的消息会发送到调用connectionWithRequest方法的线程中去。如果线程结束了,比如这里就是这样,那么,系统就不会调用相应方法,即使delegate对象本身是存在的!
改正方法很简单,有2种
1.把网络请求发送代码放到主线程,就是不使用dispatch_async
2.在dispatch_async 块的结尾处调用
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[loop run];
当connection完成回调后,会把自己(source)从runloop中移除,这样这个runloop中就没有任何source和timer了,这个runloop会自动停止,相关线程自动结束。
下面是关于run方法的说明,可见,如果没有input source 和timer 加入nsrunloop,这个run方法会立即返回的(这个经过了测试,也十分重要!)。总结下就是,在一个runloop中还有source时,如果调用了run,那么当runloop的source都没了时,这个run方法会自动返回!
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
再看看runloop的说明
The NSRunLoop class declares the programmatic interface to objects that manage input sources. An NSRunLoop object processes input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.
这里说明,nsrunloop主要处理2种消息,一种是input source 一种是timer(注意,nsobject中的performSelector的一系列函数就是利用timer放入到runloop中的),而input source分为4种,分别是鼠标消息,键盘消息,port消息(不太明白),nsconnection (不仅仅是网络连接)消息。
另外还需要注意 You should never try to call the methods of an NSRunLoop
object running in a different thread, as doing so might cause unexpected results. 也就是说,不要在其他线程中通过调用 runloop对象让runloop停止!必须放到自己的线程中做,这个错误很容易犯。
这个错误提醒我,在使用dispatch_async时,如果没有特殊操作,相关线程会在block结束后立即退出,仅仅靠系统自动加入的input source(比如这里的nsurlconnection),是无法使线程继续存活的,因为加入input source或timer 后还需要我们启动 runloop才行。除了主线程,系统不会为你自动启动runloop的。