浅谈Runloop

Runloop是做什么的

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

int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
浅谈Runloop_第1张图片

我们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_第2张图片

一个 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的状态改变:


浅谈Runloop_第3张图片
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 () 

@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)