先来一张图认识一下
屏幕快照 2018-12-21 下午 5.12.59.PNG
CFRunLoop 是一个结构体:
- 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;// 模式
- CFMutableSetRef _commonModeItems;// 包含了: source0 source1 Observer Timer
- CFRunLoopModeRef _currentMode;// 当前的模式
- CFMutableSetRef _modes;// 当前模式里面的对象
- struct _block_item *_blocks_head;
- struct _block_item *_blocks_tail;
- CFAbsoluteTime _runTime;
- CFAbsoluteTime _sleepTime;
- CFTypeRef _counterpart;
- };
CFRunLoopMode 包含了:
- 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 */
- };
在 viewDidLoad 中打印:
- CFRunLoopRef r1 = CFRunLoopGetCurrent();
- CFRunLoopMode mod = CFRunLoopCopyCurrentMode(r1);
- kCFRunLoopDefaultMode
- (
- UITrackingRunLoopMode,// 界面跟踪, 主要用于 scrollView 的滑动
- GSEventReceiveRunLoopMode,// 接受系统事件, 基本用不到
- kCFRunLoopDefaultMode,// 默认模式
- kCFRunLoopCommonModes// 结合了 UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
- )
基本认识之后, 开始介绍 mode 里面的 source,Observer 还有 Timer
CFRunLoopSource
先上源码:
- 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;
- };
可以看到结构体里面包含了一个 union, 这个是 c 语言里面的联合体
这里稍微讲一下联合体, 先定义一个联合体如下:
- typedef union {
- int a ;
- float b;
- }UnionType;
- UnionType type;
- type.a = 3;
- NSLog(@"a --- %p",&type.a);
- NSLog(@"b --- %p",&type.b);
- NSLog(@"%zd",sizeof(UnionType));
2018-12-24 15:47:57.630546+0800 runloop 与线程的关系[29756:732129] a --- 0x7ffee48a19d0
2018-12-24 15:47:57.630615+0800 runloop 与线程的关系[29756:732129] b --- 0x7ffee48a19d0
2018-12-24 15:47:57.630693+0800 runloop 与线程的关系[29756:732129] 4
可以看到 a 和 b 的地址是一样的, 这个联合体所占的字节是 4 个字节
创建一个简单的 Source0(并没什么卵用), 了解下触发回调的时机即可:
- /* 自定义源只能为 source0, 而 source1 是系统的源 */
- CFRunLoopSourceContext context = {
- 0,// 版本
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- schedule,// 回调
- cancel,// 回调
- perform// 回调
- };
- CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
- // 回调:
- void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
- NSLog(@"%s",__func__);
- }
- void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
- NSLog(@"%s",__func__);
- }
- void perform(void *info){
- NSLog(@"%s",__func__);
- }
要想触发 schedule, 需要添加源:
CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
触发 perform, 需要标识为待处理
- CFRunLoopSourceSignal(source0);
- CFRunLoopWakeUp(CFRunLoopGetCurrent());
触发 cancel, 需要将源移除
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
最后记得销毁源:
CFRelease(source0);
在介绍 source1 之前, 先了解下 NSPort:
NSPort 是什么?
通信通道的抽象类
能干什么?
我们能够使用端口进行线程间的通信
要接受传入消息, 必须将 NSPort 对象添加到 NSRunLoop 对象作为输入源
端口用完之后, 如果不用, 要释放, 不然产生的端口对象可能会逗留并创建内存泄漏. 要使使端口对象无效, 请调用它的 invalidate 方法.
Foundation 定义了 NSPort 的三个具体子类 NSMachPoart 和 NSMessagePoart 只允许本地 (在同一台机器上) 通信, NSSocketPoart 支持本地和远程通信, 但是对于本地情况, 可能要比其他的要昂贵, 在使用 allocwithZone 或 poart 创建 NSPort 对象时, 将创建一个 NSMatchPoart 对象.
下面我将写一个 demo, 尝试主线程向子线程发送消息:
定义两个端口:
- // 子线程端口
- @property (nonatomic,strong)NSPort *subThreadPort;
- // 主线程端口
- @property (nonatomic,strong)NSPort *mainThreadPort;
在 viewDidLoad 创建主线程, 在 task 中使用 NSThread 创建子线程:
- self.mainThreadPort = [NSPort port];
- // 如果要监听端口的消息, 需要遵循端口的代理: NSPortDelegate
- self.mainThreadPort.delegate = self;
- // 添加到 runLoop 中作为输入源
- [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];
- [self task];
实现 NSPortDelegate 代理:
- - (void)handlePortMessage:(NSPortMessage *)message{
- NSLog(@"%@",[NSThread currentThread]);
- }
在点击事件中, 主线程向子线程发送消息, 需要注意的是发送消息必须要是二进制流, 所以需要转一下格式:
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- // 主线程向子线程发送消息
- NSMutableArray *components = [NSMutableArray array];
- NSData *data = [@"主线程向子线程发送消息" dataUsingEncoding:NSUTF8StringEncoding];
- [components addObject:data];
- [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
- }
现在我们点击一下, 控制台会打印:
<NSThread: 0x600002ca21c0>{number = 3, name = 子线程}
说明确实向子线程发送消息成功了, 但是是仅仅是打印出来线程, 还没有打印出来我们发送的消息, 紧接着点击看看 NSPortMessage 是个什么鬼:
@class NSConnection, NSPortMessage;
仅仅是类的声明, 看不到里面的实例变量, 但是我们通过 macOS 的 foundation 框架中找到 NSPortMessage:
- @interface NSPortMessage : NSObject {
- @private
- NSPort *localPort;
- NSPort *remotePort;
- NSMutableArray *components;
- uint32_t msgid;
- void *reserved2;
- void *reserved;
- }
接下来我们可以使用 kvc 的方式访问私有变量
- - (void)handlePortMessage:(id)message{
- NSLog(@"%@",[NSThread currentThread]);
- NSMutableArray *components = [message valueForKey:@"components"];
- // 这里仅仅是 demo, 所以处理起来没那么严谨
- if (components.count> 0) {
- NSData *data = components[0];
- NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
- NSLog(@"%@",str);
- }
- }
我们收到的消息是在子线程里面的, 我们可以再这里给主线程发送一条消息:
- - (void)handlePortMessage:(id)message{
- NSLog(@"%@",[NSThread currentThread]);
- NSMutableArray *components = [message valueForKey:@"components"];
- if (components.count> 0) {
- NSData *data = components[0];
- NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
- NSLog(@"%@",str);
- }
- sleep(2);
- if ([[NSThread currentThread]isMainThread] == NO) {
- // 子线程向主线程发送消息
- NSMutableArray *components = [NSMutableArray array];
- NSData *data = [@"子线程向主线程发送消息" dataUsingEncoding:NSUTF8StringEncoding];
- [components addObject:data];
- [self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
- }
- }
屏幕快照 2018-12-24 下午 5.07.26.PNG
发送成功
- Timer:
- CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
- NSLog(@"%s", __func__);
- });
- CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
屏幕快照 2018-12-24 下午 5.15.21.PNG
- observer:
- CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
- /*
- kCFRunLoopEntry = (1UL << 0), 即将进入 runloop
- kCFRunLoopBeforeTimers = (1UL << 1), 即将处理 timer 事件
- kCFRunLoopBeforeSources = (1UL << 2), 即将处理 source 事件
- kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入睡眠
- kCFRunLoopAfterWaiting = (1UL << 6), 被唤醒
- kCFRunLoopExit = (1UL << 7), runloop 退出
- kCFRunLoopAllActivities = 0x0FFFFFFFU
- */
- switch (activity) {
- case kCFRunLoopEntry:
- NSLog(@"即将进入 runloop");
- break;
- case kCFRunLoopBeforeTimers:
- NSLog(@"即将处理 timer 事件");
- break;
- case kCFRunLoopBeforeSources:
- NSLog(@"即将处理 source 事件");
- break;
- case kCFRunLoopBeforeWaiting:
- NSLog(@"即将进入睡眠");
- break;
- case kCFRunLoopAfterWaiting:
- NSLog(@"被唤醒");
- break;
- case kCFRunLoopExit:
- NSLog(@"runloop 退出");
- break;
- default:
- break;
- }
- NSLog(@"%lu", activity);
- });
- CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
来源: http://www.jianshu.com/p/dc181e84620c