RunLoop
RunLoop
Overview
A
RunLoop
object processes input for sources such as mouse and keyboard events from the window system,Port
objects, andNSConnection
objects. ARunLoop
object also processesTimer
events.
Your application neither creates or explicitly managesRunLoop
objects. EachThread
object—including the application’s main thread—has anRunLoop
object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class methodcurrent
.
Note that from the perspective ofRunLoop
,Timer
objects are not "input"—they are a special type, and one of the things that means is that they do not cause the run loop to return when they fire.
Runloop与线程的关系是一一对应的,一个线程一次只能执行一个任务,任务执行完毕后线程就会被销毁,而Runloop的作用就是来管理和调度线程是他在没有任务的时候不会被销毁。
对于主线程来说,runloop在程序一启动就默认创建好了。
对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建。
RunLoop运行模式(一共有5种)
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode(Core Foundation)
Event tracking NSEventTrackingRunLoopMode (Cocoa)
Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes(Core Foundation)
Connection NSConnectionReplyMode (Cocoa)
Modal NSModalPanelRunLoopMode (Cocoa)
后面两种是初始化相关 默认无时间处理的时候 主线程runloop的mode为Default,Event tracking 是触摸事件产生的时候,Common modes 是占位符相当于 NSDefaultRunLoopMode + UITrackingRunLoopMode 。一些定时器的触摸失效就是因为可能被添加到了默认的Runloop里面而不是common modes。子线程如果不保证任务执行完不被销毁也一定要创建一个Runloop来保证线程的存活。
从Apple Document Achive搜索RunLoop发现根本搜不到因为RunLoop只是一个小小的分支
然后搜索Threading Programming Guide发现RunLoop相关的api出现在了这里
image.png
RunLoop是一个消息处理机制,系统交给它进行处理各种信息
一个简单程序执行的过程是 运行->处理计算->完成 -> 结束任务
最简单的结束任务是 return 0;
但是OC的主函数处理和其他不一样
int main(int argc, char * argv[]) {
@autoreleasepool {
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
}
}
UIApplication Main的函数注释是
如果为PrincipalClassName指定了nil,则使用info.plist中nsPrincipalClass的值。如果没有指定了nsPrincipalClass键,使用了uiApplication类。委托类将使用init进行实例化。所以就到了AppDelegate里面。一个App启动的流程
image.png
RunLoop的Item
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(调用timer,performselector)
image.pngimage.png
image.png
上图是一个简单的Timer和selector定时器 Runloop的类型为 CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(调用timer)
点开CoreFoundation的CFRunLoop源码
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(GCD主队列)
image.png
FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK(响应Block)
image.png
// main dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
/// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
/// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
/// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
/// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
/// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
Runloop实际开发作用
1.保持程序运行、
2.处理各种事件、
3.节省CPU资源。
RunLoop与线程的关系
#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
#endif
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
函数中传入了当前的主线程pthread_main_thread_np在宏定义中定义为当前主线程主线程进入点进_CFRunLoopGet0b
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
/// 判断当前是否是主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
/// 这里定义了 CFMutableDictionaryRef 创造主Runloop并且将其相关联 ///这里达
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
**CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);**
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
/// 这里 直接把t的指针取出来 拿到runloop 从这可以看出 线程和Runloop是一一对应的关系 key-- value
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
从上述代码可以看出来
线程和Runloop是一一对应的关系 key-- value
开启一条子线程里面执行一个定时器如果Runloop不开启的时候那么里面的计时器是不会执行的从这可以看出
子线程 Runloop默认不开启,需要手动开启
源码解析
RunLoop结构
Runloop是个结构体他是一个对象
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes; /// models 集合
CFMutableSetRef _commonModeItems; /// item 是个集合
CFRunLoopModeRef _currentMode; /// 当前models
CFMutableSetRef _modes; /// models 集合
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0; /// 集合
CFMutableSetRef _sources1; /// 集合
CFMutableArrayRef _observers; /// 数组
CFMutableArrayRef _timers; /// 数组
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
这就是是核心结构类型
RunLoop结构
1
image.png
source与RunLoop的关系
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union { /// 联合体
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
source0 包含了回调函数指针 ---》 signal(待处理) ----》 wakeup(换醒Runloop)
source0 主要处理App内部事件 app自己负责管理的事物(UIEvent,CFSocket)
- (void)source0Demo{
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform,
};
/**
参数一:传递NULL或kCFAllocatorDefault以使用当前默认分配器。
参数二:优先级索引,指示处理运行循环源的顺序。这里我传0为了的就是自主回调
参数三:为运行循环源保存上下文信息的结构
*/
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopRef rlp = CFRunLoopGetCurrent();
// source --> runloop 指定了mode 那么此时我们source就进入待绪状态
CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
// 一个执行信号
CFRunLoopSourceSignal(source0);
// 唤醒 run loop 防止沉睡状态
CFRunLoopWakeUp(rlp);
// 取消 移除
CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
CFRelease(rlp);
}
source1 包含了 match_port & 回调函数指针 主要依赖于port
@interface ViewController ()<NSPortDelegate>
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
@end
- (void)setupPort{
self.mainThreadPort = [NSPort port];
self.mainThreadPort.delegate = self;
// port - source1 -- runloop
[[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
[self task];
}
- (void) task {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
self.subThreadPort = [NSPort port];
self.subThreadPort.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
// 主线 -- 子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]); // 3
NSString *str;
dispatch_async(dispatch_get_main_queue(), ^{
// 1
NSLog(@"%@", [NSThread currentThread]);
});
});
}
// 线程之间通讯
// 主线程 -- data
// 子线程 -- data1
// 更加低层 -- 内核
// mach
- (void)handlePortMessage:(id)message {
NSLog(@"%@", [NSThread currentThread]); // 3 1
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([message class], &count);
for (int i = 0; i<count; i++) {
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
// NSLog(@"%@",name);
}
sleep(1);
if (![[NSThread currentThread] isMainThread]) {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"woard" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
// 主线程port发送信息到子线程port
[self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray* components = [NSMutableArray array];
NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:data];
/// 子线程port发送信息到主线程port
[self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
timer和RunLoop的关系
- (void)timerDemo{
// CFRunLoopMode 研究
CFRunLoopRef lp = CFRunLoopGetCurrent();
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(lp);
NSLog(@"mode == %@",mode);
CFArrayRef modeArray= CFRunLoopCopyAllModes(lp);
NSLog(@"modeArray == %@",modeArray);
//
// NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
// NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
// }];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
#pragma mark - timer
- (void)cfTimerDemo{
CFRunLoopTimerContext context = {
0,
((__bridge void *)self),
NULL,
NULL,
NULL
};
CFRunLoopRef rlp = CFRunLoopGetCurrent();
/**
参数一:用于分配对象的内存
参数二:在什么是触发 (距离现在)
参数三:每隔多少时间触发一次
参数四:未来参数
参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
参数六:回调,比如触发事件,我就会来到这里
参数七:上下文记录信息
*/
CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context);
CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
}
总结
- RunLoop与线程一一对应( key-- value),子线程默认不开启。
- RunLoop实际作用:
①.保持程序运行、
②.处理各种事件、
③.节省CPU资源 - RunTime结构:RunLoop与线程1对1, RunLoop与models一对多,models 与 Observer,timer,Sources一对多
- Sources0 负责处理各种事件
- Source1主要负责Port线程通讯(偏底层。偏内核)