runloop的使用

一、RunLoop是什么?
字面意思:运行循环,程序运行过程中循环的处理事情
它的实际:实际是一个对象, 这个对象提供一个入口函数,执行这个入口函数后,程序会进入一个do..while循环,循环的处理一些事情。
二、RunLoop有什么用?

int main(int argc, char * argv[]){
 //没有runloop
@autoreleasepool { 

    NSLog(@“%s”, __func__);
}
return 0;
}

结果:程序执行完就会退出。

int main(int argc, char * argv[]){
 //有runloop
@autoreleasepool { 

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

}
}

结果:程序一直执行没有退出。
RunLoop基本作用:

  • 保持程序的持续运行
  • 处理App中的各种事件(触摸、定时器、PerformSelector)
  • 节省CPU资源、提高程序性能:该做事的时候做事,该休息的时候休息。
    三、RunLoop怎么使用?
    iOS提供了2套API来访问和使用RunLoop:
    Foundation:NSRunLoop
    Core Foundation:CFRunLoopRef
    CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/
  1. 线程与RunLoop是一一对应的
  2. 线程创建的时候,并没有创建RunLoop对象,RunLoop会在第一次获取的时候自动创建。
  3. 主线程默认开启了RunLoop, 子线程默认没有开启子线程
    四、CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserverRef


    runloopmodel.png

    runloop结构.png

    1.CFRunLoopRef
    *一个RunLoop对应着一条线程
    *一个RunLoop包含多个Mode,每个 Mode 又包含若干个 Source/Timer/Observer
    *Source/Timer/Observer又叫mode item。不同mode下的mode item互不影响
    *RunLoop运行过程中,只选择一种模式运行
    *切换Mode,程序退出当前RunLoop mode,再重新指定Mode执行
    2.CFRunLoopSourceRef

  • source0:触摸事件,自定义输入源,performSelector:onThread:
  • source1:端口(Port)
    *计时源:NSTimer,performSelector:withObject:afterDelay
    3.线程添加runloop
- (void)viewDidLoad {
    [super viewDidLoad];
//    CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
//    CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
    self.stopping = NO;
    NSThread* th = [[NSThread alloc] initWithBlock:^{
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"定时打招呼!! 你好!");
            
            if (self.stopping) {
                [NSThread exit];//线程退出,runloop也同时结束
            }
        }];
       // NSLog(@"%s", __func__);
        [[NSRunLoop currentRunLoop] run];//线程创建的时候,并没有创建runloop对象,runloop会在第一次获取的时候自动创建
    }];
    [th start];
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.stopping = YES;
}

4.查看runloop模式

    CFRunLoopRef rl = CFRunLoopGetCurrent();
    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(rl);
    NSLog(@"mode ---> %@", mode);//输出:kCFRunLoopDefaultMode
    CFArrayRef array = CFRunLoopCopyAllModes(rl);
    NSLog(@"array ---> %@", array);
    /**
     输出:
     array ---> (
     UITrackingRunLoopMode,
     GSEventReceiveRunLoopMode,
     kCFRunLoopDefaultMode,
     kCFRunLoopCommonModes
     )
     */

5.自定义输入源

- (void) sourceTest {
    CFRunLoopSourceContext context = {
        0,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        schedule,
        cancel,
        perform
    };
//    触发schedule
    CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
//    触发perform
//    CFRunLoopSourceSignal(source0);//标记
//    CFRunLoopWakeUp(CFRunLoopGetCurrent());//唤醒
//    触发cancel
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
    CFRelease(source0);
}

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__);
}
自定义源.png

6.CFRunLoopTimerRef,定时器

@interface ViewController ()
{
    CFRunLoopTimerRef timer;
}
@end

- (void) timerTest {
//    第一种方式
    timer =  CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
       NSLog(@"%s", __func__);
    });

    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
//    第二种方式
//    CFRunLoopTimerContext context = {
//        0,
//        (__bridge void *)self,
//        CFRetain,
//        CFRelease,
//        NULL
//    };
//    timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, callBack, &context);
//    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
}

void callBack(CFRunLoopTimerRef timer, void *info){
    NSLog(@"✈️✈️✈️✈️✈️✈️");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    销毁定时器
    CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
    CFRelease(timer);
}

7.CFRunLoopObserverRef,观察者

- (void) observerTest {
//    第一种方式
/*
     第一个参数:怎么分配存储空间
     第二个参数:要监听的状态 kCFRunLoopAllActivities 所有的状态
     第三个参数:是否持续监听
     第四个参数:优先级 总是传0
     第五个参数:当状态改变时候的回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"%lu", activity);
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//    第二种方式
//    //    定义观察者
//    static CFRunLoopObserverRef runloopObserver;
//    //    创建观察者
//    CFRunLoopObserverContext context = {
//        0,
//        (__bridge void *) self,
//        &CFRetain,
//        &CFRelease,
//        NULL
//    };
//    //NULL此处相当于kCFAllocatorDefault
//    runloopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeTimers, YES, 0, &callBack1, &context);
//    //    添加观察者
//    CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopCommonModes);
//    移除并释放
//    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//    CFRelease(observer);
    
}

void callBack1(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
}
observer.png

五、NSPort
1.NSPort是通信通道的抽象类。
2.能干什么?:我们能够使用端口进行线程间的一个通信。
3.要接收传入消息,必须将NSPort对象添加到NSRunLoop对象中作为输入源
4.端口用完之后,如果不用, 要释放, 不然产生的端口对象可能会逗留并创建内 存泄漏。要使端口对象无效,请调用它的invalidate方法。
5.Foundation定义了NSPort的三个具体子类。NSMachPort和NSMessagePort只允许本地(在同一台机器上)通信。NSSocketPort支持本地和远程通信,但是对于本地情 况,可能比其他的要昂贵。在使用allocWithZone:或port创建NSPort对象时,将创建 一个NSMachPort对象。
6.使用allocWithZone:活着port创建NSPort对象那, 实际上是创建一个NSMachPort对象

@interface ViewController () <NSPortDelegate>

@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mainThreadPort = [NSPort port];
    self.mainThreadPort.delegate = self;
    [[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];//子线程要开启runloop
    }];
    [thread setName:@"子线程"];
    [thread start];
}
//- (void)handlePortMessage:(NSPortMessage *)message
- (void)handlePortMessage:(id)message {
    NSLog(@"%@", [NSThread currentThread]);
    //KVC 取值,在Macos的Foundation中的NSPortMessage.h中查看,components为私有的,所以要用kvc取值
    NSMutableArray* components = [message valueForKey:@"components"];
    
    if ([components count] > 0) {
        NSData* data = [components objectAtIndex:0];
        NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
    }

    sleep(2);
    if (![[NSThread currentThread] isMainThread]) {
        NSMutableArray* sendComponents = [NSMutableArray array];
        NSData* data = [@"world" dataUsingEncoding:NSUTF8StringEncoding];
        [sendComponents addObject:data];
         [self.mainThreadPort sendBeforeDate:[NSDate date] components:sendComponents 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];
    /**
     第一个参数:发送时间
     第二个参数:发送的数据
     第三个参数:从那个端口发送,此处从主线程端口self.mainThreadPort向子线程端口发送
     第四个参数:保留位
     */
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
//    soure0标示为待处理,要唤醒
}

六、runloop执行过程


执行过程.png

内部实现.png

休眠原理
RunLoop实现休眠的原理, 真正的原因是:
1.调用了内核的API(mach_msg), 进入内核态,由内核来将线程置于休眠
2.有消息,就唤醒线程,回到用户态,来处理消息.


休眠原理.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容