RunLoop

2020-09-20  本文已影响0人  答案不止一个
RunLoop

运行循环是与线程关联的基本基础结构的一部分。是用于管理异步到达线程的事件的基础架构。

两种不同类型的源。

  1. Input sources 输入源
    1. 通常是来自另一个线程或其他应用程序的消息
      2.会使runUntilDate:方法退出,直接在需要时运行
  2. Timer source
    1. 定时器,指定时间的窒息那个输入源,


      runloop source.png

RunLoop 状态

RunLoop还生成有关的状态行为的通知。使用Core Foundation在线程上添加 RunLoop 观察器 (CFRunloop)

  1. 创建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};
  1. 使用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);
  1. 使用 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;
        }
    })
  1. 使用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

输入源
  1. 基于端口的输入源 - 监听应用程序的Mach端口
  2. 自定义输入源 - 自定义的事件

会使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:
计时器

计时器源在将来的预设时间将事件同步传递到您的线程。计时器是线程通知自己执行某项操作的一种方式。

自定义输入源

  1. 定义一个参数对象,在执行相应操作时携带的info
  2. 创建context。context中包含着 参数对象,一些方法指针(用于回调执行等)
    1. schedule 是在source 加入到RunLoop之后就会执行的回调,可以在里面做一下保存操作
    2. perfor 是在CFRunLoopSourceSignal(source); 方法执行后,会触发的函数,注意 不同的线程 执行不会相应
    3. cancle 当使用CFRunLoopSourceInvalidate 销毁输入源时,会执行这个回调方法。如果线程销毁,也会执行这个回调
  3. 不能线程之间通信。
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
};
  1. 创建 source 使用 CFRunLoopSourceCreate(NULL, 0, &context);
 self.source = CFRunLoopSourceCreate(NULL, 0, &context);
  1. 将source 加入到 runloop中
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// CFRunLoopRun(); 如果没执行,则去执行,
  1. 在runloop对应的线程中执行,触发source。 跨线程不会触发 perform函数
//执行之前  可以修改定义的data 数据..
CFRunLoopSourceSignal(source);
  1. 根据需要使source 销毁
CFRunLoopSourceInvalidate(source)
// 销毁后,runloop将不会在执行source 的方法,但是source的内存并不会立即被deallocted。直到只有runloop持有source。才会被销毁
  1. 直接唤醒RunLoop

CFRunLoopWakeUp(runloop)

创建一个 timer源

  1. 使用NSTimer , 并加入到NSRunLoop
  2. 使用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所关联的线程中执行相关的代理方法。

  1. 创建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);
}
  1. 在其他任意线程中使用上面创建的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];
 

使用CoreFundation

上一篇下一篇

猜你喜欢

热点阅读