美文网首页
runloop的使用

runloop的使用

作者: heart_领 | 来源:发表于2018-09-15 12:51 被阅读7次

    一、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<UITouch *> *)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<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    //    销毁定时器
        CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
        CFRelease(timer);
    }
    

    7.CFRunLoopObserverRef,观察者

    - (void) observerTest {
    //    第一种方式
        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 () <NSPortDelegate>
    
    @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<UITouch *> *)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的使用

          本文链接:https://www.haomeiwen.com/subject/nvsogftx.html