1.Runloop与线程
①.RunLoop是寄生于线程的消息循环机制,它能保证线程存活,而不是线性执行完任务就消亡。
线程一旦结束死亡,不能重新开启线程
②.RunLoop与线程是一一对应的,每个线程只有唯一与之对应的一个RunLoop。我们不能创建RunLoop,只能在当前线程当中获取线程对应的RunLoop(主线程RunLoop除外)。主线程中当App启动以后主线程会自动开启一个RunLoop来保证主线程的存活并处理各种事件
获取当前线程的RunLoop的四种方法
[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
CFRunLoopGetMain();
CFRunLoopGetCurrent();
③.子线程默认没有RunLoop,需要我们去主动开启,但是主线程是自动开启了RunLoop的。
④.RunLoop想要正常启用需要运行在添加了事件源的Mode下。
⑤.RunLoop有三种启动方式run、runUntilDate:(NSDate *)limitDate、runMode:(NSString *)mode beforeDate:(NSDate *)limitDate。第一种无条件永远运行RunLoop并且无法停止,线程永远存在。第二种会在时间到后退出RunLoop,同样无法主动停止RunLoop。前两种都是在NSDefaultRunLoopMode模式下运行。第三种可以选定运行模式,并且在时间到后或者触发了非Timer的事件后退出。
2. runloop中包含一个或多个Mode Model中包含source observer timer
Source/Timer/Observer 被统称为 mode item,一个item可以被同时加入多个mode。但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item 都没有(只有Observer也不行),则 RunLoop 会直接退出,不进入循环。
RunLoop同一时间只能运行在一种Mode下,当前运行的这个Mode叫currentMode,之所以要有Mode的存在是为了让RunLoop在不同的”行为模式“之下执行不同的”动作“互不影响。
(1)一般我们常用的Mode有三种
①.kCFRunLoopDefaultMode(CFRunLoop)/NSDefaultRunLoopMode(NSRunLoop)
默认模式,在RunLoop没有指定Mode的时候,默认就跑在DefaultMode下。一般情况下App都是运行在这个mode下的
②.(CFStringRef)UITrackingRunLoopMode(CFRunLoop)/UITrackingRunLoopMode(NSRunLoop)
一般作用于ScrollView滚动的时候的模式,保证滑动的时候不受其他事件影响。
③.kCFRunLoopCommonModes(CFRunLoop)/NSRunLoopCommonModes(NSRunLoop)
这个并不是某种具体的Mode,而是一种模式组合,在主线程中默认包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。子线程中只包含NSDefaultRunLoopMode。
注意:
a.在选择RunLoop的runMode时不可以填这种模式否则会导致RunLoop运行不成功。
b.在添加事件源的时候填写这个模式就相当于向组合中所有包含的Mode中注册了这个事件源。
c.你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到 kCFRunLoopCommonModes组合。
(2)source就是输入源事件,分为source0和source1这两种。
①.source0:诸如UIEvent(触摸,滑动等),performSelector这种需要手动触发的操作。
②.source1:处理系统内核的mach_msg事件(系统内部的端口事件)。诸如唤醒RunLoop或者让RunLoop进入休眠节省资源等。
一般来说日常开发中我们需要关注的是source0,source1只需要了解。
之所以说source0更重要是因为日常开发中,我们需要对常驻线程进行操作的事件大多都是source0,稍后的实验会讲到。
(3)Timer即为定时源事件。
通俗来讲就是我们很熟悉的NSTimer,其实NSTimer定时器的触发正是基于RunLoop运行的,所以使用NSTimer之前必须注册到RunLoop,但是RunLoop为了节省资源并不会在非常准确的时间点调用定时器,如果一个任务执行时间较长,那么当错过一个时间点后只能等到下一个时间点执行,并不会延后执行(NSTimer提供了一个tolerance属性用于设置宽容度,如果确实想要使用NSTimer并且希望尽可能的准确,则可以设置此属性)。
(4)Observer它相当于消息循环中的一个监听器,随时通知外部当前RunLoop的运行状态。NSRunLoop没有相关方法,只能通过CFRunLoop相关方法创建
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%d", activity);
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
它不能作为让RunLoop正常运行的条件,只有Observer的RunLoop也是无法正常运行的。
3.RunLoop正常运行的条件是:1.有Mode。2.Mode有事件源。3.运行在有事件源的Mode下。
NSRunLoop中总共包装了3个方法供我们使用
①.- (void)run;
除非希望子线程永远存在,否则不建议使用,因为这个接口会导致Run Loop永久性的运行NSDefaultRunLoopMode模式,即使使用 CFRunLoopStop(runloopRef);也无法停止RunLoop的运行,那么这个子线程也就无法停止,只能永久运行下去。
②.- (void)runUntilDate:(NSDate *)limitDate;
比上面的接口好点,有个超时时间,可以控制每次RunLoop的运行时间,也是运行在NSDefaultRunLoopMode模式。这个方法运行RunLoop一段时间会退出给你检查运行条件的机会,如果需要可以再次运行RunLoop。注意CFRunLoopStop(runloopRef);仍然无法停止RunLoop的运行,因此最好自己设置一个合理的RunLoop运行时间。
③.- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
有一个超时时间限制,而且可以设置运行模式
这个接口在非Timer事件触发、显式的用CFRunLoopStop停止RunLoop或者到达limitDate后会退出返回。如果仅是Timer事件触发并不会让RunLoop退出返回,但是如果是PerfromSelector事件或者其他Input Source事件触发处理后,RunLoop会退出返回YES。同样可以像上面那样用while包起来使用。
4.保持线程的存活后,让线程在我们需要的时候响应消息。
线程内响应某方法的方法
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
在主线程中响应指定Selector。这两个方法给你提供了选项来阻断当前线程(不是执行Selector的线程而是调用上述方法的线程)直到selector被执行完毕。
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
在某个子线程(NSThread对像)中响应指定Selector。这两个方法同样给你提供了选项来阻断当前线程直到Selector被执行完毕。
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
在当前线程中执行Selector,并附加了延迟选项。多个排队的Selector会按照顺序一个一个的执行。
其实,这几个方法都是向线程中的RunLoop发送了消息,然后RunLoop接收到了消息就唤醒线程,去做对应的事情。所以想要正常使用这几个方法,响应selector的线程必须开启了RunLoop
5.让线程定时执行某任务(Timer)
NSTimer有如下几个创建方式
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block ;//iOS10以后新加的方法
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block ;//iOS10以后新加的方法
NSTimer定时器的触发正是基于RunLoop运行的,所以使用NSTimer之前必须注册到RunLoop
但是直接用scheduedTimerWith创建的Timer直接就生效,该方法自动把timer注册到runloop的NSDefaultRunLoopMode中