美文网首页
RunLoop学习笔记

RunLoop学习笔记

作者: LD_左岸 | 来源:发表于2017-12-16 19:54 被阅读34次

    一.
    主线程RunLoop:
    保证App不退出 负责监听所有的事件
    (触摸事件)(网络事件)(定时器事件)无事件 RunLoop睡觉😴 RunLoop在同一时间 只能响应一种模式下的事件
    例如:
    拖拽textView时 NSTimer事件会停止 为什么停??
    {
    RunLoop的mode 每个model处理三种事件 Source Obsever Timer 如下图:


    61D03D52-39B5-46DB-9370-8579836832F3.png

    <<RunLoop在同一时间 只能响应一种模式下的事件
    RunLoop有多种模式 哪种模式下有事件 就去那种模式处理 当既有Default模式下的定时器事件 又有Track模式下的UI拖拽事件时>>
    UITrackingRunLoopMode优先 所以定时器停了
    }
    怎么让定时器不停??
    {

    1. 那么把定时器的代码改成如下


      D138D073-1F87-4EED-B36C-6F2B550BD219.png

      是否可以??
      运行程序 发现不能行 因为
      UITrackingRunLoopMode只有UI事件才能触发
      也就是只有当你拖动 触摸屏幕时 主线程的RunLoop切换到UITrackingRunLoopMode
      RunLoop才能处理你的定时器事件
      主线程的RunLoop默认在NSDefaultRunLoopMode下 所以运行上图代码 定时器不能行

    2.那么把代码改成如下图 为什么行??


    B677B9FA-1732-4266-A4A8-32CAAD1DC0C6.png

    NSRunLoopCommonModes = NSDefaultRunLoopMode && UITrackingRunLoopMode CommonModes更像一个集合NSSet 里面包含了Default和Tracking模式 所以当你运行程序不触摸屏幕时 主线程RunLoop模式是Default定时器可以
    拖动textView即使此时主线程的RunLoop切换到Tracking了 定时器依然好使
    }
    二.
    当不涉及到多线程时 以上方案是OK的 但是毕竟他们全是在主线程处理事情 而主线程时不能被卡住的。


    0E2A02B1-1BE4-46C1-BB02-2299EB0FAF6F.png

    如上图
    假如定时器里有耗时操作呢?主线程是不是卡住了 那么此时拖拽textView就比较卡顿 因为主线程在处理定时器里的耗时操作。
    所以要开子线程

    三.
    引入常驻线程
    之前对常驻线程的理解如下
    RunLoop的常驻线程就是 开一个子线程 而且这个子线程不会做完一个任务后就消亡 可以一直在这个特定的子线程里做一些事情

    生成这么一个线程的方式 就是两行代码
    [[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop]run];
    

    这样就保证了这个子线程可以一直做事情 而不消亡
    但是这有什么具体的应用场景呢
    目前想到的是 可以在

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    }
    开启这么一个子线程 一直请求百度网址 用来实时监控app的联网状态
    
    但是我这么做不也可以吗
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            while (1) {
                NSLog(@"一直请求百度网址检测联网情况..");
            }
        });
    
    }
    

    就是这个runloop的常驻线程 具体有哪些 非他不可 的事情呢
    就是具体实战怎么用
    应用场景是什么 就是这种场景下 不用Runloop的常驻线程就解决不了 或者效果没常驻线程的效果好
    四.
    经过CocoChina大神jian8138指点
    先对常驻线程总结如下


    160BA9D7-B6EA-4C09-86DE-06F1BF601E04.png

    如上图 这么写 定时器的@selector(update)方法是不执行的
    为什么不执行??
    子线程中的RunLoop默认是不开启的
    当线程创建完_timer后就消亡了而定时器的事件是注册到RunLoop中的所以RunLoop并没给你去处理定时器的事件

    419C024F-F3B6-436F-9B2C-B9CD30B19C8D.png

    当我调用_timer 的fire方法时 可以有一次打印 这个是把定时器的事件处理提前了和RunLoop没关系
    五.
    如果让上图的定时器一直打印 需要引入RunLoop的常驻线程

    ABB56E01-E8E4-4C50-B200-33BB8DE1C9AE.png

    六.
    Runloop的常驻线程可以帮我们在子线程中不阻塞地监听处理各种事件。在子线程中运行定时器的同时,还要有一个按钮,一点击按钮,就要在这条子线程中处理一个事件(例如NSLog一下)。

    21_564146_476e88e00ecc10f.png

    七.
    对于二中的问题可以从下图解决了 利用常驻线程 把耗时操作放到子线程中去做 主线程的拖拽事件(在主线程的RunLoop<Tracking模式>)不受影响。


    E3F4CD12-56A0-4C75-8C71-34E8C4867309.png

    每一条线程上面都有一个RunLoop
    主线程的Runloop默认是开启的
    子线程的Runloop默认不开启 执行完任务就挂掉 节约cpu
    如果让他不挂 就要给子线程Run RunLoop让他死循环
    那么此时这个子线程还会被销毁吗?
    NO
    直到进程销毁!
    如果满足了某个条件
    让这个常驻线程挂掉 怎么搞?
    1.直接杀死线程
    [NSThread exit]; 在常驻线程调。。。
    2.控制子线程的生命
    直接操纵RunLoop
    代码实现如下


    873E86B9-B758-452F-83E4-CB7C9B25A45D.png
    3.或者如果你能确定让这个RunLoop运行确定的时间再推出的话就可以直接这么写
     [[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        NSDate*nowDate = [NSDate dateWithTimeIntervalSinceNow:5];
        [[NSRunLoop currentRunLoop]runUntilDate:nowDate];
    五秒之后 RunLoop退出 子线程挂掉 在子线程处理事件不被允许...
    

    RunLoop的模式里的处理的三种事件:

    1. Source 《输入源 事件源 Timer某种意义上来说 也是一种Source 是Source中的一种》
      {
      所有的事件 都是Source
      可分两大类
      (1).source0
      开发者定义的事件。如: Button addTarget 用户点击
      (2).source1
      内核事件
      系统内核
      }
      所以-->source0 基于 source1
      2.Obsever 《RunLoop的观察者》
      3.Timer

    八.实际应用 在项目中 有哪些应用的场景
    性能优化之 ____ 更新UI如果是个耗时操作 需要RunLoop优化
    【链接】CFRunloop优化TableView加载高清大图UI卡顿问题。
    http://blog.csdn.net/ZY_FlyWay/article/details/72921158
    其实这篇文章说的已经相当详细了
    以下为班门弄斧的个人理解记录:
    主线程的Runloop 只是让着个runloop一次不要处理太多事件 给他分批次处理 ,观察runloop的状态 每次进入等待状态 丢一个imageView的任务给他 主线程的runloop是默认开启的 主线程肯定不会消亡 但是主线程的runloop会进入休眠runloop休眠了就不处理事件了 所以我还得搞个空循环 让主线程的runloop不进入休眠状态..........

    在每次RunLoop循环的时候 只渲染一张图片 而不是12张+的图片 这样一次RunLoop处理的事件越少 卡顿感就越弱
    

    具体实现如下

    #import "ViewController.h"
    #import "LDCell.h"
    typedef void(^runloopTask)(void);
    @interface ViewController ()<UITableViewDataSource,UITableViewDelegate>
    @property (nonatomic,strong) NSTimer *timer;
    @property (nonatomic,strong) UITableView *tableView;
    //存放任务的数组
    @property (nonatomic, strong) NSMutableArray *TaskMarr;
    //最大任务数 任务数据只保留展示到用户眼前的页面的任务
    @property (nonatomic, assign) NSInteger maxTasksNumber;
    //任务执行的代码块
    @property (copy, nonatomic) runloopTask task;
    @end
    static NSString * const cellID = @"cellID";
    @implementation ViewController
    
    #pragma mark-- 懒加载
    -(NSMutableArray *)TaskMarr{
        
        if (!_TaskMarr) {
            
            _TaskMarr = [NSMutableArray array];
        }
        
        self.maxTasksNumber  =  12;
        
        return _TaskMarr;
    }
    
    - (UITableView *)tableView
    {
        if (!_tableView) {
            _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
            [_tableView registerNib:[UINib nibWithNibName:@"LDCell" bundle:nil] forCellReuseIdentifier:cellID];
            _tableView.dataSource = self;
            _tableView.delegate = self;
        }
        return _tableView;
    }
    
    
    #pragma mark --- tableViewDataSource
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return 1000;
    }
    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        LDCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
        cell.ldLabel.text = [NSString stringWithFormat:@"这是第%ld行cell 😁😁😁",(long)indexPath.row];
    #if 0
        cell.ldLeftImageV.image = [UIImage imageNamed:@"666"];
        cell.ldRightImageV.image = [UIImage imageNamed:@"777"];
    #endif
        __weak  typeof(self) weakSelf = self;
        [self addTasks:^{
            [weakSelf addLeftImageView:cell];
        }];
        [self addTasks:^{
            [weakSelf addRightImageView:cell];
        }];
        return cell;;
    }
    //添加任务进数组保存
    -(void)addTasks:(runloopTask)taskBlock{
        
        [self.TaskMarr addObject:taskBlock];
        //超过每次最多执行的任务数就移出当前数组
        if (self.TaskMarr.count > self.maxTasksNumber) {
            
            [self.TaskMarr removeObjectAtIndex:0];
        }
        
    }
    - (void)addLeftImageView:(LDCell*)cell
    {
        cell.ldLeftImageV.image = [UIImage imageNamed:@"666"];
    }
    - (void)addRightImageView:(LDCell*)cell
    {
         cell.ldRightImageV.image = [UIImage imageNamed:@"777"];
    }
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return [UIScreen mainScreen].bounds.size.height / 6;
    }
    
    #pragma mark --系统回调
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self.view addSubview:self.tableView];
        [self addRunLoopObserver];
        //给runloop一个事件源,让Runloop不断的运行执行代码块任务。
        NSTimer * timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            //NSLog(@"..因为RunLoop没任务做时就会休眠 要让Runloop不断的运行,直到我的任务结束。..");
        }];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
    }
    //回调
    void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        ViewController * vcSelf = (__bridge ViewController *)(info);
        
        if (vcSelf.TaskMarr.count > 0) {
            //获取一次数组里面的任务并执行
            runloopTask  task  =  vcSelf.TaskMarr.firstObject;
            task();
            [vcSelf.TaskMarr removeObjectAtIndex:0];
        }else
        {
            return;
        }
        
        
    }
    
    - (void)addRunLoopObserver
    {
        CFRunLoopRef runloop = CFRunLoopGetCurrent(); //获取当前的RunLoop 返回的就是一个RunLoop对象 既可以运行在Default模式上也可以运行在UITracking模式上
        static CFRunLoopObserverRef defaultModelObserver;
        //定义上下文
        CFRunLoopObserverContext context = {
            0,
            (__bridge void *)(self),
            &CFRetain,
            &CFRelease,
            NULL
        };
        //创建观察者
        defaultModelObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &CallBack,&context);
        //添加当前RunLoop的观察者
        CFRunLoopAddObserver(runloop, defaultModelObserver, kCFRunLoopCommonModes);
        CFRelease(defaultModelObserver);
    }
    

    九.通过RunLoop的NSMachPort进行线程间通信 虽然我并不知道费劲巴列的这么干有🐱用<用GCD一句代码就实现了>但是是可以这么玩耍的....
    这么玩的Demo
    参考http://url.cn/59QSg7Y
    鉴于 能力有限 水平一般 理解有误之处 望大侠不吝指教
    ThankYou😁

    相关文章

      网友评论

          本文标题:RunLoop学习笔记

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