Run Loop使用(二)

作者: 大鹏鸟 | 来源:发表于2017-12-27 18:30 被阅读57次
    这里所有的例子都只是为了说明如何使用,不去讨论代码的封装性

    一、自定义输入源

    自定义输入源需要定义如下内容:

    • 你需要输入源处理的信息
    • 一个调度中心,让感兴趣的客户知道如何联系你的输入源
    • 一个处理中心,用来处理客户发来的请求
    • 一个取消中心,用来让你的输入源失效

    因为你自定义了一个输入源去处理信息,所以其实际的配置是灵活的,但是调度、处理和取消在你的输入源里都是很关键的,都是需要的,剩余的很多的其他的输入源的相关行为都发生在这些之外。比如,传递当前输入源的数据到其他线程。

    1、基本定义

    主要介绍一下需要的对象和方法。

    • CFRunLoopSourceContext
      对于source 0事件的数据和回调的包装,其完整结构如下:
    typedef struct {
        CFIndex version;
        void *  info;
        const void *(*retain)(const void *info);
        void    (*release)(const void *info);
        CFStringRef (*copyDescription)(const void *info);
        Boolean (*equal)(const void *info1, const void *info2);
        CFHashCode  (*hash)(const void *info);
        void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*perform)(void *info);
    } CFRunLoopSourceContext;
    

    version:表示事件类型,有0级事件和1级事件,但是这里只能是0
    info:传递的信息,该结构体里的所有info都是相同的
    schedule:函数指针,在调用CFRunLoopAddSource时触发
    cancel:函数指针,当source从runloop的当前mode移除的时候调用(运行完一个会自动移除,也可以手动移除)
    perform:函数指针,事件源触发后调用

    • CFRunLoopSourceRef和CFRunLoopSourceCreate
      事件源对象和事件源的创建方法

    • CFRunLoopSourceIsValid
      判断某个事件源是否有效

    • CFRunLoopAddSource
      将事件源添加到runloop中

    • CFRunLoopSourceSignal
      标记事件源是否准备好了要执行,只对source0事件起作用,如果想要立即执行,则调用方法CFRunLoopWakeUp明显唤醒runloop

    • CFRunLoopWakeUp
      唤醒等待中的线程

    2、基本使用

    如果runloop中什么都没有,则不会进入runloop
    void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
        NSLog(@"schedule");
    }
    
    void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
        NSLog(@"cancel");
    }
    
    void perform(void *info) {
        NSLog(@"perform");
    }
    - (void)addSourceToThread {
        CFRunLoopSourceContext context = {0, (__bridge void *)self, NULL, NULL,
            &copyDescription,
            &equal,
            NULL,
            &schedule,
            &cancel,
            &perform
        };
        CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        CFRunLoopRef rl = CFRunLoopGetCurrent();
    //    Boolean isValid = CFRunLoopSourceIsValid(sourceRef);
    //    if (isValid) {
    //        NSLog(@"is valid");
    //    } else {
    //        NSLog(@"is not valid");
    //    }
        CFRunLoopAddSource(rl, sourceRef, kCFRunLoopDefaultMode);  //调用context的schedule回调函数
        
        CFRunLoopSourceSignal(sourceRef);
        CFRunLoopWakeUp(rl); //如果手动启动,应该是不需要这样
        if (sourceRef) {
            CFRelease(sourceRef);
        }
    //    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    
    注意:

    1、如果当前线程的runloop已经启动了,则直接按照上面的方法添加source和处理source即可;
    2、如果runloop没有启动,则在添加了source之后,需要手动启动
    3、看下面的代码:

    void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
        NSLog(@"schedule");
    }
    
    void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
        NSLog(@"cancel");
    }
    
    void perform(void *info) {
        NSLog(@"perform");
    }
    
    - (void)testAddSource {
        thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
        [thread start];
    }
    
    - (void)test {
        [self addObserverToRunLoop];
        CFRunLoopSourceContext context = {0, (__bridge void *)self, NULL, NULL,
            &copyDescription,
            &equal,
            NULL,
            &schedule,
            &cancel,
            &perform
        };
        CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        CFRunLoopRef rl = CFRunLoopGetCurrent();
        Boolean isValid = CFRunLoopSourceIsValid(sourceRef);
        if (isValid) {
            NSLog(@"is valid");
        } else {
            NSLog(@"is not valid");
        }
        CFRunLoopAddSource(rl, sourceRef, kCFRunLoopDefaultMode);  //调用context的schedule回调函数
        
       // CFRunLoopSourceSignal(sourceRef);
       // CFRunLoopWakeUp(rl);
    //    CFRunLoopRun();  //运行很多次
    //    CFRunLoopRunInMode(kCFRunLoopDefaultMode, kCFAbsoluteTimeIntervalSince1904, false); //在时间范围内运行很多次
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; //这句话必须要有,只运行一次就退出runloop
        if (sourceRef) {
            CFRelease(sourceRef);
        }
        NSLog(@"runloop finished");
    }
    
    - (void)prepareToAdd {
        [self performSelector:@selector(addSourceToThread) onThread:thread withObject:nil waitUntilDone:NO];
    }
    
    - (void)addSourceToThread {
        NSTimer * runTimer = [NSTimer scheduledTimerWithTimeInterval:4 target:self selector:@selector(logForCFRunLoopForFunWithMode) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:runTimer forMode:NSDefaultRunLoopMode];
    //    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    
    - (void)logForCFRunLoopForFunWithMode{
        NSLog(@"%s",__func__);
    }
    

    说明:方法- (void)testAddSource优先调用;方法- (void)prepareToAdd是手动触发去添加事件的。
    在手动添加事件之前,虽然已经添加了一个自定义source,但是因为没有做好准备(CFRunLoopSourceSignal没作用),所以执行了方法[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];时进入等待状态;
    此时手动添加事件,4秒后执行,runloop居然醒了(应该是启动runloop去确定重复周期的),然后因为[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];方法只执行一次,所以线程就退出了,自然timer就不会执行;如果是0秒,则会在下一个runloop立即执行;
    如果想让上面的timer执行,则可以采用不同的runloop的启动方法,只要是能在给定时间内不退出的都可以,如CFRunLoopRun()、CFRunLoopRunInMode、甚至[[NSRunLoop currentRunLoop] run]

    二、定时器(timer)

    在Cocoa框架里,可以使用NSTimer来创建timer对象,在Core Foundation框架下使用CFRunLoopTimerRef创建。在NSTimer实现的内部,也是Core Foundation框架,NSTimer使用起来更简单。
    在Cocoa框架下,提供了三种集创建和添加timer到runloop的类方法:

    • +(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    • +(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
    • +(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
      其中第三个是iOS10才开始有的,需要注意。
      既然是创建和添加都是一起的,那么之前的代码里的方法- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode就可以不用写了;写了也没问题,因为同一个时间源重复添加时是不起作用的。

    1、基本定义

    • CFRunLoopTimerContext
     typedef struct {
     CFIndex    version;
     void *    info;
     const void *(*retain)(const void *info);
     void    (*release)(const void *info);
     CFStringRef    (*copyDescription)(const void *info);
     } CFRunLoopTimerContext;
    

    这里唯一比较有用的是info,该参数会被用在timer的创建中

    • CFRunLoopTimerRef
      timer的类型
    • CFRunLoopTimerCreateWithHandler和CFRunLoopTimerCreate
      timer的两种创建方法:
    CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context);
    
    CFRunLoopTimerRef CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block) (CFRunLoopTimerRef timer))
    

    上面的interval表示执行间隔;
    fireDate表示第一次执行的时间;

    • CFRunLoopAddTimer
      添加timer到runloop中

    • CFRunLoopTimerSetNextFireDate
      修改timer的下次执行时间,但不会影响设置的重复执行时间

    2、基本使用

    实例代码如下:

    void RunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info) {
        NSLog(@"timer call back");
        if (i == 1) {
            CFRunLoopTimerSetNextFireDate(timer, CFAbsoluteTimeGetCurrent() + 5);  //该方法可以修改下次执行时间,但不会影响设置的重复执行时间
            i = i + 1;
        }
    //    CFRunLoopStop(CFRunLoopGetCurrent());  //该方法可以终结重复
    }
    - (void)initThreadForContext {
        NSThread * thread = [[NSThread alloc] initWithTarget:self selector:@selector(createTimerWithContext) object:nil];
        [thread start];
    }
    
    - (void)createTimerWithContext {
        NSLog(@"%@",[NSThread currentThread]);
    //    [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> invocation:<#(nonnull NSInvocation *)#> repeats:<#(BOOL)#>]
        [self addObserverToRunLoop];
        CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 2, 0, 0, &RunLoopTimerCallBack, NULL);
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        CFRelease(timer);
    }
    
    - (void)initThreadForHandle {
        NSThread * thread = [[NSThread alloc] initWithTarget:self selector:@selector(createTimerWithHandle) object:nil];
        [thread start];
    }
    
    - (void)createTimerWithHandle {
        CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 2, 0, 0, ^(CFRunLoopTimerRef blockTimer) {
            NSLog(@"timer call back");
            if (i == 1) {
                CFRunLoopTimerSetNextFireDate(blockTimer, CFAbsoluteTimeGetCurrent() + 5);
                i++;
            }
        });
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
        CFRunLoopRun();
        CFRelease(timer);
    }
    
    同一个timer一次只能添加到一个runloop中,但是可以被添加到同一个runloop的不同模式

    三、端口输入源(属于source1事件)

    可操作性不太强,所以这里暂时不做过多的深入。

    最常用的是:初始化一个空port,添加到runloop中,然后启动runloop,用来做runloop的保活,如:
    + (void)launchThreadWithPort:(NSPort *)port {
        ZPZWorkerClass * worker = [[self alloc] init];
        [worker addObserverToRunLoop];
        worker.remotePort = port;
        [worker sendPortMessage];
        do {
            BOOL result = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; //为了保活
            if (result) {
                NSLog(@"processed");
            } else {
                NSLog(@"processed failed");
            }
        } while (!worker.shouldExit);
    }
    
    - (void)sendPortMessage {
        NSMutableArray * array = [NSMutableArray array];
        [array addObject:@"pengzu"];
        [array addObject:@"zhou"];
        _selfPort = [NSMachPort port];
        _selfPort.delegate = self;
        [[NSRunLoop currentRunLoop] addPort:_selfPort forMode:NSDefaultRunLoopMode];
        [_remotePort sendBeforeDate:[NSDate date] components:array from:_selfPort reserved:0];
    
        //    remotePort sendBeforeDate:<#(nonnull NSDate *)#> msgid:<#(NSUInteger)#> components:<#(nullable NSMutableArray *)#> from:<#(nullable NSPort *)#> reserved:<#(NSUInteger)#>
    }
    

    可以试试如果不在方法sendPortMessage里添加port到runloop,运行的效果很美!!!

    相关文章

      网友评论

        本文标题:Run Loop使用(二)

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