• 多线程篇-RunLoop


    RunLoop

    简述

    1、RunLoop是事件接收和分发机制的一个实现
    2、并且它能处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    3、以及节省CPU资源,提高程序性能:(该做事时做事,该休息时休息)
    

    如何获取Runloop对象:

    这里的话IOS提供了两套API来访问或使用RunLoop
        1、CFRunLoopRef      是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
        2、NSRunLoop         是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
    

    CFRunLoopRef的代码是开源的,你可以在这里CFRunLoopRef源码下载到整个 CoreFoundation 的源码

    每一个线程对应着一个RunLoop,但是线程在创建的时候是没有RunLoop的,如果你不去获取它,它会一直没有,当然必须你自己的主动去获取,但是在你线程结束的时候,你所获取的RunLoop也跟着销毁了。如果你需要在某个线程对你自己的RunLoop执行一些事件的时候,那么你就的在线程未结束之前进行操作,然而在程序中是具有一个主RunLoop的,它用来管理程序的生死,具体的话是在UIApplicationMain里面执行

    //具体显示
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            //程序开始执行  会输出这段语句
            NSLog(@"------------------");
            //可以看出这里面是一直执行的  相当于一个死循环
            int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
            //程序结束了才会执行  会输出这段语句,在未结束之前这句话是不会执行的
            NSLog(@"++++++++++++++++++");
            return result;
        }
    }
    
    

    获取的方式

    //获取的两种方式
    
    1、这种为CFRunLoopRef中的
        CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
        CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    
    2、这种为NSRunLoop中的
        [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
        [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    

    相关类

    //相关的五个类
    
    1、CFRunLoopRef
        1、代表一个RunLoop对象
    2、CFRunLoopModeRef
        1、代表RunLoop的运行模式
            1、一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
            2、每次RunLoop启动时,只能指定其中一个 Mode,如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
            3、同一时刻只能进行一种模式
        2、苹果内部提供了五种模式
            1、kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
                1、App的默认Mode,通常主线程是在这个Mode下运行
            2、UITrackingRunLoopMode
                1、界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
            //这个通常用不到
            3、UIInitializationRunLoopMode
                1、在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
            //这个通常用不到
            4、GSEventReceiveRunLoopMode
                1、接受系统事件的内部 Mode
            5、kCFRunLoopCommonModes
                1、这是一个占位用的Mode,这个的话用语言很难表达,后面会看到实例中会使用到这里,大家仔细体会
    3、CFRunLoopSourceRef
        1、用来管理所有事件的事件源,包括自定义的事件,以及系统自带的事件。
        2、Source有两个版本:Source0 和 Source1
            1、Source0-----为用户主动触发的事件
            2、Source1-----通过内核和其他线程相互发送消息。
    4、CFRunLoopTimerRef
        1、基本上说的就是NSTimer,基本用法如下实例标示
    5、CFRunLoopObserverRef
        1、用来监听RunLoop的状态改变
        2、状态列表
            kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
            kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
            kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
            kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
            kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
            kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
            kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态
    

    补充

    一个RunLoop有很多Mode,一个Mode里面有很多得Source/Timer/Observer,但是同一时刻只能进行一种模式。 如图:

    实例

    • CFRunLoopTimerRef

    • 首先在我们的storyboard添加一个text view控件
      然后使用代码

    • 代码

      -(void)viewDidLoad {
          [super viewDidLoad];
          //在原来使用time的时候,我们是直接这样写的,它是直接添加到RunLoop的DefaultMode模式中去得,如果我们去滑动text view的时候,也就是说我们现在操作的是RunLoop的Tracking,因为在前面我们并没有把time添加到Tracking中去,那么滑动的时候是不会输出的,
          //创建time
          NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
          //在这里我们把time添加到Tracking中去,那么操作就会发现,默认的是时候它也会输出,滑动text view的时候他也会输出了
          [[NSRunLoop currentRunLoop]addTimer:time forMode:UITrackingRunLoopMode];
      }
      -(void)show{
          NSLog(@"%s",__func__);
      }
      

      把time添加到NSDefaultRunLoopMode模式下

      NSTimer *time = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
      //在这里的话可以看到,我们滑动的时候它并没有输出,因为我们forMode的为NSDefaultRunLoopMode,也就是通常主线程的这个Mode下运行
      [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
      

      把time添加到UITrackingRunLoopMode模式下

      NSTimer *time = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
      

    // 在这里的话可以看到,我们滑动的时候它才会输出,因为我们forMode的为UITrackingRunLoopMode,
    [[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode];
    看了上面两种是不是有种感觉为什么两者不能共存,那么下面这种就可以看到它们共存了 objc
    NSTimer *time = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 在这里的话可以看到,我们不管是程序启动还是滑动的时候它都会输出,因为我们forMode的为kCFRunLoopCommonModes,
    [[NSRunLoop currentRunLoop] addTimer:time forMode:kCFRunLoopCommonModes];
    ```

    • 补充

      关于定时器的话是有两种的一个是NSTime,但是它是会受RunLoop的模式所影响的,一个是GCD的定时器,它呢是不受RunLoop的模式所影响的,这里的话留给大家一个引子(GCD的定时器是如何不受RunLoop模式的影响),这个也是公司一般很爱问的一个问题。

    • CFRunLoopObserverRef

    • 代码

      - (void)viewDidLoad {
          [super viewDidLoad];
          /*
              第一个参数:指定如何给obsever分配存储空间
              第二个参数:需要监听的类型/kCFRunLoopAllActivities为全部
              第三个参数:是否每次都监听
              第四个参数:优先级
              第五个参数:监听状态改变之后的回调函数
           */
          CFRunLoopObserverRef obsever = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      //        kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
      //        kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
      //        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
      //        kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
      //        kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
      //        kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
      //        kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态
              switch (activity) {
                  case kCFRunLoopEntry:
                      NSLog(@"即将进入Loop");
                      break;
                  case kCFRunLoopBeforeTimers:
                      NSLog(@"即将处理 Timer");
                      break;
                  case kCFRunLoopBeforeSources:
                      NSLog(@"即将处理 Source");
                      break;
                  case kCFRunLoopBeforeWaiting:
                      NSLog(@"即将进入休眠");
                      break;
                  case kCFRunLoopAfterWaiting:
                      NSLog(@"刚从休眠中唤醒");
                      break;
                  case kCFRunLoopExit:
                      NSLog(@"即将退出Loop");
                      break;
                  default:
                      break;
              }
          });
          //给主线程的RunLoop添加一个观察者
          /*
           第一个参数:需要给那个RunLoop添加观察者
           第二个参数:需要添加的observer
           第三个参数:在那种模式下监听
           */
          CFRunLoopAddObserver(CFRunLoopGetMain(), obsever,kCFRunLoopDefaultMode );
          CFRelease(obsever);        
      }
      
    • 补充

      这里的话如果打印出来,是会具有很多time和Source的,因为苹果内部进行了一系列的调用,那么大家可以明显的看到,这里是如何监听RunLoop状态是如何改变的,最后一定要记得去release,因为ARC无法释放Core Foundation 框架中的Create、Copy、Release

                     本章到此结束
               欢迎各位码友随意转载并指正
  • 相关阅读:
    nginx-1.8.1的安装
    ElasticSearch 在3节点集群的启动
    The type java.lang.CharSequence cannot be resolved. It is indirectly referenced from required .class files
    sqoop导入导出对mysql再带数据库test能跑通用户自己建立的数据库则不行
    LeetCode 501. Find Mode in Binary Search Tree (找到二叉搜索树的众数)
    LeetCode 437. Path Sum III (路径之和之三)
    LeetCode 404. Sum of Left Leaves (左子叶之和)
    LeetCode 257. Binary Tree Paths (二叉树路径)
    LeetCode Questions List (LeetCode 问题列表)- Java Solutions
    LeetCode 561. Array Partition I (数组分隔之一)
  • 原文地址:https://www.cnblogs.com/ljy-666/p/5135997.html
Copyright © 2020-2023  润新知