RunLoop

2023-04-10  本文已影响0人  冰棍儿好烫嘴
什么是RunLoop

RunLoop:运行循环,在程序运行过程中循环做一些事情
应用范畴:
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool

如果没有RunLoop
执行完第18行代码后,会即将退出程序

如果有了RunLoop

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {

    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
UIApplicationMain中会创建RunLoop对象,RunLoop中的伪代码实现大概是如下的代码:
int retVal = 0;
do{
  //睡眠中等待消息
  int message = sleep_and_wait();
  //处理消息
  retVal = process_message(message);
}while(0 == retVal);
RunLoop对象
RunLoop与线程
获取RunLoop对象
RunLoop相关的类
CFRunLoopModeRef
RunLoop的运行逻辑
创建添加observer监听runloop的所有状态
第一种方法:
- (void)viewDidLoad {
    [super viewDidLoad];
    //创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    //kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode,UITrackingRunLoopMode
    //添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    //释放
    CFRelease(observer);
}
void observeRunLoopActicities(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            break;
    }
}
第二种方法:
- (void)viewDidLoad {
    [super viewDidLoad];
    ··先创建一个textview,然后滚动,观察打印结果
    //创建observer
    CFRunLoopObserverRef observer =  CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@",mode);
                CFRelease(mode);
                break;
            }
            default:
                break;
        }
    });
    //添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    //释放
    CFRelease(observer);
}
RunLoop的运行逻辑
RunLoop休眠的实现原理

RunLoop休眠的细节:
RunLoop休眠和while(1)是不一样的,while(1)虽然也不会继续往下进行执行代码,但是一直在执行死循环,属于线程阻塞,CPU是一直没有休息的;而RunLoop休眠是CPU进入休息状态。有一个比较底层的函数mach_msg()直接进入休眠状态,睡觉,属于内核层面的API,不占用CPU的资源。


RunLoop在实际开发中的应用
新建一个类继承NSThread,重写dealloc方法,方便监听子线程什么时候销毁
LDThread.h文件
#import <Foundation/Foundation.h>
@interface LDThread : NSThread

@end

LDThread.m文件
#import "LDThread.h"
@implementation LDThread
- (void)dealloc{
    NSLog(@"%s",__func__);
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
}
- (void)run{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
最终打印结果:
-[ViewController run] <LDThread: 0x60000003abc0>{number = 7, name = (null)}  //number=7说明是子线程,如果是主线程,number=1
-[LDThread dealloc]

那么如果有需求,当前的子线程不要挂,要一直存在 ,比如touchBegan方法里,点击一次,就在子线程里走一次run方法,代码如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
}
- (void)run{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
打印结果:
-[ViewController run] <LDThread: 0x600001541f00>{number = 8, name = (null)}
-[LDThread dealloc]

-[ViewController run] <LDThread: 0x60000157c080>{number = 9, name = (null)}
-[LDThread dealloc]
每点击一次,创建一个子线程,实现完毕就会被销毁

现在不希望线程挂掉,有没有什么办法呢?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
}
- (void)run{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
     while (1) ;
}
这种方法虽然可以保证子线程不死,但是是已经阻塞了,也没办法做别的事情了,不可取。

那么现在用RunLoop去做,让子线程在有事情做的时候就去做,没事情做的时候就休眠

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//子线程需要执行的任务
- (void)test{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
//这个方法的目的:线程保活
- (void)run{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
    //往RunLoop里面添加Source/Timer/Observer,这里如果不添加的话,Runloop会直接退出。因为RunLoop里边是空的
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s ---end---",__func__);
}

但是上面的方法会造成ViewController和thread都不会被释放,相互引用造成了强引用,所以这里需要完善一下,代码如下所示:

#import "ViewController.h"
#import "LDThread.h"
#import <objc/runtime.h>
@interface ViewController ()
@property (nonatomic,strong) LDThread *thread;
@property (nonatomic,assign,getter = isStoped)BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    self.stopped = NO;
    self.thread = [[LDThread alloc] initWithBlock:^{
        NSLog(@"%@ ---begin---",[NSThread currentThread]);
        //往RunLoop里面添加Source/Timer/Observer,这里如果不添加的话,Runloop会直接退出。因为RunLoop里边是空的
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf  && !weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
//        [[NSRunLoop currentRunLoop] run];//这种开启方法开启了无限次循环,它专门用于开启一个永不销毁的线程,所以调用runloopStop也无法停止。如果想手动停止子线程,就不能调用 [[NSRunLoop currentRunLoop] run];
        NSLog(@"%@ ---end---",[NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if(!self.thread) return;
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//子线程需要执行的任务
- (void)test{
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
//用于停止子线程的RunLoop
- (void)stop{
    //设置标记为YES
    self.stopped = YES;
    //停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@",__func__,[NSThread currentThread]);
    //清空线程
    self.thread = nil;
}
- (void)dealloc{
    NSLog(@"%s",__func__);
    //在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    //不用RunLoop设置mode的话,在滑动tableview时,timer被停止,停止滚动之后才会继续工作。
    static int count = 0;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",++count);
    }];
//设置如下的mode之后,定时器在拖拽tableview时不受影响
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    //NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
    //NSRunLoopCommonModes并不是一个真的模式,只是一个标记,timer能在_commonModes数组中存放的模式下工作,_commonModes数组中放着两种模式:NSDefaultRunLoopMode和UITrackingRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
线程的封装(创建一条可控制生命周期的线程)
LDPermenantThread.h文件
#import <Foundation/Foundation.h>

typedef void(^LDPermenantThreadTask)(void);

//这里创建的新类是继承自NSObject的,而不是NSThread的,之所以这样做,是不想外界能直接访问NSThread里边的一些方法,只想让它访问我给提供的接口。
@interface LDPermenantThread : NSObject
/*
 开启线程
 */
- (void)run;
/*
 在当前子线程执行一个任务
 */
- (void)executeTask:(LDPermenantThreadTask)task;
/*
 结束线程
 */
- (void)stop;
@end

LDPermenantThread.m文件
#import "LDPermenantThread.h"
/* LDThread 主要就是用来监听线程周期的,正式用的时候就不用这个类了,直接用NSThread就可以了,这个类在调试的时候监听是否能销毁就可以了**/
@interface LDThread : NSThread
@end
@implementation LDThread
- (void)dealloc{
    NSLog(@"%s",__func__);
}
@end

/*  LDPermenantThread **/
@interface LDPermenantThread ()
@property (nonatomic,strong) /*LDThread*/NSThread *innerThread;
@property (nonatomic,assign,getter =  isStopped) BOOL stopped;
@end
@implementation LDPermenantThread
#pragma mark - public methods
- (instancetype)init{
    if (self = [super init]) {
        self.stopped = NO;
        __weak typeof(self) weakSelf = self;
        self.innerThread = [[/*LDThread*/NSThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
    }
    return self;
}

- (void)run{
    if (!self.innerThread) return;
    [self.innerThread start];
}

- (void)executeTask:(LDPermenantThreadTask)task{
    if (!self.innerThread || !task) return;
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:nil waitUntilDone:NO];
}

- (void)stop{
    if (!self.innerThread) return;
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc{
    NSLog(@"%s",__func__);
    [self stop];
}
#pragma mark - private methods
- (void)__stop{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}
- (void)__executeTask:(LDPermenantThreadTask)task{
    task();
}
@end
ViewController.m文件
#import "ViewController.h"
#import "LDPermenantThread.h"
@interface ViewController ()
@property (nonatomic,strong) LDPermenantThread *thread;
@property (nonatomic,strong) UIButton *stopButton;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.stopButton];
    self.thread = [[LDPermenantThread alloc] init];
    [self.thread run];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [self.thread executeTask:^{
        NSLog(@"执行任务 -  %@",[NSThread currentThread]);
    }];
}
//停止按钮的点击事件
- (void)stop{
    [self.thread stop];
}
- (void)dealloc{
    NSLog(@"%s",__func__);
}
- (UIButton *)stopButton{
    if (_stopButton == nil) {
        _stopButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _stopButton.frame = CGRectMake(100, 100, 100, 50);
        [_stopButton setTitle:@"停止" forState:UIControlStateNormal];
        [_stopButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
        [_stopButton addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
    }
    return _stopButton;
}
@end

上面的是用OC语言,那么用C语言怎么实现呢,init的代码如下:

- (instancetype)init{
    if (self = [super init]) {
        self.stopped = NO;
        __weak typeof(self) weakSelf = self;
        self.innerThread = [[/*LDThread*/NSThread alloc] initWithBlock:^{
            NSLog(@"begin ----");
            //创建上下文(要初始化一下结构体)
            CFRunLoopSourceContext context = {0};
            
            //创建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            //往RunLoop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            //销毁source
            CFRelease(source);
            
            //启动
            while (weakSelf && !weakSelf.isStopped) {
                //第三个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
            }
            //或者写成:
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);//这样就不需要stopped这个属性了。更精简了
            NSLog(@"end ----");
        }];
    }
    return self;
}
上一篇 下一篇

猜你喜欢

热点阅读