RunLoop学习笔记
在一般情况下,一个线程在执行完了一个任务后就会自动退出。我们想要有这样一个机制,让线程随时可以处理事件但是不退出。RunLoop实际就是一个对象,这个对象提供了一个入口函数,线程执行了这个函数后,会一直处在这个函数循环中,接收消息->等待->处理,直到循环结束。
RunLoop概念
RunLoop介绍
[图片上传失败...(image-663bc5-1547089087030)]
从代码上看,RunLoop就是一个对象,它的结构如下,源码在这里
struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; //RunLoop对应的线程 uint32_t _winthread; CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer) CFRunLoopModeRef _currentMode; //当前运行的mode CFMutableSetRef _modes; //存储的是CFRunLoopModeRef struct _block_item *_blocks_head;//doblocks的时候用到 struct _block_item *_blocks_tail; CFTypeRef _counterpart; };
可以看到,一个RunLoop对象,主要包含一个线程,若干mode,若干commoMode,若干commonModeItem,还有一个当前运行的mode。
RunLoop和线程
RunLoop就是管理线程的,当线程的RunLoop开启后,线程就会在执行任务后,处于休眠状态,随时等待接收新的任务被唤醒,而不是退出。
只有主线程的RunLoop是默认开启的,所以程序启动后,主线程会一直运行,不会退出。其他线程默认是没有RunLoop的,所以在执行完了任务后线程会自动退出,但是当我们去获取当前线程的RunLoop时,系统会自动创建该线程对应的RunLoop。
RunLoop Mode
Mode可以看做是事件的管家,一个Mode管理着各种事件,它的结构如下:
struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; //mode名称 Boolean _stopped; //mode是否被终止 char _padding[3]; //几种事件 CFMutableSetRef _sources0; //sources0 CFMutableSetRef _sources1; //sources1 CFMutableArrayRef _observers; //通知 CFMutableArrayRef _timers; //定时器 CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中 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 */};
一个CFRunLoopModeRef
对象有一个name,若干source0,source1,timer,observer和port,可以看出事件都是由mode在管理,而RunLoop管理着Mode。
从源码可以看出,RunLoop总是运行在某个特定的Mode下,每个RunLoop都可以包含若干个Mode,每个Mode又包含source,timer,observer。每次调用RunLoop的主函数_CFRunLoopRun()
时必须指定一种Mode。这个Mode称为currentMode
,当切换Mode时必须退出当前Mode。然后重新进入RunLoop以保证不同的Mode的source,timer,observer互不影响。
[图片上传失败...(image-3cd7a0-1547089087030)]
如上图所示,RunLoop实际就是source,timer,observer的集合,不同的mode把不同组的source,timer,observer隔绝开来。RunLoop在某个时刻只能泡在一个Mode下,处理这一个mode中的source,timer,observer。
苹果文档中提到的Mode有五个:
NSDefaultRunLoopMode
NSConnectionReplyMode
NSModalPanelRunLoopMode
NSEventTrackingRunLoopMode
NSRunLoopCommonModes
iOS中暴露的Mode只有NSDefaultRunLoopMode
和NSRunLoopCommonModes
,NSRunLoopCommonModes
是一个Mode的集合,默认包括NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
,当我们把事件源加入到NSRunLoopCommonModes
这种Mode时,就等于是把事件源同时加入了NSDefaultRunLoopMode
和NSEventTrackingRunLoopMode
这两种Mode。我们也可以通过CFRunLoopAddCommonMode()
这个方法将自定义的Mode加入到kCFRunLoopCommonModes
组合中。
RunLoop Source
RunLoop Source分为Source,Observer,Timer三种,他们统称为ModeItem。
CFRunLoopSource
CFRunLoopSource
分为source0和source1,它的结构如下:
struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理 pthread_mutex_t _lock; CFIndex _order; /* immutable */ CFMutableBagRef _runLoops; union { CFRunLoopSourceContext version0; //version0和version1决定是source0还是source1 CFRunLoopSourceContext1 version1; } _context; };
source0是APP内部事件,由APP自己管理的UIEvent都是source0。当一个source0事件准备执行的时候,必须先把他标记为signal状态,以下是source0的结构体:
typedef struct { CFIndex version; void * info; const void *(*retain)(const void *info); void (*release)(const void *info); CFStringRef (*copyDescription)(const void *info); Boolean (*equal)(const void *info1, const void *info2); CFHashCode (*hash)(const void *info); void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode); void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); void (*perform)(void *info); } CFRunLoopSourceContext;
source0是飞非基于port的,它包含了一个回调,并不能主动触发事件。使用时需要先调用CFRunLoopSourceSignal(source)
,将这个source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)
来唤醒RunLoop,让其处理这个事件。
source1由内核和RunLoop管理。source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体:
typedef struct { CFIndex version; void * info; const void *(*retain)(const void *info); void (*release)(const void *info); CFStringRef (*copyDescription)(const void *info); Boolean (*equal)(const void *info1, const void *info2); CFHashCode (*hash)(const void *info);#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) mach_port_t (*getPort)(void *info); void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);#else void * (*getPort)(void *info); void (*perform)(void *info);#endif} CFRunLoopSourceContext1;
CFRunLoopObserver
CFRunLoopObserver
是RunLoop状态的监听者,可以监听RunLoop的各种状态并执行回调:
struct __CFRunLoopObserver { CFRuntimeBase _base; pthread_mutex_t _lock; CFRunLoopRef _runLoop; //哪个RunLoop对象 CFIndex _rlCount; CFOptionFlags _activities; //监听的是哪种状态 CFIndex _order; /* immutable */ CFRunLoopObserverCallBack _callout; //回调 CFRunLoopObserverContext _context; /* immutable, except invalidation */};
CFRunLoopObserver可以观察的状态有以下6种:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), //即将进入run loop kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer kCFRunLoopBeforeSources = (1UL << 2),//即将处理source kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事件 kCFRunLoopExit = (1UL << 7),//run loop已经退出 kCFRunLoopAllActivities = 0x0FFFFFFFU };
CFRunLoopTimer
CFRunLoopTimer
和NSTimer
是toll-free-bridged
,可以在设定的时间点抛出回调:
struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; //标记fire状态 pthread_mutex_t _lock; CFRunLoopRef _runLoop; //添加该timer的runloop CFMutableSetRef _rlModes; //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在 CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; //理想时间间隔 /* immutable */ CFTimeInterval _tolerance; //时间偏差 /* mutable */ uint64_t _fireTSR; /* TSR units */ CFIndex _order; /* immutable */ CFRunLoopTimerCallBack _callout; /* immutable */ CFRunLoopTimerContext _context; /* immutable, except invalidation */};
RunLoop的实现
下面从以下3个方面来介绍RunLoop的实现:
获取RunLoop
添加Mode
添加RunLoop Source
获取RunLoop
Apple不允许我们直接创建RunLoop对象,只能通过以下几个函数来获取:
CFRunLoopRef CFRunLoopGetCurrent(void)
CFRunLoopRef CFRunLoopGetMain(void)
+(NSRunLoop *)currentRunLoop
+(NSRunLoop *)mainRunLoop
前两个是Core Foundation的API,后两个是Foundation中的API。
那么RunLoop什么时候创建呢?我们进入函数内部来看:
CFRunLoopGetCurrent
CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; //传入当前线程作为参数 return _CFRunLoopGet0(pthread_self()); }
在CFRunLoopGetCurrent(void)
函数内部调用了_CFRunLoopGet0()
,传入的参数是当前线程。
CFRunLoopGetMain
CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed//传入主线程作为参数 if (!__main) __main = _CFRunLoopGet0(_CFMainPThread); // no CAS needed return __main; }
CFRunLoopGetMain(void)
最终也是调用了CFRunLoopGet0
,传入了主线程作为参数,所以这个方法不管是在主线程还是子线程中都能获得主线程的RunLoop。
CFRunLoopGet0
前面两个方法最终都是调用了CFRunLoopGet0
这个方法,下面我们看一下这个方法的具体实现:
struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; //mode名称 Boolean _stopped; //mode是否被终止 char _padding[3]; //几种事件 CFMutableSetRef _sources0; //sources0 CFMutableSetRef _sources1; //sources1 CFMutableArrayRef _observers; //通知 CFMutableArrayRef _timers; //定时器 CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中 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 */};
0
这段代码可以得出下列结论:
RunLoop和线程一一对应,对应的方式是以key-value的方式保存在一个全局字典中。
主线程的RunLoop会在初始化全局字典时创建。
子线程的RunLoop会在第一次获取的时候创建,如果不获取的话就一直不会被创建。
RunLoop会在线程销毁的时候销毁。
RunLoop运行
本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/runloopxuexibiji.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:1 月 21, 2019 at 06:04 下午