学习了关于iOS开发中Runloop的基本用法,记录下笔记。
学习阶段,如果哪部分有问题,欢迎指正,共同探讨。@Apach3
顾名思义,Runloop是指运行时循环(死循环)。现在在iOS 开发中基本用不到,但在早期程序员都会用到。可以看作是一个比较高级但while循环,可以节省CPU时间,不用CPU一直去处理你的事件。
上图是App中的main.m文件,解读以下关键字:
(1) argc
– 参数数量 argv
- 参数值
(2) @autoreleasepool {……}
- 自动释放池
(3) return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- 告诉程序APP的单例对象是谁。
(4) nil
- 可转换成@"UIApplication"
,UIApplication
内有方法regisiterAsSystemApp
,写nil是默认为UIApplication
,如果在App启动前希望拦截系统事件,可以在nil
部分填写UIApplication
或者它的子类。
(5) return ……;
- 在return里主线程开启Runloop,又因为Runloop是死循环,所以main内只执行到return语句便会不停循环。
RunLoopMode
有5种,一个时刻RunLoop 只能处在一种模式下
(1)NSDefaultRunLoopMode
- 默认模式(苹果建议:默认模式下放时钟和网络事件)
(2) UITrackingRunLoopMode
- UI模式,优先级最高,只能被UI事件唤醒,放用户交互事件。
(3) NSRunLoopCommonModes
- 并不是真正的Runloop模式,是在默认模式和UI模式的占位模式。
(4) UIInitializationRunLoopMode
- 刚启动进入的模式。
(5) GSEventReceiveRunLoopMode
- 接收系统内核事件的模式。
CFRunLoopTimer
:管理定时器的调度。
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithBlock:^{
//每个线程都有默认的Runloop,默认不开启
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];//开启后,便是一个死循环,如果没有[[NSRunLoop currentRunLoop] run]; 这个代码,我们这个thread线程会被回收
}];
[thread start];
}
- (void)timerMethod {
NSLog(@"Runloop运行循环");
}
运行结果可以看到无论屏幕上有什么UI操作,我们的NSTimer都会一直运行,满足我们的基本要求。
CFRunLoopSource
(事件源):
按照函数调用栈,source分两种
(1)Source0 - 非Source1就为Source0
(2)Source1 - 通过接受系统内核和其他线程间的通讯等其他的系统事件
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建一个定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置timer,GCD的时间单位是纳秒#define NSEC_PER_SEC 1000000000ull
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
//设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"%@", [NSThread currentThread]);
});
//启动
dispatch_resume(self.timer);
}
运行结果同样可以看到,无论我们进行什么UI操作,NSTimer都会执行,满足我们的要求。
CFRunLoopObserver
:观察者,观察当前RunLoop 在某个模式下的状态改变,监听Runloop循环。
举例:UI渲染优化
页面内有多个cell
,每个cell
内有数张高清大图,此时正常滚动由于图片太大,导致一次Runloop循环会加载数张大图,造成页面卡顿。
解决办法:每次Runloop循环只渲染一张图片,通过数次Runloop来加载。
#import "ViewController.h"
//定义block
typedef BOOL(^RunloopBlock)(void);
static NSString * IDENTIFIER = @"IDENTIFIER";
static CGFloat CELL_HEIGHT = 135.f;
@interface ViewController ()<UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *exampleTableView;
/** 时钟事件 */
@property(nonatomic,strong)NSTimer * timer;
/** 数组 */
@property(nonatomic,strong)NSMutableArray * tasks;
/** 最大任务s */
@property(assign,nonatomic)NSUInteger maxQueueLength;
@end
@implementation ViewController
//因为消息发送机制!!那么我们可以优化!! 直接用底层代码!哥么发消息!不用OC调用方法!!
//Runtime 里面讲!!
-(void)timerMethod{
//任何事情都不做!!!
}
- (void)viewDidLoad {
[super viewDidLoad];
_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];
}
//MARK: 内部实现方法
//添加文字
+(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 - Drawing index is top priority", 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 - Drawing large image is low priority. Should be distributed into different run loop passes.", 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:@"spaceship" 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:@"spaceship" 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:@"spaceship" 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];
}
//MARK: UI初始化方法
//设置tableview大小
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.exampleTableView.frame = self.view.bounds;
}
//Cell 高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return CELL_HEIGHT;
}
//加载tableview
- (void)loadView {
self.view = [UIView new];
self.exampleTableView = [UITableView new];
self.exampleTableView.delegate = self;
self.exampleTableView.dataSource = self;
[self.view addSubview:self.exampleTableView];
}
#pragma mark -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 399;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:IDENTIFIER];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//干掉contentView上面的子控件!! 节约内存!!
for (NSInteger i = 1; i <= 5; i++) {
//干掉contentView 上面的所有子控件!!
[[cell.contentView viewWithTag:i] removeFromSuperview];
}
//添加文字
[ViewController addlabel:cell indexPath:indexPath];
//添加图片 -- 耗时操作!!! 丢给每一次RunLoop循环!!!
[self addTask:^BOOL{
[ViewController addImage1With:cell];
return YES;
}];
[self addTask:^BOOL{
[ViewController addImage2With:cell];
return YES;
}];
[self addTask:^BOOL{
[ViewController addImage3With:cell];
return YES;
}];
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){
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(@"%p %@",currentRunLoop, currentRunLoop.currentMode);
//从数组里面取代码
ViewController * vc = (__bridge ViewController *)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, kCFRunLoopCommonModes);
//C语言里面有Creat\new\copy 就需要 释放 ,ARC 管不了
CFRelease(defaultModeObserver);
}
@end
加油!