博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OC RunLoop应用例子
阅读量:6343 次
发布时间:2019-06-22

本文共 13596 字,大约阅读时间需要 45 分钟。

知识点: 1、RunLoop的基础知识 2、RunLoop 与 NSTimer 3、RunLoop 与 Perform Selector 4、RunLoop、线程、AutoreleasePool三者联系 5、RunLoop 与 线程通信 6、RunLoop 的各种状态监听 7、RunLoop 与 NSNotificationQueue

####什么是RunLoop?

RunLoop 有五种运行模式,其中常见的有1.2两种

1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode复制代码

####RunLoop应用 #####1、NSTimer 主线程下:

- (void)timer{    NSTimer *aTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];    // 1、定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];        // 2、定时器只运行在UITrackingRunLoopMode下(滑动UIScrollView),一旦RunLoop进入其他模式,这个定时器就不会工作    [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];        // 3、标记为NSRunLoopCommonModes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容    [[NSRunLoop mainRunLoop] addTimer:aTimer forMode:NSRunLoopCommonModes];}复制代码

scheduledTimerWithTimeInterval

- (void)viewDidLoad {    [super viewDidLoad];        //主线程    dispatch_async(dispatch_get_global_queue(0, 0), ^{        // 这种方法是直接加入RunLoop:[[NSRunLoop currentRunLoop] addTimer: t forMode: NSDefaultRunLoopMode];        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerFired:)userInfo:nil repeats:YES];        // 需要确保当前currentRunLoop run起来        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];    });    }复制代码

#####RunLoop的run与stop 1、CFRunLoopStop能直接停止掉所有用CFRunloop运行起runloop

- (void)testTimer{    NSTimer *aTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(runrun) userInfo:nil repeats:YES];    // 手动获取    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];    // 由于UI都是在主线程,子线程不会有UITrackingRunLoopMode干扰,所以使用NSDefaultRunLoopMode就可以了    [currentRunLoop addTimer:aTimer forMode:NSDefaultRunLoopMode];    // run起来的方式,有    // 方式1    // 方式2    // 方式3}复制代码

方式1:run

/*方式1:     永久性的运行在NSDefaultRunLoopMode模式     停止runloop方式:     1、使用CFRunLoopStop无效     2、(1)停止移除timer或者移除port;(2)暴力强制退出线程(不是解决办法)     */    [currentRunLoop run];复制代码

方式2:runUntilDate

/*方式2:     规定时间运行在NSDefaultRunLoopMode模式     停止runloop方式:     1、使用CFRunLoopStop无效     2、(1)停止移除timer或者移除port;(2)到达指定时间     */    [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];//复制代码

方式3:runMode: beforeDate: (其实调用CFRunLoopRunInMode)

// returnAfterSourceHandled参数为YES,当触发一个非timer事件后,runloop就终止了    CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);returnAfterSourceHandled = YES的封装;复制代码
/*方式3:     规定runMode、规定时间,运行完返回运行状态     停止runloop方式:        (1)使用CFRunLoopStop;        (2)停止移除timer或者移除port;        (3)到达指定时间     */    BOOL result =  [currentRunLoop runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:20]];    if (result) {        // 如果是PerfromSelector*事件或者其他Input Source事件触发处理后,Run Loop结束时候返回YES,其他返回NO。    }else {        // NO    }}复制代码

一定时间内监听某种事件,或执行某种任务的线程,如在30分钟内,每隔30s执行onTimerFired:

@autoreleasepool {        NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30                                                    target:self                                                  selector:@selector(onTimerFired:)                                                  userInfo:nil                                                   repeats:YES];    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];}复制代码

#####2、performSelector: withObject: afterDelay: inModes

主线程

// 延时performSelector在主线程会被RunLoopMode干扰    // 延时performSelector其实里面就是一个NSTimer要实现afterDelay    [self performSelector:@selector(performSelector) withObject:nil afterDelay:2   inModes:@[NSDefaultRunLoopMode]];复制代码

子线程

BBThread *thread = [[BBThread alloc] initWithBlock:^{                [self performSelector:@selector(performSelector) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode]];        // 需要手动currentRunLoop跑起来        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];        [currentRunLoop run];             }];    [thread start];复制代码

#####3、常驻线程 AFNetworking 2.0中 创建了一条常驻线程专门处理所有请求的回调事件

+ (void)networkRequestThreadEntryPoint:(id)__unused object {    @autoreleasepool {        [[NSThread currentThread] setName:@"AFNetworking"];        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];         // 这里主要是监听某个 port,目的是让这个 Thread 不会回收        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];         [runLoop run];    }}+ (NSThread *)networkRequestThread {    static NSThread *_networkRequestThread = nil;    static dispatch_once_t oncePredicate;    dispatch_once(&oncePredicate, ^{        _networkRequestThread =        [[NSThread alloc] initWithTarget:self                                selector:@selector(networkRequestThreadEntryPoint:)                                  object:nil];        [_networkRequestThread start];    });    return _networkRequestThread;}复制代码

#####4、线程、RunLoop、AutoreleasePool三者关系 ######线程与RunLoop CFRunLoopGetMain() 和 CFRunLoopGetCurrent()的源码

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRefstatic CFMutableDictionaryRef loopsDic;/// 访问 loopsDic 时的锁static CFSpinLock_t loopsLock; /// 获取一个 pthread 对应的 RunLoop。CFRunLoopRef _CFRunLoopGet(pthread_t thread) {    OSSpinLockLock(&loopsLock);        if (!loopsDic) {        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。        loopsDic = CFDictionaryCreateMutable();        CFRunLoopRef mainLoop = _CFRunLoopCreate();        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);    }        /// 直接从 Dictionary 里获取。    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));        if (!loop) {        /// 取不到时,创建一个        loop = _CFRunLoopCreate();        CFDictionarySetValue(loopsDic, thread, loop);        /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);    }        OSSpinLockUnLock(&loopsLock);    return loop;} CFRunLoopRef CFRunLoopGetMain() {    return _CFRunLoopGet(pthread_main_thread_np());} CFRunLoopRef CFRunLoopGetCurrent() {    return _CFRunLoopGet(pthread_self());}复制代码

从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

######AutoreleasePool与RunLoop 1、一个线程对应一个RunLoop (主线程系统启动,其他线程需要使用来加载获取并启动) 2、线程如果没有启动RunLoop,遇到autorelease对象会用下面情况:

1)、线程里面如果有对象调用autorelease方法,系统就去找这个线程key对应的最栈顶poolpage;2)、如果找到page.add(obj)OK;3)、如果找不到就调用autoreleaseNoPage,新建这个线程的一个page;4)、线程释放,这个线程对应的page也pop。复制代码

3、线程启动了RunLoop,RunLoop内部会管理AutoreleasePool

1)、RunLoop开启时会objc_autoreleasePoolPush;2)、RunLoop休眠时会先objc_autoreleasePoolPop再objc_autoreleasePoolPush;3)、RunLoop退出时会objc_autoreleasePoolPop;复制代码

4、另外 在启动RunLoop之前建议用 @autoreleasepool {...}包裹 意义:创建一个大释放池,释放{}期间创建的临时对象,一般好的框架的作者都会这么做 (上面的AFNetworking就是这么做)

+ (void)networkRequestThreadEntryPoint:(id)__unused object {    @autoreleasepool {        [[NSThread currentThread] setName:@"AFNetworking"];        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];         // 这里主要是监听某个 port,目的是让这个 Thread 不会回收        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];         [runLoop run];    }}复制代码

######NSRunLoop与NSMachPort来线程通信的实例:

- (void)testDemo3{    //声明两个端口   随便怎么写创建方法,返回的总是一个NSMachPort实例    NSMachPort *mainPort = [[NSMachPort alloc]init];    NSPort *threadPort = [NSMachPort port];    //设置线程的端口的代理回调为自己    threadPort.delegate = self;    //给主线程runloop加一个端口    [[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];    dispatch_async(dispatch_get_global_queue(0, 0), ^{        //添加一个Port        [[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];    });    NSString *s1 = @"hello";    NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];        //过2秒向threadPort发送一条消息,第一个参数:发送时间。msgid 消息标识。        //components,发送消息附带参数。reserved:为头部预留的字节数(从官方文档上看到的,猜测可能是类似请求头的东西...)        [threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];    });}//这个NSMachPort收到消息的回调,注意这个参数,可以先给一个id。如果用文档里的NSPortMessage会发现无法取值- (void)handlePortMessage:(id)message{    NSLog(@"收到消息了,线程为:%@",[NSThread currentThread]);    //只能用KVC的方式取值    NSArray *array = [message valueForKeyPath:@"components"];    NSData *data =  array[1];    NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];    NSLog(@"%@",s1);//    NSMachPort *localPort = [message valueForKeyPath:@"localPort"];//    NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];}复制代码

打印如下:

2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了,线程为:
{number = 3, name = (null)}2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello复制代码

######自定义的输入源来实现线程通信

- (void)testDemo4{    dispatch_async(dispatch_get_global_queue(0, 0), ^{        NSLog(@"starting thread.......");        _runLoopRef = CFRunLoopGetCurrent();        //初始化_source_context。        bzero(&_source_context, sizeof(_source_context));        //这里创建了一个基于事件的源,绑定了一个函数        _source_context.perform = fire;        //参数        _source_context.info = "hello";        //创建一个source        _source = CFRunLoopSourceCreate(NULL, 0, &_source_context);        //将source添加到当前RunLoop中去        CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);        //开启runloop 第三个参数设置为YES,执行完一次事件后返回        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);        NSLog(@"end thread.......");    });    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        if (CFRunLoopIsWaiting(_runLoopRef)) {            NSLog(@"RunLoop 正在等待事件输入");            //添加输入事件            CFRunLoopSourceSignal(_source);            //唤醒线程,线程唤醒后发现由事件需要处理,于是立即处理事件            CFRunLoopWakeUp(_runLoopRef);        }else {            NSLog(@"RunLoop 正在处理事件");            //添加输入事件,当前正在处理一个事件,当前事件处理完成后,立即处理当前新输入的事件            CFRunLoopSourceSignal(_source);        }    });}//此输入源需要处理的后台事件static void fire(void* info){    NSLog(@"我现在正在处理后台任务");    printf("%s",info);}复制代码

输出结果如下:

2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件输入 2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我现在正在处理后台任务hello2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......复制代码

#####CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

-(void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event{ //创建监听者 /* 第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配 第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态 第三个参数 Boolean repeats:YES:持续监听 NO:不持续 第四个参数 CFIndex order:优先级,一般填0即可 第五个参数 :回调 两个参数observer:监听者 activity:监听的事件 */ /* 所有事件 typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU }; */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"RunLoop进入"); break; case kCFRunLoopBeforeTimers: NSLog(@"RunLoop要处理Timers了"); break; case kCFRunLoopBeforeSources: NSLog(@"RunLoop要处理Sources了"); break; case kCFRunLoopBeforeWaiting: NSLog(@"RunLoop要休息了"); break; case kCFRunLoopAfterWaiting: NSLog(@"RunLoop醒来了"); break; case kCFRunLoopExit: NSLog(@"RunLoop退出了"); break; default: break; } }); // 给RunLoop添加监听者 /* 第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop 第二个参数 CFRunLoopObserverRef observer 监听者 第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); /* CF的内存管理(Core Foundation) 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了 */ CFRelease(observer);}复制代码

CFRunLoopObserverRef使用demo: 利用主线程RunLoop空闲时候在处理,一些UI事情,减少卡顿(这就不需要多线程了)

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {                if (activity == kCFRunLoopBeforeWaiting) {            // RunLoop要休息了        }    });    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);    CFRelease(observer);复制代码

NSNotificationQueue也与runloop有关系

转载地址:http://djyla.baihongyu.com/

你可能感兴趣的文章
18年秋季学习总结
查看>>
Effective前端1:能使用html/css解决的问题就不要使用JS
查看>>
网络攻防 实验一
查看>>
由莫名其妙的错误开始---浅谈jquery的dom节点创建
查看>>
磨刀-CodeWarrior11生成的Makefile解析
查看>>
String StringBuffer StringBuilder对比
查看>>
bootstrap随笔点击增加
查看>>
oracle 中proc和oci操作对缓存不同处理
查看>>
[LeetCode] Spiral Matrix 解题报告
查看>>
60906磁悬浮动力系统应用研究与模型搭建
查看>>
指纹获取 Fingerprint2
查看>>
SB阿里云,windows2012r2无法安装.net3.5
查看>>
函数的继承
查看>>
黑盒测试用例设计方法&理论结合实际 -> 场景法
查看>>
快速打开软件以及文件夹
查看>>
CSS选择符
查看>>
剑指offer---19--***-顺时针打印矩阵
查看>>
关于数组随机不重复的思路
查看>>
oracle赋值问题(将同一表中某一字段赋值给另外一个字段的语句)
查看>>
Windows 安装 Jenkins 2.6
查看>>