Runloop随手记

Runloop是iOS开发的一个难点,需要不断体会才能真正理解。

一个runloop有几个要素组成,一个是runloop模式,一个是source,一个是observer。runloop模式是需要监视的source和observer的集合,runloop运行期间,只有和该模式相关的source才会被监视并允许传递事件,也只允许相关的observer会被通知runloop的进程。 source分input source和timer source。

input source:

基于端口的输入源

自定义输入源

Cocoa执行selector的源


timer source:

timer source(定时源)

务必记住一点,输入源和观察者都是依赖于当前runloop的模式的。模式不对,是不会触发它们工作的。


timer创建

NSTimer的运行是依赖于runloop的。可以通过以下三种方式创建:

使用 scheduledTimerWithTimeInterval:invocation:repeats: 或者scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 这两个类方法创建一个timer并把它指定到一个默认的runloop模式中

  使用 timerWithTimeInterval:invocation:repeats: 或者 timerWithTimeInterval:target:selector:userInfo:repeats:这两个类方法创建一个timer的对象,不把它知道那个到run loop. (当创建之后,你必须手动的调用NSRunLoop下对应的方法 addTimer:forMode: 去将它制定到一个runloop模式中.)

  使用 initWithFireDate:interval:target:selector:userInfo:repeats: 方法分配并创建一个NSTimer的实例 (当创建之后,你必须手动的调用NSRunLoop下对应的方法addTimer:forMode: 去将它制定到一个runloop模式中.)

(以上引用自http://www.cnblogs.com/ios-wmm/archive/2012/08/24/2654779.html)

前面两种方式创建的timer可能不能满足我们的需要,这时候,我们还得执行addTimer:forMode:加到正确的runloop中去。


timer启动

timer的启动有两个条件,一是当前为匹配的runloop mode,二是fire date到来。不管如何你可以先调用fire来等待runloop进入匹配的模式。或者可以设置fireDate,这都是类似的。设置不同的fireDate,在不同时期会有不同的意义。

time  setFireDate:[NSDate  distantFuture]] 很显然这个一直不会fire,如果正在运行中的话,就相当于暂停了。

[time setFireDate:[NSDate  distanPast]] 这个是已经fire了。

[timer setFireDate:[NSDate date]] 这个表示继续,和fire其实是一样的。和上面的没有什么区别。

除了设置fireDate,timer创建的时候设定的timeinterval也是另一种触发方式。


timer的销毁

timer要从runloop中销毁,要使用invalidate方法:

if ([scrollTimer isValid] == YES) {
        [scrollTimer invalidate];
        scrollTimer = nil;
}

这里还有个可能会碰到的问题,滚动tableview时候,NSTimer似乎卡住了。原因是当前的runloop的mode发生了改变,运行在默认模式上的source没有被触发。可参考http://blog.csdn.net/jasonblog/article/details/7854693。

core foundation版本:

  • RunLoopRef runLoop = CFRunLoopGetCurrent(); 
  • CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL}; 
  • CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, 
  •                                         &myCFTimerCallback, &context); 
  •  
  • CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes); 

说完timer,就要说说input source。自定义的先不管,先说基于端口和Cocoa的selector源。首先他们都是iOS进程间通信的方式。现在我们很少使用前者,推荐使用后者。

NSMachPort方式下,NSPort有3个子类,NSSocketPort、NSMessagePort、NSMachPort,但在iOS下只有NSMachPort可用。使用的方式为接收线程中注册NSMachPort,在另外的线程中使用此port发送消息,则被注册线程会收到相应消息,然后最终在主线程里调用某个回调函数。(http://blog.sina.com.cn/s/blog_7815a31f0101ea0n.html)

配置一个 NSMachPort相当的复杂。

首先要在当前线程创建一个Port,并设置其代理,加入runloop中。然后分配一个新的线程,在上面调用另一个类的方法,很关键的一点是要把这个Port作为参数传入。

代理要实现handlePortMessage:方法或者handleMachMessage:方法。参数是NSPortMessage,可以从它的msgid方法中获取message类型,并作出相应的处理。

再回到被调用的那个类的方法中,这时候传入了一个Port,被调用的类有责任向该Port发送message来和调用线程通信,因此要在该调用方法中调用sendCheckinMessage:
诸如之类的方法,参数是NSPort类型。当然这个方法也有义务保持自己的runloop。

sendCheckinMessage:方法中,你同样要创建自己的Port,并设置代理,实现代理方法,将Port加入运行循环。关键的一点是要创建NSPortMessage对象,连接这两个Port。创建完之后要设置msgid,并通过sendBeforeDate:方法发送出去。

配置一个NSMessagePort对象 (略)

Core Foundation版本(略)

以上详细代码可查看http://mobile.51cto.com/hot-403083_2.htm

这时候似乎是抛出创建自定义输入源的时候。自定义输入源也是一个大坑,也很复杂。简单来说,你需要创建source,并把它安装到runloop,然后发送一个signal通知源并唤醒runloop。source的创建依赖于一个context,context中指定你关心事件的回调函数。这些事件包括scheduleRoutine,performRouting和cancelRouting等。在scheduleRouting中你可以把这些source的引用保存起来便于调度。performRouting中应该是执行source的主要的任务。cancelRouting中,如果这些source被管理中的话,这个时候就可以将其从管理队列中移除。分段讲解的代码详见官方文档。



Runloop的启动

runloop有三种启动方式:

无条件的,不推荐

设置超时

特定模式

后面两种是建议的做法,当然它们不是互斥的,一个超时的也可以是在某种模式下的。


退出runloop

退出有两种方式:

超时自退

通知停止,只能使用CFRunLoopStop来实现。

尽管已出runloop中的所有输入源和定时源也可能导致runloop退出,但是文档多次强调这是不可取的,因为系统会动态加入一些source。

另外NSRunloop是非线程安全的,而CFRunloop是线程安全的。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值