程序员iOS Developer

Run Loop使用(二)

2017-12-27  本文已影响57人  大鹏鸟
这里所有的例子都只是为了说明如何使用,不去讨论代码的封装性

一、自定义输入源

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

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

1、基本定义

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

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:函数指针,事件源触发后调用

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的类方法:

1、基本定义

 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 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表示第一次执行的时间;

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

上一篇下一篇

猜你喜欢

热点阅读