RunLoop
2020-09-20 本文已影响0人
答案不止一个
RunLoop
runloop mode.png
运行循环是与线程关联的基本基础结构的一部分。是用于管理异步到达线程的事件的基础架构。
两种不同类型的源。
- Input sources 输入源
- 通常是来自另一个线程或其他应用程序的消息
2.会使runUntilDate:方法退出,直接在需要时运行
- 通常是来自另一个线程或其他应用程序的消息
- Timer source
-
定时器,指定时间的窒息那个输入源,
runloop source.png
-
RunLoop 状态
RunLoop还生成有关的状态行为的通知。使用Core Foundation在线程上添加 RunLoop 观察器 (CFRunloop)
- 创建context
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
- 使用CFRunLoopObserverCreate 创建观察者
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//进入runloop
kCFRunLoopBeforeTimers = (1UL << 1),//准备处理timer
kCFRunLoopBeforeSources = (1UL << 2),//准备处理source
kCFRunLoopBeforeWaiting = (1UL << 5),//准备休眠
kCFRunLoopAfterWaiting = (1UL << 6),//休眠结束,处理事件之前
kCFRunLoopExit = (1UL << 7),//runloop退出
kCFRunLoopAllActivities = 0x0FFFFFFFU//所有状态
};
// 先定义回调方法
void myRunLoopObserver (CFRunLoopObserverRef observer, CFRunLoopActivity activity)
{
NSLog(@"myRunLoopObserver");
}
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
- 使用 CFRunLoopObserverCreateWithHandler 创建观察者
CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"%@", activity);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理time事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
})
- 使用CFRunLoopAddObserver(CFRunLoopRef, CFRunLoopObserverRef, CFRunLoopMode) 添加观察者
CFRunLoopRef ref = CFRunLoopGetCurrent();
CFRunLoopAddObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFRunLoopMode mode#>)
RunLoop observer.png
RunloopModal
是多个事件源, timers 和多个监听着的集合
常见的由default modal, common modal 。 event Event Tracking modal。
runloop mode.png
Mode | Name |
---|---|
Default | NSDefaultRunLoopMode (Cocoa) |
Connection | NSConnectionReplyMode |
Modal | NSModalPanelRunLoopMode |
Event tracking | NSEventTrackingRunLoopMode |
Common modes | NSRunLoopCommonModes |
RunLoop在一个事件,只会执行其当前对应模式下的事件源以及 observers
输入源
- 基于端口的输入源 - 监听应用程序的Mach端口
- 自定义输入源 - 自定义的事件
会使runUntilDate:退出
两种输入源的区别: Port-Based source是由kernal 自动产生, custom source 一定是由其他线程手动发出的信号
自定义输入源
出入 port-base的输入源外,Cocoa 中Perform Selector Sources 允许方法在任何一个线程中执行,
perform selector source 在 方法执行完成之后,就会从Runloop中移除。在另一个线程上执行选择器时,目标线程必须具有活动的运行循环
//允许您使用performSelector:withObject:afterDelay:or performSelector:withObject:afterDelay:inModes:方法取消发送到当前线程的消息。
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
计时器
计时器源在将来的预设时间将事件同步传递到您的线程。计时器是线程通知自己执行某项操作的一种方式。
自定义输入源
- 定义一个参数对象,在执行相应操作时携带的info
- 创建context。context中包含着 参数对象,一些方法指针(用于回调执行等)
- schedule 是在source 加入到RunLoop之后就会执行的回调,可以在里面做一下保存操作
- perfor 是在CFRunLoopSourceSignal(source); 方法执行后,会触发的函数,注意 不同的线程 执行不会相应
- cancle 当使用CFRunLoopSourceInvalidate 销毁输入源时,会执行这个回调方法。如果线程销毁,也会执行这个回调
- 不能线程之间通信。
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); // 在source加入到runloop就会调用
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode); // 在 runloop对应的线程中 执行 CFRunLoopSourceSignal方法,就会执行这个方法
void (*perform)(void *info); //
} CFRunLoopSourceContext;
// 在source 加入到
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode){
// NSLog(@"RunLoopSourceScheduleRoutine %@ %@ %@", info, rl, mode);
NSLog(@"RunLoopSourceScheduleRoutine");
}
void RunLoopSourcePerformRoutine (void *info)
{
// NSLog(@"RunLoopSourcePerformRoutine %@ ", info);
NSLog(@"RunLoopSourcePerformRoutine");
}
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
// NSLog(@"RunLoopSourceCancelRoutine %@ %@ %@", info, rl, mode);
NSLog(@"RunLoopSourceCancelRoutine");
}
CFRunLoopSourceContext context = {
0,
(__bridge void *)(data), // data 定义的数据。
NULL, // retain
NULL, // release
NULL, // copyDescription
NULL, // equal
NULL, // hash
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine
};
- 创建 source 使用 CFRunLoopSourceCreate(NULL, 0, &context);
self.source = CFRunLoopSourceCreate(NULL, 0, &context);
- 将source 加入到 runloop中
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// CFRunLoopRun(); 如果没执行,则去执行,
- 在runloop对应的线程中执行,触发source。 跨线程不会触发 perform函数
//执行之前 可以修改定义的data 数据..
CFRunLoopSourceSignal(source);
- 根据需要使source 销毁
CFRunLoopSourceInvalidate(source)
// 销毁后,runloop将不会在执行source 的方法,但是source的内存并不会立即被deallocted。直到只有runloop持有source。才会被销毁
- 直接唤醒RunLoop
CFRunLoopWakeUp(runloop)
创建一个 timer源
- 使用NSTimer , 并加入到NSRunLoop
- 使用CFRunLoopTimerRef
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0,NULL,NULL,NULL,NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,0.1,0.3,0,0,
&myCFTimerCallback,&context);
CFRunLoopAddTimer(runLoop,timer,kCFRunLoopCommonModes);
创建Port-based 源
基于端口的对象,可以用于在线程之间或进程之间进行通信.
使用NSMachPort
MachPort的工作方式其实是将NSMachPort的对象添加到一个线程所对应的RunLoop中,并给NSMachPort对象设置相应的代理。在其他线程中调用该MachPort对象发消息时,会在MachPort所关联的线程中执行相关的代理方法。
- 创建NSMachPort,并添加到一个线程的runloop中。并且对port 对象设置代理
- (void)start{
thread = [[NSThread alloc] initWithBlock:^{
self.port = [NSMachPort port];
[self.port setDelegate:self];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:self.port forMode:NSDefaultRunLoopMode];
[runloop run];
}];
[thread start];
}
// NSMatchPort 代理方法。当 port 执行sendPort 是会在 port加入的线程中,执行这个方法
- (void)handlePortMessage:(NSPortMessage *)message{
NSLog(@"%@ %@ ", [NSThread currentThread], message);
}
// message 只是 @class NSConnection, NSPortMessage;这种定义识别不到里面的数据
// 自己定义一个struct 去取数据
struct MyPortMsg{
Class isa;
NSMachPort * localPort;
NSMachPort * remotePort;
NSMutableArray * components;
unsigned int msgid;
void * revered;
void * reser1;
};
- (void)handlePortMessage:(NSPortMessage *)message{
NSObject * msg = (NSObject *)message;
// 以结构体的方式取对内存数据
struct MyPortMsg * p = (__bridge struct MyPortMsg *)(message);
NSData *data = p->components.firstObject;
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@ %@ ", [NSThread currentThread] , msg);
}
- 在其他任意线程中使用上面创建的port 进行发送消息。 port的代理都会在期所加入的线程中执行
-(void)tt{
NSThread *t = [[NSThread alloc] initWithBlock:^{
NSLog(@"2 %@",[NSThread currentThread]);
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:12];
[arr addObject:[@"asdsda" dataUsingEncoding:NSUTF8StringEncoding]];
// 穿一个string数据 和msgid
[myp.port sendBeforeDate:[NSDate distantFuture] msgid:100 components:arr from:nil reserved:nil];
}];
[t start];
}
- (BOOL)sendBeforeDate:(NSDate *)limitDate components:(NSMutableArray *)components from:(NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;
- (BOOL)sendBeforeDate:(NSDate *)limitDate msgid:(NSUInteger)msgID components:(nullable NSMutableArray *)components from:(nullable NSPort *)receivePort reserved:(NSUInteger)headerSpaceReserved;
// components 是 NSData 以及其子类对象, NSPort 和NSPort子类对象 的数组
使用 NSMessagePort
NSPort* localPort = [[NSMessagePort alloc] init];
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];