版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.08.22 |
前言
NSRunloop
是OC Foundation
框架中非常重要的一个类,很多时候我们会使用它,但是未必对其有深入的了解,接下来几篇我就会带着大家重新学习一下NSRunloop
这个类,从简单到复杂,从基本到深化,我会一步步的走完。希望对大家有所帮助。具体可以参考苹果的开发文档。
NSRunloop基本了解
Runloop
即运行循环。NSRunloop是对CFRunloop
的封装,为什么你的APP放在那里不去动它,在某个时间点去操作它,它还会给你反馈。就是因为Runloop的存在,因为Runloop的存在,保证你的程序不会死。具体可以参见苹果开发文档。也可以在xcode里面下载。具体可参照下图。
安装好了以后大家可以从下面的路径/Applications/Xcode.app/Contents/Developer/Documentation/DocSets
查看,具体如下图所示。
至于开发文档的使用后面会单独抽出来一篇和大家详细说明。
使用command + shift + 0
快捷键出来的文档,大家也可以参考。
NSRunloop的本质
NSRunloop是对CFRunloop
的封装。
NSRunloop主要作用
NSRunloop主要有以下作用:
- 使程序一直运行并接受用户输入
- 决定程序在何时处理一些Event
- 调用解耦
(Message Queue)
- 节省
CPU
时间(没事的时候闲着,有事的时候处理)
依赖NSRunloop的类和框架
NSTimer
UIEvent
autorelease
NSObject(NSDelaydPerforming)
NSObject(NSThreadPerformAddtion)
CADisplayLink
CATransition
CAAnimation
dispatch_get_main_queue()
NSRunloop消息类型
下面我们看一下消息类型,其实就是很经典那个图。
Port
:
监听程序的Mach ports
,Mach ports是一个比较底层的东西,可以简单的理解为:内核通过port这种方式将信息发送,而mach则监听内核发来的port信息,然后将其整理,打包发给runloop。-
Customer
:
很明显,由开发人员自己发送。不仅仅是发送,过程的话相当复杂,苹果也提供了一个CFRunLoopSource
来帮助处理。由于很少用到,可以简单说下核心,但是对帮助我们理解runloop却很有帮助:- 定义输入源(数据结构)
- 将输入源添加到runloop,那么这样就有了接受者,即为R1。
- 协调输入源的客户端(单独线程),专门监听消息,然后将消息打包成runloop能够处理的样式,即第一步定义的输入源。它类似Mach的功能。
- 谁来发送消息的问题?上面的machport是由内核发送的。自定义的当然要我们自己发送了。。。首先必须是另一个线程来发送(当然如果只是测试的话可以和第三步在同一个线程),先发送消息给输入源,然后唤醒R1,因为R1一般处于休眠状态,然后R1根据输入源来做相应的处理。
Selector Sources
NSObject类提供了很多方法供我们使用,这些方法是添加到runloop的,所以如果没有开启runloop的话,不会运行。Timer Sources
:它的事件发送是同步的,这个用的比较多。-
Observers
,观察者:首先它并不属于事件源(不会影响runloop的生命周期),它比较特殊,用于观察runloop自身的一些状态的,有以下几种:- 进入runloop
- runloop即将执行定时器
- runloop即将执行输入源(Port,Customer,Selector Sources)
- runloop即将休眠
- runloop被唤醒,在处理完唤醒它的事件之前
- 退出
NSRunloop API文档
下面我们就看一下苹果给我们预留的API文档。
#import <Foundation/NSObject.h>
#import <Foundation/NSDate.h>
#import <CoreFoundation/CFRunLoop.h>
@class NSTimer, NSPort, NSArray<ObjectType>, NSString;
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;
FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes NS_AVAILABLE(10_5, 2_0);
//这里是NSRunLoop本类
@interface NSRunLoop : NSObject {
@private
id _rl;
id _dperf;
id _perft;
id _info;
id _ports;
void *_reserved[6];
}
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong) NSRunLoop *currentRunLoop;
@property (class, readonly, strong) NSRunLoop *mainRunLoop NS_AVAILABLE(10_5, 2_0);
#endif
@property (nullable, readonly, copy) NSRunLoopMode currentMode;
- (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;
- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
- (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
- (void)removePort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
- (nullable NSDate *)limitDateForMode:(NSRunLoopMode)mode;
- (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
@end
//这里是NSRunLoop其中的一个分类NSRunLoopConveniences
@interface NSRunLoop (NSRunLoopConveniences)
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
- (void)configureAsServer NS_DEPRECATED(10_0, 10_5, 2_0, 2_0);
#endif
/// Schedules the execution of a block on the target run loop in given modes.
/// - parameter: modes An array of input modes for which the block may be executed.
/// - parameter: block The block to execute
- (void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/// Schedules the execution of a block on the target run loop.
/// - parameter: block The block to execute
- (void)performBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@end
/**************** Delayed perform ******************/
@interface NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end
//这里是NSRunLoop其中的一个分类NSOrderedPerform
@interface NSRunLoop (NSOrderedPerform)
- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;
@end
从这个API文档上我们可以看见,提供的是一个本类,两个分类(NSRunLoopConveniences
和 NSOrderedPerform
)。下面以表格的形式给出。
模块 | 内容 |
---|---|
获取Runloop及其模式 | @property(class, readonly, strong) NSRunLoop *currentRunLoop; |
@property(readonly, copy) NSRunLoopMode currentMode; | |
- (NSDate *)limitDateForMode:(NSRunLoopMode)mode; | |
@property(class, readonly, strong) NSRunLoop *mainRunLoop; | |
- (CFRunLoopRef)getCFRunLoop; | |
定时器管理 | - (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode; |
端口Ports管理 | - (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode; |
- (void)removePort:(NSPort *)aPort forMode:(NSRunLoopMode)mode; | |
configureAsServer | - (void)configureAsServer; |
Running a loop | - (void)run; |
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate; | |
- (void)runUntilDate:(NSDate *)limitDate; | |
- (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate; | |
scheduling and canceling Messages | - (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes; |
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)arg; | |
- (void)cancelPerformSelectorsWithTarget:(id)target; | |
Run Loop Modes | - (void)performBlock:(void (^)(void))block; |
- (void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void (^)(void))block; |
下面我们就看一下文档里面给出的NSRunloop主要的方法和属性等信息。
参考文章
1. iOS NSRunloop详解
2. NSRunLoop原理详解——不再有盲点
后记
未完,待续~~~