RunLoop学习笔记

2017-12-16  本文已影响34人  LD_左岸

一.
主线程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😁

上一篇下一篇

猜你喜欢

热点阅读