美文网首页
RunLoop篇-底层源码

RunLoop篇-底层源码

作者: 亲爱的大倩倩 | 来源:发表于2019-07-29 16:26 被阅读0次
RunLoop数据结构

OC中为我们提供了两个Runloop
NSRunloop位于Foundation框架,CFRunloop位于CoreFoundation框架
NSRunloop是CFRunloop的封装,提供了面向对象的API

RunLoop内部数据结构之间的关系

source,observers和timers统称为Item
任何一个item都依赖于mode,任何一个mode都依赖于runloop
线程和RunLoop是一一对应的
RunLoop和Mode是一对多的关系,一个RunLoop可以有多个Mode
mode和对应的Source,Timer,Observer也是一对多的关系

CFRunLoop

下图表示runloop是个结构体对象


  1. pthead: C级别的线程对象,代表线程,说明Runloop和线程是一一对应的关系
  2. currentMode: 代表Runloop当前所处的模式,实际上是CFRunLoopMode数据结构
  3. modes: 多个mode的一个集合,它是NSMutableSet<CFRunLoopMode*>集合,里面的元素是CFRunLoopMode类型的
  4. commonModes: 也是NSMutableSet<NSString*>集合,里面元素都是字符串
  5. commonModeItems: 也是一个集合,但这个集合中会有多个元素,包含多个Observer,多个Timer,多个Source
CFRunloopMode

每次运行,只会依赖于一个mode
name: 字符串类型,对应某一个RunLoop的mode的名称,我们经常用的NSDefaultRunLoopMode,当我们调用Runloop的一些API去运行在指定模式下时,是通过模式名称来找到对应的Runloop模式的, commonModes里面的字符串元素就是不同模式的名称

一个RunLoop可以有多个Mode,每个Mode中又可以有多个sources,observers,timers

之所以有多个Mode的原因:
假设当RunLoop运行在Mode1上时,如果此时Mode2的某个observers事件回调了,我们是没办法接收到Mode2中回调来的事件的
之所以RunLoop有多个Mode,就是为了起到这样一个屏蔽效果
当我们运行在Mode1上时,只能接收处理Mode1上的sources, observers以及Timers
其他Mode的回调事件,是不会给与处理的

tableView里面如果有滚动的广告栏,当我们滑动tableView时,广告栏就不滚动了

如果个一Timer想同时加入到两个mode里面,需要怎样做?


NSRunLoopCommonModes
  1. CommonModex 不是实际存在的一种Mode
  2. 是同步Source / Timer / Observer到多个Mode中的一种技术解决方案
定时器Demo
#import "ViewController.h"

@interface ViewController ()<UITextViewDelegate>
@property (weak, nonatomic) IBOutlet UITextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    self.textView.delegate = self;
}

当把Mode设置为NSRunLoopCommonModes时候,手指滑动textView,计时器仍在计时,如下图可以看到Mode变化了



但如果当我们将mode改为defaultMode,那么在我们手指滑动时计时器就不响应了

CFRunLoopSource
source0 Demo

一旦我们调用了source0Demo, schedule, perform和cancel都会执行

- (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);
}

void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"准备");
}

void perform(void *info){
    NSLog(@"执行");
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"取消");
}

source1 Demo

平时线程通信都是GCD的方法去通信
现在可以用port来通信

GCD通信

    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *str;
        NSLog(@"%@", [NSThread currentThread]);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //
           NSLog(@"%@", [NSThread currentThread]);
        });
    });
}

运行结果 
<NSThread: 0x6000002d3200>{number = 3, name = (null)}
<NSThread: 0x600000282940>{number = 1, name = main}

port通信,和上面的结果一样

  1. port要加入到runloop里面
  2. 当有信息时,会响应handlePortMessage代理
#import "ViewController.h"
#import <objc/message.h>

@interface ViewController ()<NSPortDelegate>
@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupPort];
}


#pragma mark - source1: port演示

- (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];
        //子线程的runloop默认不开启
        [[NSRunLoop currentRunLoop] run];
    }];
    
    [thread start];
}

- (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];
        [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];
    //i点击屏幕时发送消息,子线程向主线程发送消息
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
@end
CFRunLoopTimer

基于事件的定时器
和我们平时使用的NSTimer是具备免费桥转换(toll-free bridged)的

CFRunLoopObserver

可以通过注册Observer来实现对RunLoop的相关时间点的观察
可以监听RunLoop哪些时间点呢

  1. kCFRunLoopEntry: RunLoop的入口时机,当RunLoop准备启动时,系统会给我们一个回调通知,就是kCFRunLoopEntry
  2. kCFRunLoopBeforeTimers: 通知观察者,RunLoop将要对Timer的一些相关事件进行处理
  3. kCFRunLoopBeforeSources: 通知将要处理一些source事件
  4. kCFRunLoopBeforeWaiting: 通知对应观察者当前RunLoop将要进入休眠状态,即将要发生用户态到内核态的切换
  5. kCFRunLoopAfterWaiting: 从内核态切换到用户态之后,会发出通知
  6. kCFRunLoopExit: RunLoop退出的通知

相关文章

  • RunLoop内部调用过程

    上一篇:RunLoop介绍篇下一篇:RunLoop应用篇 RunLoop的可执行底层源码已经放在这里了,需要看源码...

  • RunLoop篇-底层源码

    RunLoop数据结构 OC中为我们提供了两个RunloopNSRunloop位于Foundation框架,CFR...

  • Runloop

    Runloop 实现原理及应用iOS - RunLoop 底层源码详解及具体运用

  • 看 CFRunLoop源码深入理解 RunLoop

    原文地址Runloop是 iOS 中的基础概念,这篇文章将通过CFRunLoop源码来看RunLoop的概念及底层...

  • RunLoop 源码分析

    此篇主要分析 RunLoop 的源码,对源码的注释在仓库中。 分析源码我主要采用的是: RunLoop 相关的结构...

  • iOS Runloop 补充

    主要讲解Runloop的底层结构; 文中使用的 CF源码是CF-1153.18版本; Runloop 简析Runl...

  • 学习RunLoop

    runloop源码地址:源码下载 runloop官方介绍:查看文档 runloop的源码在corefundatio...

  • RunLoop学习资料

    非常好的runloop学习系列 CoreFoundation源码 RunLoop系列之源码分析 关于Runloop...

  • Runloop底层原理--源码分析

    什么是Runloop? Runloop不仅仅是一个运行循环(do-while循环),也是提供了一个入口函数的对象,...

  • 记录一些介绍Runloop的牛文

    老司机出品——源码解析之RunLoop详解深入理解RunLoop关于RunLoop部分源码的注释CFRunLoop...

网友评论

      本文标题:RunLoop篇-底层源码

      本文链接:https://www.haomeiwen.com/subject/pvfbrctx.html