配置Runloop的sources

當系統(tǒng)的輸入源不足以滿足我們的需求的時候, 我們可以自定義輸入源. 看了蘋果的官方文檔, 也沒有知道為什么, 所以妄自猜測下, 可能是當系統(tǒng)的輸入源不足以滿足我們的需求的時候, 我們需要自定義輸入源.

定義輸入源

網(wǎng)上大部分配置sources的demo, 核心代碼都出自這里, 下面簡單的對自定義source的類圖進行分析.

自定義輸入源.png

核心類是CCRunLoopInputSource也就是自定義的輸入源. CCRunLoopCustomInputSourceThread是自定義輸入源生存的環(huán)境.

創(chuàng)建自定義輸入源需要定義以下內(nèi)容

  • 1 輸入源要處理的信息.
  • 2 輸入源被添加到Runloop時的調(diào)度例程.
  • 3 輸入源被告知有事件要處理的調(diào)度例程.
  • 4 輸入源被取消時的調(diào)度例程.
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;

// CCRunLoopInputSource.m
- (instancetype)init
{
    self = [super init];
    if (self) {
        CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL,
            &runLoopSourceScheduleRoutine,
            &runLoopSourceCancelRoutine,
            &runLoopSourcePerformRoutine};
        
        _runLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        
        _commands = [NSMutableArray array];
    }
    return self;
}

CFRunLoopSourceCreate創(chuàng)建輸入源的時候, 需要傳入CFRunLoopSourceContext結(jié)構(gòu)體指針, 這里第二個參數(shù)的info傳的是self, 當系統(tǒng)回調(diào)時候會把這個信息當成上下文回調(diào), 此外還定義了schedule, perform, cancel三個函數(shù)指針, 分別對應(yīng)事件源被加到Runloop, 輸入源被告知有事件要處理, 和輸入源失效的函數(shù)回調(diào).

當調(diào)用以下方法

- (void)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop, _runLoopSource, kCFRunLoopDefaultMode);
}

會導致runLoopSourceScheduleRoutine函數(shù)回調(diào),

void runLoopSourceScheduleRoutine (void *info, CFRunLoopRef runLoopRef, CFStringRef mode)
{
    CCRunLoopInputSource *runLoopInputSource = (__bridge CCRunLoopInputSource *)info;
    CCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    
    CCRunLoopContext *runLoopContext = [[CCRunLoopContext alloc] initWithSource:runLoopInputSource runLoop:runLoopRef];
    [appDelegate performSelectorOnMainThread:@selector(registerSource:) withObject:runLoopContext waitUntilDone:NO];
}

這里只是把回調(diào)的事件源封裝成CCRunLoopContext再拋出去, 這里是拋給CCAppDelegate, 實際上拋給哪個類處理都是可以的.

當執(zhí)行

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop
{
    NSLog(@"Current Thread: %@", [NSThread currentThread]);

    CFRunLoopSourceSignal(_runLoopSource);
    CFRunLoopWakeUp(runLoop);
}

實際上是把當前事件源標記為有事件要處理, 然后調(diào)用CFRunLoopWakeUp喚起線程, 類似我們的

    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];

將當前畫布標記為dirty, 然后觸發(fā)重繪.

線程被wakeup后, 會執(zhí)行上一篇博文里面步驟9, 處理source1和timer, 這里當然就是source1了,

void runLoopSourcePerformRoutine (void *info)
{
    CCRunLoopInputSource *runLoopInputSource = (__bridge CCRunLoopInputSource *)info;
    [runLoopInputSource inputSourceFired];
}

實際是執(zhí)行了runLoopSourcePerformRoutine回調(diào), 這里我們看到只是將回調(diào)傳遞來的事件源info取出來, 并執(zhí)行inputSourceFired

- (void)inputSourceFired
{
    NSLog(@"Enter inputSourceFired");
    
    // Test
    if (_testPrintString) {
        if ([self.delegate respondsToSelector:@selector(activeInputSourceForTestPrintStringEvent:)]) {
            [self.delegate activeInputSourceForTestPrintStringEvent:_testPrintString];
        }
    }
    
    NSLog(@"Exit inputSourceFired");
}

這里調(diào)用了代理方法activeInputSourceForTestPrintStringEvent, 將, 最終CCRunLoopInputSource的代理CCRunLoopCustomInputSourceThread將處理這個事件.

- (void)activeInputSourceForTestPrintStringEvent:(NSString *)string
{
    NSLog(@"activeInputSourceForTestPrintStringEvent : %@", string);
}

當然這里的代理不一定要是CCRunLoopCustomInputSourceThread, 也可以是其它的類.

當調(diào)用

- (void)invalidate
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopRemoveSource(runLoop, _runLoopSource, kCFRunLoopDefaultMode);
}

使一個事件源失效的時候, 會觸發(fā)runLoopSourceCancelRoutine回調(diào)

void runLoopSourceCancelRoutine (void *info, CFRunLoopRef runLoopRef, CFStringRef mode)
{
    CCRunLoopInputSource *runLoopInputSource = (__bridge CCRunLoopInputSource *)info;
    CCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    
    CCRunLoopContext *runLoopContext = [[CCRunLoopContext alloc] initWithSource:runLoopInputSource runLoop:runLoopRef];
    [appDelegate performSelectorOnMainThread:@selector(removeSource:) withObject:runLoopContext waitUntilDone:YES];
}

下面看下AppDelegate的代碼

@implementation CCAppDelegate (RunLoop)

- (void)registerSource:(CCRunLoopContext *)sourceContext
{
    if (!self.sources) {
        self.sources = [NSMutableArray array];
    }
    [self.sources addObject:sourceContext];
}

- (void)removeSource:(CCRunLoopContext *)sourceContext
{
    [self.sources enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        CCRunLoopContext *context = obj;
        if ([context isEqual:sourceContext]) {
            [self.sources removeObject:context];
            *stop = YES;
        }
    }];
}

- (void)testInputSourceEvent
{
    CCRunLoopContext *runLoopContext = [self.sources objectAtIndex:0];
    CCRunLoopInputSource *inputSource = runLoopContext.runLoopInputSource;
    [inputSource addTestPrintCommandWithString:[[NSDate date] description]];
    [inputSource fireAllCommandsOnRunLoop:runLoopContext.runLoop];
}

@end

這里維護了一個輸入源的數(shù)組, 用來區(qū)分不同的輸入源. 對于不同的輸入源我們可以在testInputSourceEvent選擇不同的輸入源進行觸發(fā).

[inputSource addTestPrintCommandWithString:[[NSDate date] description]];

上面的方法, 我理解是進行數(shù)據(jù)的傳遞, 作者也設(shè)計了更加通用的接口

- (void)addCommand:(NSInteger)command data:(NSData *)data;

不過demo里面沒有使用到.

這里實際上是建立了一個通道和線程狀態(tài)切換的機制


自定義輸入源數(shù)據(jù)通道.png

臆想:
這個通道是線程之間傳遞數(shù)據(jù), 喚醒休眠線程的一套機制, 我們完全可以自定義一個輸入源, 用來處理服務(wù)器發(fā)來的數(shù)據(jù).

步驟如下:

  • 1 初始化線程的時候創(chuàng)建輸入source, 并添加.
  • 2 當有數(shù)據(jù)過來的時候調(diào)用CFRunLoopSourceSignalCFRunLoopWakeUp, 并將當前數(shù)據(jù)的回調(diào)模塊信息保存到CFRunLoopSourceSignal.
  • 3 在子線程里進行數(shù)據(jù)解析等一些耗時操作.
  • 4 當數(shù)據(jù)解析完, 將數(shù)據(jù)封裝成CCRunLoopContext, 找到回調(diào)模塊, 在主線程進行回調(diào)(performSelectorOnMainThread).

相對比dispatch_async這樣做的好處是, 在觸發(fā)事件前, 可以保存一些數(shù)據(jù)到自定義source, 這樣在回調(diào)的時候可以很方便的找到指定的方法進行回調(diào), 有點類似運行時的效果.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容