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的状态改变:
我们可以在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
----未完待续