浅谈Runloop

作者: 小湾子 | 来源:发表于2017-09-29 19:50 被阅读1146次

    Runloop是做什么的

    Runloop顾名思义,运行着的循环,它保证我们的线程在有任务的时候执行任务,没有任务的时候处于休眠状态,比如我们的主线程,main函数里调用UIApplicationMainrunloop方法默认给我们的主线程创建了一个Runloop,从而程序不会被退出。

    int main(int argc, char * argv[]) {
    @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
    }
    

    我们runloop一直处于一个循环之中
    在这个循环中Runloop做的几件事情
    保证程序不退出
    负责监听事件: 触摸(UI界面的交互),时钟,网络事件.
    负责渲染屏幕上的所有UI(一次RunLoop循环需要渲染屏幕上所有UI变化的点!)

    Runloop与timer

    CFRunloopTimerRef是基于时间的触发器,通常就是指NSTimer.

    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
    - (void)updataTimer {
    static int num = 0;
    NSLog(@"%@  %d",[NSThread currentThread],num++);
    }
    

    我们发现,当滑动页面上的scrollView时,timer就停止了,这是为什么呢?看下图:


    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    Runloop包括 以下四种Mode:
    NSDefalutRunLoopMode      默认状态.空闲状态
    UITrackingRunLoopMode     滑动ScrollView
    UIInitializationRunLoopMode    私有,App启动时
    NSRunLoopCommonModes     默认包括上面第一和第二

    由于NSTimer默认被加载到了NSDefalutRunLoopMode下,当滑动scrollView时Runloop切换到了TrackingMode下,因此NSTimer就停止执行了,只要我们把timer加到Defalut和Tracking两种Mode下,timer就可以一直打印了:

    NSTimer * timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(updataTimer) userInfo:nil repeats:YES];
    //加入到RunLoop
    /*
    NSDefaultRunLoopMode  -- 时钟,网络事件
    NSRunLoopCommonModes  -- 用户交互模式:UI处理!
    */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    Runloop与source

    CFRunloopSourceRef 输入源
    source0:非基于port的,比如触摸事件,UI事件
    source1:基于基于port的,处理系统系统的一些事件,一般是由内核发出的,比如网络请求,蓝牙等

    Runloop与Observer

    CFRunloopObserverRef观察者,可以监听到Runloop的状态改变:


    observer.jpeg

    我们可以在Runloop特定的状态下去处理一些事件。

    Runloop启动必须得有运行模式Mode,使用默认的Mode也可以自定义Mode,并且在运行模式中必须得有timer或者source事件,否则Runloop就会退出

    RunLoop优化tableView

    前面我们已经对Runloop有一个大致的了解,那么Runloop在实际开发中有什么用途呢?
    下面我们思考一个问题:tableview加载 大量 的 大 图,当我们滑动tableview的时候,为什么会出现卡顿现象呢?
    这是由于一次Runloop循环需要加载屏幕上的所有点(包括所有图片),由于图片很 大 ,以至于这次循环有点久,Runloop循环有点久,就造成了卡顿的现象。
    头脑风暴:既然Runloop一次加载屏幕上所有点很耗时,那我们能不能一次runloop只加载一张图片呢?这么做,当然是可以的!!!

    思路:
    创建一个数组,放任务
    返回cell的数据源方法,不加载图片--(加载图片的代码放在数组中)
    监听Runloop循环,一次循环就从数组中拿代码执行

    下边是代码实现:

    //
    //  YMWLoadBigImageViewController.m
    //  test
    //
    //  Created by 袁明湾 on 2017/9/30.
    //  Copyright © 2017年 yuanmingwan. All rights reserved.
    //
    
    #import "YMWLoadBigImageViewController.h"
    
    //定义block
    typedef BOOL(^RunloopBlock)(void);
    
    static NSString * IDENTIFIER = @"IDENTIFIER";
    static CGFloat CELL_HEIGHT = 135.f;
    
    @interface YMWLoadBigImageViewController () <UITableViewDataSource,UITableViewDelegate>
    
    @property (nonatomic, strong) UITableView *exampleTableView;
    
    @property(nonatomic,strong)NSTimer * timer;             //时钟事件
    @property(nonatomic,strong)NSMutableArray * tasks;      //数组
    @property(assign,nonatomic)NSUInteger maxQueueLength;   //最大任务量
    
    @end
    
    @implementation YMWLoadBigImageViewController
    
    - (void)timerMethod{
        //任何事情都不做!!!
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.exampleTableView = [UITableView new];
        self.exampleTableView.delegate = self;
        self.exampleTableView.dataSource = self;
        [self.view addSubview:self.exampleTableView];
        
        _timer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        
        //注册Cell
        [self.exampleTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:IDENTIFIER];
        //添加RunLoop的监听
        [self addRunloopObserver];
        
        _maxQueueLength = 18;
        _tasks = [NSMutableArray array];
        
    }
    
    // 设置tableview大小
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        self.exampleTableView.frame = self.view.bounds;
    }
    
    #pragma mark cell的UI布局
    
    //添加文字
    + (void)addlabel:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath{
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 300, 25)];
        label.backgroundColor = [UIColor clearColor];
        label.textColor = [UIColor redColor];
        label.text = [NSString stringWithFormat:@"%zd - 优先绘制索引", indexPath.row];
        label.font = [UIFont boldSystemFontOfSize:13];
        label.tag = 4;
        [cell.contentView addSubview:label];
        
        UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(5, 99, 300, 35)];
        label1.lineBreakMode = NSLineBreakByWordWrapping;
        label1.numberOfLines = 0;
        label1.backgroundColor = [UIColor clearColor];
        label1.textColor = [UIColor colorWithRed:0 green:100.f/255.f blue:0 alpha:1];
        label1.text = [NSString stringWithFormat:@"%zd - 绘制大图,放在不同的运行循环", indexPath.row];
        label1.font = [UIFont boldSystemFontOfSize:13];
        label1.tag = 5;
        [cell.contentView addSubview:label1];
        
    }
    
    
    //加载第一张
    + (void)addImage1With:(UITableViewCell *)cell{
        //第一张
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 20, 85, 85)];
        imageView.tag = 1;
        NSString *path1 = [[NSBundle mainBundle] pathForResource:@"largeImage" ofType:@"png"];
        UIImage *image = [UIImage imageWithContentsOfFile:path1];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.image = image;
        [UIView transitionWithView:cell.contentView duration:0.3 options:(UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionCrossDissolve) animations:^{
            [cell.contentView addSubview:imageView];
        } completion:nil];
    }
    
    
    
    //加载第二张
    + (void)addImage2With:(UITableViewCell *)cell{
        //第二张
        UIImageView *imageView1 = [[UIImageView alloc] initWithFrame:CGRectMake(105, 20, 85, 85)];
        imageView1.tag = 2;
        NSString *path1 = [[NSBundle mainBundle] pathForResource:@"largeImage" ofType:@"png"];
        UIImage *image1 = [UIImage imageWithContentsOfFile:path1];
        imageView1.contentMode = UIViewContentModeScaleAspectFit;
        imageView1.image = image1;
        [UIView transitionWithView:cell.contentView duration:0.3 options:(UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionCrossDissolve) animations:^{
            [cell.contentView addSubview:imageView1];
        } completion:nil];
    }
    //加载第三张
    +(void)addImage3With:(UITableViewCell *)cell{
        //第三张
        UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(200, 20, 85, 85)];
        imageView2.tag = 3;
        NSString *path1 = [[NSBundle mainBundle] pathForResource:@"largeImage" ofType:@"png"];
        UIImage *image2 = [UIImage imageWithContentsOfFile:path1];
        imageView2.contentMode = UIViewContentModeScaleAspectFit;
        imageView2.image = image2;
        [UIView transitionWithView:cell.contentView duration:0.3 options:(UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionCrossDissolve) animations:^{
            [cell.contentView addSubview:imageView2];
        } completion:nil];
    }
    
    #pragma mark - tableview
    
    //Cell 高度
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return CELL_HEIGHT;
    }
    
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 399;
    }
    
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:IDENTIFIER];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
        
        
        // remove contentView上面的子控件!! 节约内存!!
        for (NSInteger i = 1; i <= 5; i++) {
            
            [[cell.contentView viewWithTag:i] removeFromSuperview];
        }
        //添加文字
        [YMWLoadBigImageViewController addlabel:cell indexPath:indexPath];
        
        //添加图片  -- 耗时操作 丢给每一次RunLoop循环
        [self addTask:^BOOL{
            [YMWLoadBigImageViewController addImage1With:cell];
            return YES;
        }];
        [self addTask:^BOOL{
            [YMWLoadBigImageViewController addImage2With:cell];
            return YES;
        }];
        [self addTask:^BOOL{
            [YMWLoadBigImageViewController addImage3With:cell];
            return YES;
        }];
        
        
        //    [ViewController addImage2With:cell];
        //    [ViewController addImage3With:cell];
        
        return cell;
    }
    
    #pragma mark - <关于RunLoop的方法>
    // 添加新的任务的方法!
    - (void)addTask:(RunloopBlock)unit {
        
        [self.tasks addObject:unit];
        
        // 判断一下 保证没有来得及显示的cell不会绘制图片!!
        if (self.tasks.count > self.maxQueueLength) {
            [self.tasks removeObjectAtIndex:0];
        }
        
        
    }
    
    
    // 回调函数
    static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
        // 从数组里面取代码!! info 就是 self
        YMWLoadBigImageViewController * vc = (__bridge YMWLoadBigImageViewController *)info;
        if (vc.tasks.count == 0) {
            return;
        }
        BOOL result = NO;
        while (result == NO && vc.tasks.count) {
            //取出任务
            RunloopBlock unit = vc.tasks.firstObject;
            //执行任务
            result = unit();
            //干掉第一个任务
            [vc.tasks removeObjectAtIndex:0];
        }
        
    }
    
    // 这里面都是c语言的代码
    - (void)addRunloopObserver {
        // 获取当前RunLoop
        CFRunLoopRef runloop = CFRunLoopGetCurrent();
        // 定义一个上下文
        CFRunLoopObserverContext context = {
            0,
            (__bridge void *)(self),
            &CFRetain,
            &CFRelease,
            NULL,
        };
        // 定义一个观察者
        static CFRunLoopObserverRef defaultModeObserver;
        // 创建观察者
        defaultModeObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, NSIntegerMax - 999, &Callback, &context);
        // 添加当前RunLoop的观察者
        CFRunLoopAddObserver(runloop, defaultModeObserver, kCFRunLoopDefaultMode);
        // C语言里面有Creat\new\copy 就需要 释放 ARC 管不了!!
        CFRelease(defaultModeObserver);
        
    }
    
    
    @end
    
    

    ----未完待续

    相关文章

      网友评论

      本文标题:浅谈Runloop

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