runLoop的运用

   runLoop称之为运行循环。我们知道应用程序刚启动的时候就会自动调用main函数,然后main函数中直接返回return,从代码层面上理解,既然renturn了,我们程序为什么一直没有退出呢?这就是runLoop做的事了,开启了一个runLoop,相当于一个死循环,保证了程序不退出。return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));


runLoop可以运用在 Timer(NSTimer)、Source(按照函数调用栈方式分为source0和source1,source1内核事件,系统底层事件,souce0非source1事件,例如触摸事件touchesBegan。source可以运用在GCD中)上、  Obsever(CFRunLoopObserver)上。


  runLoop负责处理事件: a.触摸事件(用户操作UI界面响应的相关事件)。b.时钟事件。例如NSTimerc.网络事件。runLoop可以这样理解,当没有事件做的时候,处于睡眠状态。当有事件处理时又处于活跃状态,处理完事件后又处于睡眠状态这样响应模式。

runLoop模式有:

UITrackingRunLoopMode    UI模式  处理UI事件

UIDefaultRunLoopMode   默认模式  苹果建议处理网络和时钟事件。

NSRunLoopCommonModes  占位模式    同时运行在UI模式和默认模式

优先级:UI模式 >默认模式。

通过上面的介绍,觉得runLoop比较好理解,但是用起来还是挺复杂的,因为runLoop与多线程有密不可分关系,runLoop用在多线程上面会有些难度。每一条线程都要一个runLoop。当你开启一个线程时,如果你不运行runLoop,线程就会被回收,线程里面做的相关处理就不会执行。所以如果需要在线程中保持相关的事件处理,那么线程中runLoop应处理运行状态,一旦runLoop失去了运行状态,线程中相关时钟操作就不会执行了

说这么多,下面我们结合代码分别介绍runLoop运用在 Timer(NSTimer)、Source(例如GCD)上、  Obsever(CFRunLoopObserver)上


一、runLoop时钟事件上运用。

NSTimer * timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(updataTimer)userInfo:nilrepeats:YES];

[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];

- (void)updataTimer {

    staticint num =0;

    [NSThreadsleepForTimeInterval:1.0];

    NSLog(@"%@ %d",[NSThreadcurrentThread],num++);

    

}

这样我们通过RunLoop就启动了timer,但是我们会发现,当操作UI界面上的控件时,定时器的方法就没有执行了,什么原因呢?原来是由于当你操作UI界面时,runLoop去处理UI界面事件,所以时钟事件runLoop就没有处理啦,那么想要操作UI的同时,时钟事件也同时响应。这就用到了我们前面介绍的runLoop模式,当添加timer时,这样写:

 [[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];//那么runLoop执行UI同时执行时钟事件,这样就满足操作UI的同时,时钟事件也同时响应啦


二.runLoop的source(关于线程)使用。

每一条线程中runLoop默认是没有关闭的,所以我们需要手动开启runLoop(记住:要想保住一条线程,必须开启的他的runLoop)

下面直接代码复制参考:

@interface ViewController (){

    NSThread *_thread;

    BOOL _finishd;

}


@end


@implementation ViewController


- (void)viewDidLoad {

    [superviewDidLoad];

    _finishd =NO;

    NSThread *thread = [[NSThreadalloc]initWithBlock:^{

        NSTimer *timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(timerMethod)userInfo:nilrepeats:YES];

        [[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];

        while (!_finishd) {

            [[NSRunLoopcurrentRunLoop]runUntilDate:[NSDatedateWithTimeIntervalSinceNow:0.001]];

        }

    }];

    //启动线程

    [thread start];

    _thread = thread;

}


- (void)timerMethod {

    NSLog(@"come here");

    [NSThreadsleepForTimeInterval:1.0];

    staticint num;

    num ++;

    NSLog(@"%d---%@",num,[NSThreadcurrentThread]);

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    _finishd =YES;

}


- (void)didReceiveMemoryWarning {

    [superdidReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

@end


测试得出结果:

 当我们没有点击屏幕没有执行touchesBegan方法时,runLoop0.001秒执行一次,_finishd一直为零,runLoop一直执行。

 当我们点击屏幕时候_finishdYES,!_finishd为真,runLoop就不再执行了,线程中的时钟事件就不再执行。

 补充一下runLoopsource中运用,即GCD中运用。

 首先在viewDidLoad中已经写了三个变量:

__weak IBOutletUILabel *numberLabel;

    dispatch_source_t _timer;

    int outTime;


我们快速的创建的一个GCD定时器

dispatch_queue_t queue =dispatch_get_global_queue(0,0);

    dispatch_source_t timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0, queue);

    _timer = timer;

    dispatch_source_set_timer(_timer,dispatch_walltime(NULL,0),1.0 *NSEC_PER_SEC,0);//每秒执行

    dispatch_source_set_event_handler(_timer, ^{

        NSLog(@"++++%d",outTime);

        if (outTime <=0) {

            dispatch_source_cancel(_timer);

            dispatch_async(dispatch_get_main_queue(), ^{

            numberLabel.text =@"0";

            });

        } else {

            NSString *timeString = [NSStringstringWithFormat:@"%d",outTime];

            dispatch_async(dispatch_get_main_queue(), ^{

                numberLabel.text = timeString;

                outTime--;

            });

        }

    });

    dispatch_resume(_timer);


输出结果:

2017-10-25 16:34:35.381 GCDTest[4456:1058917] ++++10

2017-10-25 16:34:36.382 GCDTest[4456:1058917] ++++9

2017-10-25 16:34:37.382 GCDTest[4456:1058917] ++++8

2017-10-25 16:34:38.382 GCDTest[4456:1058917] ++++7

2017-10-25 16:34:39.382 GCDTest[4456:1058917] ++++6

2017-10-25 16:34:40.382 GCDTest[4456:1058917] ++++5

2017-10-25 16:34:41.382 GCDTest[4456:1058917] ++++4

2017-10-25 16:34:42.383 GCDTest[4456:1058917] ++++3

2017-10-25 16:34:43.382 GCDTest[4456:1058917] ++++2

2017-10-25 16:34:44.383 GCDTest[4456:1058917] ++++1

2017-10-25 16:34:45.383 GCDTest[4456:1058917] ++++0

我们发现代码中没有一句写关于runLoop的,但是输出结果的同时拖动UI不会卡,这是什么原因呢?我们上面不是说每一条线程中都有一个runLoop吗?原来原因是GCDC书写的,GCD已经帮我们把runLoop相关工作做好了,不用的管理,这就是为什么GCD的好用指出,很多人都喜欢使用GCD


三.runLoop的Obsever的使用。

首先在 ViewController中定义

/** 存放任务的数组  */

@property(nonatomic,strong)NSMutableArray * tasks;

/** 任务标记

 */

@property(nonatomic,strong)NSMutableArray * tasksKeys;

/** 最大任务数 */

@property(assign,nonatomic)NSUInteger max;



/** timer  */

@property(nonatomic,strong)NSTimer * timer;


如果我们需要在tableView的每个cell上添加三张高清的大图。如下图:


如果我们直接在cell添加imageView[cell.contentView addSubview:imageView];发现拖动tableView的时候会非常卡。

 那是什么原因导致tableView的卡动呢?原来一次runLoop循环需要渲染界面上显示的很多张高清的大图,需要耗很多时间,导致UI操作卡顿现象。

 解决方案:我让一次runLoop循环只加载一张图片,runLoop就会变得很流畅,拖拽的时候响应事件就会变得很流畅,界面的渲染就会变得流畅,卡顿就不会变得那么明显。

 首先我们说让一次runLoop循环只加载一张图片,那么就需要知道runLoop的每次循环。在viewDidLoad里面

[selfaddRunloopObserver];

//当然这里面都是C语言 --添加一个监听者

-(void)addRunloopObserver{

    //获取当前的RunLoop

    CFRunLoopRef runloop =CFRunLoopGetCurrent();

    //定义一个centext

    CFRunLoopObserverContext context = {

        0,

        ( __bridgevoid *)(self),

        &CFRetain,

        &CFRelease,

        NULL

    };

    //定义一个观察者

    staticCFRunLoopObserverRef defaultModeObsever;

    //创建观察者

    defaultModeObsever = CFRunLoopObserverCreate(NULL,

                                                 kCFRunLoopBeforeWaiting,

                                                 YES,

                                                 NSIntegerMax -999,

                                                 &Callback,

                                                 &context

                                                 );

    

    //添加当前RunLoop的观察者

    CFRunLoopAddObserver(runloop, defaultModeObsever,kCFRunLoopCommonModes);

    //c语言有creat就需要release

    CFRelease(defaultModeObsever);

}


Callback这个函数是为addRunloopObserver方法提供回调的。

//MARK: 回调函数

//定义一个回调函数 一次RunLoop来一次

static void Callback(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){

    ViewController * vc = (__bridgeViewController *)(info);

    if (vc.tasks.count ==0) {

        return;

    }

    BOOL result =NO;

    while (result ==NO && vc.tasks.count) {

        //取出任务

        RunloopBlock unit = vc.tasks.firstObject;

        //执行任务

        result = unit();

        //干掉第一个任务

        [vc.tasksremoveObjectAtIndex:0];

        //干掉标示

        [vc.tasksKeysremoveObjectAtIndex:0];

    }

    

}


最后我们在

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

 方法中并不是直接添加在cell.contentView上添加imageView,而是定义一个含有block的一个方法,cell上添加图片放入这个block中执行,每一次runLoop循环执行一个block(这个block代码块就是往cell上天机imageView)。含有block的方法如下:

//MARK: 添加任务

-(void)addTask:(RunloopBlock)unit withKey:(id)key{

    [self.tasksaddObject:unit];

    [self.tasksKeysaddObject:key];

    //保证之前没有显示出来的任务,不再浪费时间加载

    if (self.tasks.count > self.max) {

        [self.tasksremoveObjectAtIndex:0];

        [self.tasksKeysremoveObjectAtIndex:0];

    }

    

}


 最后测试滑动tableView就不再像之前那么卡顿了,因为我们让一次runLoop循环执行一个block只加载一张图片。

 注意的是runLoopObsever模式情况下,runloop对应的是CFRunLoopRef,是C语言的,监听关于runLoop执行的代码用C来写的。




        






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值