NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决

1.NSRunloop

NSRunloop顾名思义,就是一个消息循环,它会侦测输入源(input source)和定时源(timer source),然后做回调处理。这和windows的消息处理非常类似,只不过你无法看到类似SendMessage,PostMessage,GetMessage的方法,NSRunloop已经封装了这些细节。那NSRunloop的好处是不是只有封装细节,然后方便调用呢?答案是否定的。看apple官方文档(多线程编程指南)描述: "run loop 是用来在线程上管理事件异步到达的基础设施......run loop在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗CPU周期轮询,并防止处理器本身进入休眠状态并节省电源。" 看见没,消除CPU空转才是它最大的用处。

所以NSRunloop的重点就是:

1.run loop 用来监听长耗时的异步事件,如果用不到异步事件,就不用扯这个东西了(给你的面试官这么说吧)。例如,网络回调,无论是apple提供的NSURL还是开源的ASIHttpRequest,都是用NSRunloop来监听网络事件(TCP/IP的堆栈)。(这个人的经历可以参考 http://www.blogjava.net/writegull/archive/2012/07/25/383926.html)

2.run loop解决了CPU空转。


工作原理图如下

NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决_第1张图片


图 3-1显示了run loop的概念结构以及各种源。输入源传递异步消息给相应的处理例程,并调用runUntilDate:方法来退出(在线程里面相关的NSRunLoop对象调用)。定时源则直接传递消息给处理例程,但并不会退出run loop。 

从上图中我们可以看出,每个线程都有一个默认的NSRunloop。主线程的NSRunloop默认是运行的。非主线程的NSRunloop默认是没有运行的,需要为NSRunloop添加一个事件,然后去run,一般情况下没有必要启用线程的runloop,除非需要长久地监测某个异步事件。

拿具体的应用举个例子,NSURLConnection网络数据请求,默认是异步的方式,其实现原理就是创建之后将其作为事件源加入到当前的 RunLoop,而等待网络响应以及网络数据接受的过程则在一个新创建的独立的线程中完成,当这个线程处理到某个阶段的时候比如得到对方的响应或者接受完了网络数据之后便通知之前的线程去执行其相关的delegate方法。所以在Cocoa中经常看到scheduleInRunLoop:forMode: 这样的方法,这个便是将其加入到事件源中,当检测到某个事件发生的时候,相关的delegate方法便被调用。


2.runloop和 autorelease pool

先提出一个问题,在Iphone项目中,大家会看到一个默认的Autorelease pool,程序开始时创建,程序退出时销毁,按照对Autorelease的理解,岂不是所有autorelease pool里的对象在程序退出时才release, 这样跟内存泄露有什么区别?答案是,对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。

那什么是一个runloop?一个UI事件,一个timer,一个系统delegate都称之为runloop(不是NSRunloop),runloop实际上是从接收消息,然后处理完消息的一个完整过程。

为了更加形象说明auto release pool机制,下面举例:

NSString* str1是assign。

UI事件:UIButton的target-action机制,在action中创建一个autorelease的UILabel对象,并赋值,在action中打印出值,action执行完毕,这个时候runloop结束,autorelease pool被释放,label也被释放,所以再调用这个对象的值时,出现bad_exec_access。

NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决_第2张图片NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决_第3张图片

NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决_第4张图片NSRunloop,runloop,autoReleasePool和thread的关系理解及案例解决_第5张图片


3.autorelease pool和thread

多说一句,只有以上提到了3种runloop才会自动创建autorelease pool,thread是不会自动创建的,所以我们可以看到子线程中会有手动写的autorelease pool代码。这点以前搞混过,切记!

iphone线程中使用异步网络的悲催经历

就个人经验而言,在iphone线程中使用异步NSURLConnection的经验可以说是一个完全和愉悦搭不上边的事情。他给我带来的麻烦可真不少。例如,前几天,帮客户定位一个问题的时候发生的事情。

事情经过是这样的:客户反馈,无法正常使用我们提供的某个和网络相关的功能,网络回调没有收到。但是其他回调可以正常工作,并且所有回调都是以同样的逻辑放在某个地方的。

我先确认了他的使用方式是否正确,并确认了输入参数的正确性,并且验证了回调的正确设置以及回调函数的使用无误。一切都没有问题,但是就是无法收到回调。

一切都那么的神奇,功能的调用并没有什么特殊的,但是就单单这个功能工作不正常。中间又经过一些确认,发现数据并没有到达服务端。但是同样的使用方式在我们自己的环境下工作又是正常的。

花了将近5个小时,还是没有发现原因,就在我准备放弃的时候,突然想到,莫非他的调用位置是线程中调用?经过确认,发现果然是在线程中调用的!让他们使用performOnMainThread的方式调用,终于解决了问题。

以前遇到过一次在线程中调用异步网络的情况,请教同事,同事告知,异步网络需要自己的RunLoop,所以要在线程中使用异步网络必须有自己的RunLoop,可以将他放到主线程的RunLoop。

但是,这样子,多线程的性能必然被降低,因为这样网络的工作就都是在主线程中完成的。同时怀疑,这样子的网络性能设计不是很低下!

刚好有点时间,就仔细的翻了一下苹果的开发文档,发现其中指出,所有的NSThread都有一个属于自己的NSRunLoop,而NSURLConnection的回调都会回调到当前线程中!

这个和我目前遇到的完全不一样!我无法在当前线程中收到回调!

又仔细在网络上搜索之后发现,原来,是因为网络回调的时候线程的NSRunLoop已经被无效的原因。

我们常见的线程中网络调用是这样的:

- (void)threadFunc
{
NSAutoReleasePool *pool = [NSAutoReleasePool new];
//do something
……

//send your request
[NSURLConnection connectionWithRequest:xxx];

//do some other thing
……
[pool release];
}

一般来说,这个函数会很快走完,线程就结束了。所以,当网络响应回来的时候,你的线程已经结束了。你的网络回调依赖于该线程的NSRunLoop,但是线程已经结束了,所以你的网络回调就无法收到!!同理,所有的performAfterDelay之类的api以及NSTimer的在线程中工作都不正常!

那么,该如何让他正常工作呢?很简单,做完事情之前,让线程不要结束,保持空转状态就行了。

- (void)threadFunc
{
NSAutoReleasePool *pool = [NSAutoReleasePool new];
//do something
……

//send your request
[NSURLConnection connectionWithRequest:xxx];

//do some other thing
……

while(shouldExit)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}



你可能感兴趣的:(ios,ios开发,runloop)