runloop的使用

一、RunLoop是什么?
字面意思:运行循环,程序运行过程中循环的处理事情
它的实际:实际是一个对象, 这个对象提供一个入口函数,执行这个入口函数后,程序会进入一个do..while循环,循环的处理一些事情。
二、RunLoop有什么用?

int main(int argc, char * argv[]){
 //没有runloop
@autoreleasepool { 

    NSLog(@“%s”, __func__);
}
return 0;
}

结果:程序执行完就会退出。

int main(int argc, char * argv[]){
 //有runloop
@autoreleasepool { 

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

}
}

结果:程序一直执行没有退出。
RunLoop基本作用:

  • 保持程序的持续运行
  • 处理App中的各种事件(触摸、定时器、PerformSelector)
  • 节省CPU资源、提高程序性能:该做事的时候做事,该休息的时候休息。
    三、RunLoop怎么使用?
    iOS提供了2套API来访问和使用RunLoop:
    Foundation:NSRunLoop
    Core Foundation:CFRunLoopRef
    CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/
  1. 线程与RunLoop是一一对应的
  2. 线程创建的时候,并没有创建RunLoop对象,RunLoop会在第一次获取的时候自动创建。
  3. 主线程默认开启了RunLoop, 子线程默认没有开启子线程
    四、CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserverRef


    runloopmodel.png

    runloop结构.png

    1.CFRunLoopRef
    *一个RunLoop对应着一条线程
    *一个RunLoop包含多个Mode,每个 Mode 又包含若干个 Source/Timer/Observer
    *Source/Timer/Observer又叫mode item。不同mode下的mode item互不影响
    *RunLoop运行过程中,只选择一种模式运行
    *切换Mode,程序退出当前RunLoop mode,再重新指定Mode执行
    2.CFRunLoopSourceRef

  • source0:触摸事件,自定义输入源,performSelector:onThread:
  • source1:端口(Port)
    *计时源:NSTimer,performSelector:withObject:afterDelay
    3.线程添加runloop
- (void)viewDidLoad {
    [super viewDidLoad];
//    CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
//    CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
    self.stopping = NO;
    NSThread* th = [[NSThread alloc] initWithBlock:^{
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"定时打招呼!! 你好!");
            
            if (self.stopping) {
                [NSThread exit];//线程退出,runloop也同时结束
            }
        }];
       // NSLog(@"%s", __func__);
        [[NSRunLoop currentRunLoop] run];//线程创建的时候,并没有创建runloop对象,runloop会在第一次获取的时候自动创建
    }];
    [th start];
    
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.stopping = YES;
}

4.查看runloop模式

    CFRunLoopRef rl = CFRunLoopGetCurrent();
    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(rl);
    NSLog(@"mode ---> %@", mode);//输出:kCFRunLoopDefaultMode
    CFArrayRef array = CFRunLoopCopyAllModes(rl);
    NSLog(@"array ---> %@", array);
    /**
     输出:
     array ---> (
     UITrackingRunLoopMode,
     GSEventReceiveRunLoopMode,
     kCFRunLoopDefaultMode,
     kCFRunLoopCommonModes
     )
     */

5.自定义输入源

- (void) sourceTest {
    CFRunLoopSourceContext context = {
        0,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        schedule,
        cancel,
        perform
    };
//    触发schedule
    CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
//    触发perform
//    CFRunLoopSourceSignal(source0);//标记
//    CFRunLoopWakeUp(CFRunLoopGetCurrent());//唤醒
//    触发cancel
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
    CFRelease(source0);
}

void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    NSLog(@"%s", __func__);
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    NSLog(@"%s", __func__);
}

void perform(void *info) {
    NSLog(@"%s", __func__);
}
自定义源.png

6.CFRunLoopTimerRef,定时器

@interface ViewController ()
{
    CFRunLoopTimerRef timer;
}
@end

- (void) timerTest {
//    第一种方式
    timer =  CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
       NSLog(@"%s", __func__);
    });

    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
//    第二种方式
//    CFRunLoopTimerContext context = {
//        0,
//        (__bridge void *)self,
//        CFRetain,
//        CFRelease,
//        NULL
//    };
//    timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, callBack, &context);
//    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
}

void callBack(CFRunLoopTimerRef timer, void *info){
    NSLog(@"✈️✈️✈️✈️✈️✈️");
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//    销毁定时器
    CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
    CFRelease(timer);
}

7.CFRunLoopObserverRef,观察者

- (void) observerTest {
//    第一种方式
/*
     第一个参数:怎么分配存储空间
     第二个参数:要监听的状态 kCFRunLoopAllActivities 所有的状态
     第三个参数:是否持续监听
     第四个参数:优先级 总是传0
     第五个参数:当状态改变时候的回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"%lu", activity);
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//    第二种方式
//    //    定义观察者
//    static CFRunLoopObserverRef runloopObserver;
//    //    创建观察者
//    CFRunLoopObserverContext context = {
//        0,
//        (__bridge void *) self,
//        &CFRetain,
//        &CFRelease,
//        NULL
//    };
//    //NULL此处相当于kCFAllocatorDefault
//    runloopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeTimers, YES, 0, &callBack1, &context);
//    //    添加观察者
//    CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopCommonModes);
//    移除并释放
//    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//    CFRelease(observer);
    
}

void callBack1(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
}
observer.png

五、NSPort
1.NSPort是通信通道的抽象类。
2.能干什么?:我们能够使用端口进行线程间的一个通信。
3.要接收传入消息,必须将NSPort对象添加到NSRunLoop对象中作为输入源
4.端口用完之后,如果不用, 要释放, 不然产生的端口对象可能会逗留并创建内 存泄漏。要使端口对象无效,请调用它的invalidate方法。
5.Foundation定义了NSPort的三个具体子类。NSMachPort和NSMessagePort只允许本地(在同一台机器上)通信。NSSocketPort支持本地和远程通信,但是对于本地情 况,可能比其他的要昂贵。在使用allocWithZone:或port创建NSPort对象时,将创建 一个NSMachPort对象。
6.使用allocWithZone:活着port创建NSPort对象那, 实际上是创建一个NSMachPort对象

@interface ViewController () 

@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mainThreadPort = [NSPort port];
    self.mainThreadPort.delegate = self;
    [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode];//主线程上
    [self task];
}

- (void) task {
    NSThread* thread = [[NSThread alloc] initWithBlock:^{
        self.subThreadPort = [NSPort port];
        self.subThreadPort.delegate = self;
        
        [[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode];//子线程上
        [[NSRunLoop currentRunLoop] run];//子线程要开启runloop
    }];
    [thread setName:@"子线程"];
    [thread start];
}
//- (void)handlePortMessage:(NSPortMessage *)message
- (void)handlePortMessage:(id)message {
    NSLog(@"%@", [NSThread currentThread]);
    //KVC 取值,在Macos的Foundation中的NSPortMessage.h中查看,components为私有的,所以要用kvc取值
    NSMutableArray* components = [message valueForKey:@"components"];
    
    if ([components count] > 0) {
        NSData* data = [components objectAtIndex:0];
        NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
    }

    sleep(2);
    if (![[NSThread currentThread] isMainThread]) {
        NSMutableArray* sendComponents = [NSMutableArray array];
        NSData* data = [@"world" dataUsingEncoding:NSUTF8StringEncoding];
        [sendComponents addObject:data];
         [self.mainThreadPort sendBeforeDate:[NSDate date] components:sendComponents from:self.subThreadPort reserved:0];
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSMutableArray* components = [NSMutableArray array];
    NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
    [components addObject:data];
    /**
     第一个参数:发送时间
     第二个参数:发送的数据
     第三个参数:从那个端口发送,此处从主线程端口self.mainThreadPort向子线程端口发送
     第四个参数:保留位
     */
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
//    soure0标示为待处理,要唤醒
}

六、runloop执行过程


执行过程.png

内部实现.png

休眠原理
RunLoop实现休眠的原理, 真正的原因是:
1.调用了内核的API(mach_msg), 进入内核态,由内核来将线程置于休眠
2.有消息,就唤醒线程,回到用户态,来处理消息.


休眠原理.png

你可能感兴趣的:(runloop的使用)