这里所有的例子都只是为了说明如何使用,不去讨论代码的封装性
一、自定义输入源
自定义输入源需要定义如下内容:
- 你需要输入源处理的信息
- 一个调度中心,让感兴趣的客户知道如何联系你的输入源
- 一个处理中心,用来处理客户发来的请求
- 一个取消中心,用来让你的输入源失效
因为你自定义了一个输入源去处理信息,所以其实际的配置是灵活的,但是调度、处理和取消在你的输入源里都是很关键的,都是需要的,剩余的很多的其他的输入源的相关行为都发生在这些之外。比如,传递当前输入源的数据到其他线程。
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
明显唤醒runloopCFRunLoopWakeUp
唤醒等待中的线程
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,
©Description,
&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,
©Description,
&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,运行的效果很美!!!