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,运行的效果很美!!!

相关文章

  • runloop

    走进Run Loop的世界 (一):什么是Run Loop?走进Run Loop的世界 (二):如何配置Run L...

  • Run Loop使用(二)

    这里所有的例子都只是为了说明如何使用,不去讨论代码的封装性 一、自定义输入源 自定义输入源需要定义如下内容: 你需...

  • 详解Run Loop

    Run Loop Run Loop是事件驱动的。 iOS中有2套API来访问使用Run LoopFoundatio...

  • iOS开发之Run loop

    1.什么是Run loop,Run loop有什么作用? 2.Run loop 是怎么运作的? 3.什么情况下使用...

  • Run Loops基础概念篇二

    When Would You Use a Run Loop? 你唯一要使用run loop,就是当你要在appli...

  • RunLoop基本知识点

    Run Loop是什么,使用的目的,何时使用和关注点 Run Loop是一让线程能随时处理事件但不退出的机制。Ru...

  • iOS Runloop学习笔记

    一、** what is run loop ** 1、A run loop is an abstraction t...

  • iOS 开发学习笔记runLoop 机制详解

    Run Loop就是一个事件处理的循环,用来不停的调动工作以及处理输入事件。使用Run Loop的目的就是节省CP...

  • Run Loop使用(一)

    在上一节,简单的说了一下Run Loop相关的知识,这一节将做进一步练习巩固。 每个runloop对象都提供了一些...

  • iOS Runloop(二)

    Run Loop观察者源是合适的同步或异步事件发生时触发,而run loop观察者则是在run loop本身运行的...

网友评论

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

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