iOS开发之Runloop

学习了关于iOS开发中Runloop的基本用法,记录下笔记。
学习阶段,如果哪部分有问题,欢迎指正,共同探讨。@Apach3


什么是Runloop

顾名思义,Runloop是指运行时循环(死循环)。现在在iOS 开发中基本用不到,但在早期程序员都会用到。可以看作是一个比较高级但while循环,可以节省CPU时间,不用CPU一直去处理你的事件。


Runloop开启的目的

  1. 保证程序不退出
  2. 负责监听事件(触摸事件、时钟事件、网络事件)
  3. 如果没有事情发生则会进入休眠

UIApplication函数验证Runloop的存在

iOS开发之Runloop_第1张图片

上图是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语句便会不停循环。


Runloop的构成

iOS开发之Runloop_第2张图片

RunLoopMode 有5种,一个时刻RunLoop 只能处在一种模式下

(1)NSDefaultRunLoopMode - 默认模式(苹果建议:默认模式下放时钟和网络事件)
(2) UITrackingRunLoopMode - UI模式,优先级最高,只能被UI事件唤醒,放用户交互事件。
(3) NSRunLoopCommonModes - 并不是真正的Runloop模式,是在默认模式和UI模式的占位模式。
(4) UIInitializationRunLoopMode - 刚启动进入的模式。
(5) GSEventReceiveRunLoopMode - 接收系统内核事件的模式。


CFRunLoopTimer的使用

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的使用

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的使用

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

加油!

你可能感兴趣的:(ios开发)