Runloop与线程

什么是runloop?

1、运行循环,run  loop 奔跑的循环

2、在程序运行过程中循环做一些事情

runloop的应用范畴?

1、定时器、PerformSelector

2、GCD Async Main Queue

3、事件响应、手势识别、界面刷新

4、网络请求

5、AutoReleaseTool

runloop的基本作用?
 

来看看这段代码~思考下:以下代码的输出是什么?

int main(int argc, char * argv[]) {

    @autoreleasepool {   

    NSLog(@"来了");

     nil 就相当于UIApplication

     int a = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

    NSLog(@"come here");

     return a;

    }

}

想好了吗?揭晓答案了哦~答案是“来了”,你猜对了吗?why?因为UIApplicationMain 有个死循环(runloop循环),

如果以上直接返回0话(没有runloop的情况),程序就会自动闪退

所以runloop的作用是

- 保证去程序不退出(保持程序的持续运行)

- 负责处理App中的各种事件,触摸,时钟,网络事件

- 节省cpu 的资源,提高cpu性能:如果没有事件发生,会让程序进入休眠状态

Runloop 对象的获取

 

RunLoop对象 OS中有2套API来访问和使⽤用RunLoop

Foundation:NSRunLoop
Core Foundation:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象

NSRunLoop是基于CFRunLoopRef的⼀一层OC包装 CFRunLoopRef是开源的

https://opensource.apple.com/tarballs/CF/

Foundation

[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象

[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象

CFRunLoopGetMain(); // 获得主线程的RunLoop对象

Runloop 的相关类

Core Foundation中关于RunLoop的5个类(CFRunLoopRef 是开源的)

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

 

CFRunLoopObserverRef

Source0

触摸事件处理理

performSelector:onThread:

Source1

基于Port的线程间通信

系统事件捕捉

Timers

NSTimer performSelector:withObject:afterDelay:

Observers

⽤用于监听RunLoop的状态

 UI刷新(BeforeWaiting)

Autorelease pool(BeforeWaiting)

 

 

typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {

    pthread_t _pthread;

    CFMutableSetRef _commonModes;

    CFMutabluSetRef _commonModeItems;

    CFRunLoopModeRef _currentMode;

    CFMutableSetRef _modes;

};

CFRunLoopModeRef 代表Rrunloop的运行模式,一个Runloo包含如干个Mode,每个Mode又包含若干个source0、source1、timer、observer,但是runloop启动只能选择其中一个mode,作为currentmode,runloop启动时只能选择一个mode 作为currentmode。当没有任何Source0/Source1/Timer/Observer,RunLoop会⽴立⻢马退出

 

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoop {

    CFStringRef _name;

    CFMutableSetRef _sources0;

    CFMutableSetRef _sources1;

    CFMutableArrayRef _observers;

    CFMutableArrayRef _times;

};

Runloop 在实际开发中的应用

1.控制线程生命周期(线程保活)

2.解决NSTimer在滑动时停止工作的问题

3.监控应用卡顿

4.性能优化

 

Runloop 的五种模式

1.UITrackingRunLoopMode UI模式

2.NSDefaultRunLoopMode 默认模式

3.NSRunLoopCommonModes 占位模式相当于 UI模式+ 默认模式

4.UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用

5.GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到

有这样一个场景 :界面上有个textview,当滚动textview,viewdidload 里的timer就不工作了

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];

 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

我们来解释下原因,请看下图

此时的timer在默认模式下,苹果的一贯注重用户体验,所以当滚动textview (用户操作UI)的时候,runloop 会优先处理UI模式下的source。如果这时吧计时器加入到UI模式下,timeMethod 会正常进行,但当用户停止操作UI,那么UI模式下的runloop 就会进行休眠,所以timeMethod也会停止运行。此时我们可以吧timer加入到UI模式+默认模式。这时苹果为了优化,产生了一个NSRunLoopCommonModes 占位模式,但是它并不属于Runloop的五大模式之一。

Runloop 与线程的关系

1. 每条线程都有唯一的与之对应的runloop对象

2.Runloop保存在一个全局的Dictionary里,线程作为key,Runloop作为value

3.线程刚创建的时候是没有runloop对象的,Runloop会在第一次获取他时创建,在main.m 获取当前runloop,要是有的话直接获取,没有的话会自动创建一个runloop的对象

4、runloop会在线程结束时销毁

5、主线程的run loop默认是启动的。子线程默认是没有runloop的

iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

  1. 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

  2. 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

RunLoop && 多线程

- (void)viewDidLoad {

    [super viewDidLoad];

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

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];

        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

        NSLog(@"来了1");

    }];

    [thread start];

}

- (void)timeMethod {

    NSLog(@"来了2");

    [NSThread sleepForTimeInterval:1];

    NSLog(@"%@",[NSThread currentThread]);

}

 

2018-05-16 10:05:46.915347+0800 测试完[841:26856] 来了1

 

原因:线程走了,想要保证线程的命,就要让线程有执行不完的任务,线程就不会释放了~

 

 

- (void)viewDidLoad {

    [super viewDidLoad];

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

 NSTime*timer=                    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nilrepeats:YES];

        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

       //这个循环只能保证线程的命,但是timeMethod 还是不能调用

       // while(true)

       //{

             //从事件队列中取出事件来处理!!(但是平没有开源这个,但是封装了一个NSRunLoop 来处理这个事件)

       // }

 

       //RunLoop -- 一条线程上面的RunLoop模式是不循环的

        //CFRunLoop -- currentRunLoop ()  第一次获取RunLoop的时候,创建RunLoop

        [[NSRunLoop currentRunLoop]run];//死循环

       NSLog(@"来了1");

    }];

    [thread start];

}

- (void)timeMethod {

    NSLog(@"来了2");

    [NSThread sleepForTimeInterval:1];

    NSLog(@"%@",[NSThread currentThread]);

}

打印结果如下:

 

2018-05-11 10:06:44.594863+0800 RunloopTest[1300:42091] 来了2

2018-05-11 10:06:45.599998+0800 RunloopTest[1300:42091] <NSThread: 0x60400047df80>{number = 4, name = (null)}

主线程与子线程做的事情都是一样的,唯一的区别是:UI界面在主线上面的;App启动的第一条线程

苹果为啥要吧UI界面放在主线程?为了安全+效率

三,CFRunloop 的一个用法

 

 

#import "ViewController.h"

 

typedef void(^RunloopBlock)(void);

 

@interface ViewController ()

@property(strong,nonatomic)NSMutableArray *tasks;

@end

 

@implementation ViewController

//这里在cell 上加载图片的耗时操作没写

/*分析卡顿的原因:

所有的Cell的加载都在主线程的一次Runloop循环中,UI渲染也属于Runloop的事情,但是一次渲染18张图片,渲染太多。导致卡顿

解决思路:一次Runloop循环,只加载一张图片

步骤:

 1.观察(observer)Runloop的循环

 2.一次Runloop循环,加载一张图片

  |-Cell加载图片的方法放到数组里

  |-Runloop循环 一次,就从数组取出一个图片加载

*/

- (void)viewDidLoad {

    [self addRunloopObserver];

    self.tasks = NSMutableArray.array;

    [self addTask:^{

       

    }];

}

- (void)addTask:(RunloopBlock)task{

    //保存任务到数组

    [self.tasks addObject:task];

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

        return;

    }

    if (self.tasks.count>18) {

        [self.tasks removeObjectAtIndex:0];

    }

}

- (void)addRunloopObserver{

    //获取当前的runloop

    CFRunLoopRef runloop = CFRunLoopGetCurrent();

    //定义上下文

    /*

     typedef struct {

     CFIndex    version;

     void *    info;

     const void *(*retain)(const void *info);

     void    (*release)(const void *info);

     CFStringRef    (*copyDescription)(const void *info);

     } CFRunLoopObserverContext;

     */

    

    CFRunLoopObserverContext context = {

        0,

        (__bridge void *)self,

        &CFRetain,

        &CFRelease,

        NULL

    };

    

    //创建观察者

    //c 中create new copy 堆区开辟内存空间。需要释放

    CFRunLoopObserverRef  runLoopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callback, &context);

    CFRunLoopAddObserver(runloop, runLoopObserver, kCFRunLoopCommonModes);

    //释放

    CFRelease(runloop);

}

 

void callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){

    ViewController *vc =  (__bridge ViewController *)info;

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

        return;

    }

    RunloopBlock task = vc.tasks.firstObject;

    task();

    [vc.tasks removeObjectAtIndex:0];

};

 


 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值