浅谈Runloop

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

----未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • 转载自深入理解RunLoop 一、RunLoop概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退...
    夏天的风_song阅读 375评论 0 0
  • 先贴下 apple doc, 本文基本是对照 doc 的翻译:https://developer.apple.co...
    brownfeng阅读 6,833评论 8 111
  • 前言 最近离职了,可以尽情熬夜写点总结,不用担心第二天上班爽并蛋疼着,这篇的主角 RunLoop 一座大山,涵盖的...
    zerocc2014阅读 12,363评论 13 67
  • 一、什么是runloop 字面意思是“消息循环、运行循环”。它不是线程,但它和线程息息相关。一般来讲,一个线程一次...
    WeiHing阅读 8,106评论 11 111
  • 当一只活泼机敏的猫忍住口馋死死地护着一只安静的鸟,耐心的看它梳理羽毛,悄悄又乖巧的像个歌剧观众期待又暧昧的迫切等待...
    晏追阅读 339评论 0 0