首先来一个简单的Demo
我们会经常看到这样的代码:
这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
1.NSRunLoop是消息机制的处理模式
NSRunLoop的作用在于有事情做的时候使的当前NSRunLoop的线程工作,没有事情做让当前NSRunLoop的线程休眠
2.nstimer默认添加到当前NSRunLoop中,也可以手动制定添加到自己新建的NSRunLoop的中
[NSTimer schduledTimerWithTimeInterval: target:selector:userInfo:repeats];
此方法默认添加到当前NSRunLoop中
NSTimer *timer = [NSTimer timerWithTimeInterval: invocation:repeates:];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:...];
创建timer [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
注意 timer的释放
3.NSRunLoop就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)同步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
4.runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers
模式包括:
default模式:几乎包括所有输入源(除NSConnection) NSDefaultRunLoopMode模式
mode模式:处理modal panels
connection模式:处理NSConnection事件,属于系统内部,用户基本不用
event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件
common modes模式:NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。
每次运行一个run loop,你指定(显式或隐式)run loop的运行模式。当相应的模式传递给run loop时,只有与该模式对应的input sources才被监控并允许run loop对事件进行处理(与此类似,也只有与该模式对应的observers才会被通知)
例:
1).在timer与table同时执行情况,当拖动table时,runloop进入UITrackingRunLoopModes模式下,不会处理定时事件,此时timer不能处理,所以此时将timer加入到NSRunLoopCommonModes模式(addTimer forMode)
2).在scroll一个页面时来松开,此时connection不会收到消息,由于scroll时runloop为UITrackingRunLoopModes模式,不接收输入源,此时要修改connection的mode
[scheduleInRunLoop:[NSRunLoop currentRunLoop]forMode:NSRunLoopCommonModes];
5.关于-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)date;方法
指定runloop模式来处理输入源,首个输入源或date结束退出。
暂停当前处理的流程,转而处理其他输入源,当date设置为[NSDate distantFuture](将来,基本不会到达的时间),所以除非处理其他输入源结束,否则永不退出处理暂停的当前处理的流程。
6.while(A){
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
当前A为YES时,当前runloop会一直接收处理其他输入源,当前流程不继续处理,出为A为NO,当前流程继续
7.perform selector在thread中被序列化执行,这样就缓和了许多在同一个thread中运行多个方法所产生的同步问题。perform selector source在运行完selector后自动从run loop中移除。
当在非main thread中perform selector时,其thread中必须有一个激活的run loop。对于你自己创建的thread而言,只有你的代码显式的运行一个run loop后该perform selector才能得到执行。Run loop在当loop运行时处理所有已排队的perform selector,而不是在一个loop循环时只处理某一个perform selector。
8.performSelector关于内存管理的执行原理是这样的执行 [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3]; 的时候,系统会将tableLayer的引用计数加1,执行完这个方法时,还会将tableLayer的引用计数减1,由于延迟这时tableLayer的引用计数没有减少到0,也就导致了切换场景dealloc方法没有被调用,出现了内存泄露。
利用如下函数:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
当然你也可以一个一个得这样用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了这个以后,顺利地执行了dealloc方法
在touchBegan里面
[self performSelector:@selector(longPressMethod:) withObject:nil afterDelay:longPressTime]
然后在end 或cancel里做判断,如果时间不够长按的时间调用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(longPressMethod:) object:nil]
取消began里的方法
**********************************以下是我在cocoachina中看到的一份总结 转载过来
线程实现的几种方式:
1. Operation Objects // NSOperation及相关子类
2. ***** // dispatch_async等相关函数
3. Idle-time notifications // NSNotificationQueue,低优先级
3. Asynchronous functions // 异步函数
4. Timers // NSTimer
5. Separate processes // 没用过
线程创建的成本:
kernel data structures 约1KB
Stack space 512KB(secondary threads)
1MB(iOS main thread)
Creation time 约90 microseconds
Run Loop和线程的关系:
1. 主线程的run loop默认是启动的,用于接收各种输入sources
2. 对第二线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程执行一个长时间已确定的任务则不需要。
Run Loop什么情况下使用:
a. 使用ports 或 input sources 和其他线程通信 // 不了解
b. 在线程中使用timers // 如果不启动run loop,timer的事件是不会响应的
c. 在Cocoa 应用中使用performSelector...方法 // 应该是performSelector...这种方法会启动一个线程并启动run loop吧
d. 让线程执行一个周期性的任务 // 如果不启动run loop, 线程跑完就可能被系统释放了
注:timer的创建和释放必须在同一线程中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 此方法会retain timer对象的引用计数。
弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源,呜呜)。
官网的解释是说run loop可以用于处理异步事件,很抽象的说法。不罗嗦,先看看NSRunLoop几个常用的方法。
+ (NSRunLoop *)currentRunLoop; //获得当前线程的run loop
+ (NSRunLoop *)mainRunLoop; //获得主线程的run loop
- (void)run; //进入处理事件循环,如果没有事件则立刻返回。注意:主线程上调用这个方法会导致无法返回(进入无限循环,虽然不会阻塞主线程),因为主线程一般总是会有事件处理。
- (void)runUntilDate:(NSDate *)limitDate; //同run方法,增加超时参数limitDate,避免进入无限循环。使用在UI线程(亦即主线程)上,可以达到暂停的效果。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //等待消息处理,好比在PC终端窗口上等待键盘输入。一旦有合适事件(mode相当于定义了事件的类型)被处理了,则立刻返回;类同run方法,如果没有事件处理也立刻返回;有否事件处理由返回布尔值判断。同样limitDate为超时参数。
- (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //似乎和runMode:差不多(测试过是这种结果,但确定是否有其它特殊情况下的不同),没有BOOL返回值。
官网文档也提到run和runUntilDate:会以NSDefaultRunLoopMode参数调用runMode:来处理事件。
当app运行后,iOS系统已经帮助主线程启动一个run loop,而一般线程则需要手动来启动run loop。
使用run loop的一个好处就是避免线程轮询的开销,run loop在无事件处理时可以自动进入睡眠状态,降低CPU的能耗。
比如一般线程轮询的方式为:
while (condition)
{
// waiting for new data
sleep(1);
// process current data
}
其实这种方式是很能消耗CPU时间片的,如果在UI线程中这样使用还会阻塞UI响应。而改用NSRunLoop来实现,则可大大改善线程的执行效率,而且不会阻塞UI(很神奇,呵呵。有点像javascript,用单线程实现多线程的效果)。上面的例子可以改为:
while (condition)
{
// waiting for new data
if ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
{
// process current data
}
}
接下来我们看看具体的例子,包括如何实现线程执行的关联同步(join),以及UI线程run loop的一般使用技巧等。
假设有个线程A,它会启动线程B,然后等待B线程的结束。NSThread是没有join的方法,用run loop方式实现就比较精巧。
NSThread *A; //global
A = [[NSThread alloc] initWithTarget:self selector:@selector(runA) object:nil]; //生成线程A
[A start]; //启动线程A
- (void)runA
{
[NSThread detachNewThreadSelector:@selector(runB) toTarget:self withObject:nil]; //生成线程B
while (1)
{
if ([[NSRunLoop currentRunLoop] runMode:@"CustomRunLoopMode" beforeDate:[NSDate distantFuture]]) //相当于join
{
NSLog(@"线程B结束");
break;
}
}
}
- (void)runB
{
sleep(1);
[self performSelector:@selector(setData) onThread:A withObject:nil waitUntilDone:YES modes:@[@"CustomRunLoopMode"]];
}
实际运行时,过1秒后线程A也会自动结束。这里用到自定义的mode,一般在UI线程上调用run loop会使用缺省的mode。结合while循环,UI线程就可以实现子线程的同步运行(具体例子这里不再描述,可参看:http://www.cnblogs.com/tangbinblog/archive/2012/12/07/2807088.html)。
下面罗列调用主线程的run loop的各种方式,读者可以加深理解:
[[NSRunLoop mainRunLoop] run]; //主线程永远等待,但让出主线程时间片
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate distantFuture]]; //等同上面调用
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //主线程等待,但让出主线程时间片,然后过10秒后返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]; //主线程等待,但让出主线程时间片;有事件到达就返回,比如点击UI等。
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate date]]; //立即返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]]; //主线程等待,但让出主线程时间片;有事件到达就返回,如果没有则过10秒返回。