我们开发App时,常常需要异步下载网络资源或者实现REST API调用,目前流行的HTTP库有ASIHTTPRequest(已经停止开发维护)和AFNetWorking。两者实现异步网络请求的方式不太相同,ASIHTTPRequest使用的是NSOperation+CFNetWork API实现异步网络请求,但是在一个公共独立子线程上去执行网络请求:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
AFNetWorking则是包装了NSOperation和NSURLConnection技术实现异步网络请求,它在NSOperation中真正启动NSURLConnection网络请求时,同样生成了一个公共独立子线程来Kick off网络请求:
1 2 3 4 5 6 7 8 9 10 |
|
ASIHTTPRequest和AFNetWorking实际上都是使用一个公共独立子线程配合Run Loop来实现管理网络请求 。由于我在开发中需要一个简单的网络图片下载功能,直接使用第三方库需要添加太多文件,就写了一个类似Sample Code中PTNormalDownloaler下载类,当时遇到个tricky的问题。
首先,如果是直接调用NSURLConnection的initWithRequest:delegate:startImmediately:(第三个参数用YES,这个是designated initializer)或者方法initWithRequest:delegate:时,NSURLConnection会默认运行在NSDefaultRunLoopMode模式下,即使再使用scheduleInRunLoop:forMode:设置运行模式也没有用。如果NSURLConnection运行在NSDefaultRunLoopMode下,何为Run Loop的模式Mode,请参考这篇Blog), 这篇Blog提到NSDefaultRunLoopMode是Run Loop默认的运行模式,用于处理除了NSConnection对象的事件。 然而如果NSURLConnection是运行在NSDefaultRunLoopMode,而当前线程是主线程,并且UI上有类似滚动这样的操作,那么主线程的Run Loop会运行在UITrackingRunLoopMode下,就无法响应NSURLConnnection的回调。此时需要首先使用initWithRequest:delegate:startImmediately:(第三个参数为NO)生成NSURLConnection,再重新设置NSURLConnection的运行模式为NSRunLoopCommonModes,那么UI操作和回调的执行都将是非阻塞的,因为NSRunLoopCommonModes是一组run loop mode的集合,默认情况下包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
其次在调用PTNormalDownloaler的start方法时,如果是用GCD在其他线程中开始运行:
1 2 3 |
|
而非在主线程中运行:
1 2 3 |
|
你在GCD的全局队列里运行的PTNormalDownloaler中的不会得到NSURLConnection回调,而从主线程中启动NSURLConnection可以得到回调,这是由于在GCD全局队列中执行时没有运行Run Loop,那么NSURLConnection也就无法触发回调了。
当然在GCD的全局队列里启动NSURLConnection需要这样这样:
1 2 3 4 5 6 |
|
如果从主线程启动NSURLConntection,其回调会在主线程中被调用:
1
|
|
如果是在子线程启动NSURLConntection,其回调则会在子线程中被调用:
1
|
|
在主线程启动NSURLConnection会不会影响主线程的UI?影响肯定会有,但是网络IO本身不会影响,那是底层操作系统的事情,仅仅是网络IO结束后由主线程来处理回调而已。
样例程序里也实现了一个基于子线程的下载器PTThreadDownloader,与ASIHTTPRequest和AFNetWorking中的方法类似,生成一个公共子线程来启动NSURLConnection。这里没有必要对每个网络请求都生成一个后台子线程去启动NSURLConnection(例如上面那种用GCD扔到global queue的方式最终也是每次从线程池找到一个线程来启动NSURLConnection),因为底层网络IO并不是在这个子线程里去执行的,子线程仅仅用于响应NSURLConnection回调。不过公共子线程的方法会导致有一个子线程一直运行在后台,等待用户用它来启动NSURLConnection。
本文例子放在Github上(工程NSURLConnectionExample),可以根据文中的几种情况测试initWithRequest:delegate:startImmediately:第三个参数的影响以及回调问题,例子UI中的按钮每点击一次会产生一个下载PTNormalDownloaler对象并开始执行,如果这个NSURLConnection是运行在NSDefaultRunLoopMode模式下,那么上下滚动UI中的Table View,是不会触发NSURLConnection回调的,只有UI操作结束后才会触发。