Run Loop使用(二)

这里所有的例子都只是为了说明如何使用,不去讨论代码的封装性

一、自定义输入源

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

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容