iOS runloop 自定义输入源

创建自定义输入源需要定义以下内容

1)输入源要处理的信息
2)使感兴趣的客户端知道如何和输入源交互的调度例程
3)处理其他任何客户发送请求的例程
4)使输入源失效的取消例程
iOS runloop 自定义输入源 - 勇者之尊 - 勇者之尊
 上图的处理流程:主线程(Main Thread)发起任务(Task)给工作线程(Worker Thread),主线程会给命令缓冲区(send command-->Command Buffer),通知输入源(signal source-->Input Source),并唤醒工作线程(Wake Up-->Worker Thread)。工作线程收到唤醒命令,Run Loop会调用输入源的处理程序,由它来执行命令缓冲区中相应的命令。
注:因为主线程和输入源所在工作线程都可以访问命令缓冲区(Command Buffer),因此这些访问必须使同步的
1)定义输入源(The custom input source object definition
下面代码中,定义了RunLoopSource对象,它管理命令缓冲区,并以此来接收其他线程的消息。RunLoopContext对象是一个用来传递RunLoopSource对象(RunLoopSource* source)和“run loop引用”(CFRunLoopRef runLoop)给程序主线程的一个容器
======
// Listing 3-3 The custom input source object definition

@interface RunLoopSource : NSObject

{

    CFRunLoopSourceRef runLoopSource;

    NSMutableArray* commands;

}

 

- (id)init;

- (void)addToCurrentRunLoop;

- (void)invalidate;

 

// handlermethod

- (void)sourecFired;

// Client interface for registering commands to process

- (void)addCommand:(NSInteger)command withData:(id)data;

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop;

 

@end

 

// These are the CFRunLoopSourceRef callback functions.

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);

void RunLoopSourcePerformRoutine (void *info);

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);

 

// RunLoopContext is a container object used during registration of the input source.

@interface RunLoopContext : NSObject

{

    CFRunLoopRef runLoop;

    RunLoopSource* source;

}

 

@property (readonlyCFRunLoopRef runLoop;

@property (readonly) RunLoopSource* source;

 

- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;

@end

===当将输入源附加到run loop时,调用这个协调调度例程,将源注册到客户端(可以理解为其他线程)

// Listing 3-4 Scheduling a run loop source

//当source添加进runloop的时候,调用此回调方法 <== CFRunLoopAddSource(runLoop, source, mode);

 

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    AppDelegate* del = [AppDelegate sharedAppDelegate];

    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];

    [del performSelectorOnMainThread:@selector(registerSource:) withObject:theContext waitUntilDone:NO];

}

===在输入源被告知(signal source)时,调用这个处理例程,这儿只是简单的调用了 [obj sourceFired]方法

 

// Listing 3-5 Performing work in the input source 

//当sourcer接收到消息的时候,调用此回调方法(CFRunLoopSourceSignal(source);CFRunLoopWakeUp(runLoop);

void RunLoopSourcePerformRoutine (void *info)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    [obj sourceFired];

}

===如果使用CFRunLoopSourceInvalidate/CFRunLoopRemoveSource函数把输入源从run loop里面移除的话,系统会调用这个取消例程,并且把输入源从注册的客户端(可以理解为其他线程)里面移除

 

// Listing 3-6 Invalidating an input source == 

//当source 从runloop里删除的时候,调用此回调方法 <== CFRunLoopRemoveSource(runLoop, source, mode); 

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)

{

    RunLoopSource* obj = (RunLoopSource*)info;

    AppDelegate* del = [AppDelegate sharedAppDelegate];

    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];

    [del performSelectorOnMainThread:@selector(removeSource:) withObject:theContext waitUntilDone:YES];

}

2)安装输入源到Run Loop---分两步首先初始化一个输入源,然后将这个输入源添加到当前Run Loop里面

 

// List 3-7 Installing the run loop source

 

 

- (id)init

{

    /*

     // Setup the context.

     context.version = 0;

     context.info = self;

     context.retain = NULL;

     context.release = NULL;

     context.copyDescription = CFCopyDescription;

     context.equal = CFEqual;

     context.hash = CFHash;

     context.schedule = RunLoopSourceScheduleRoutine;

     context.cancel = RunLoopSourceCancelRoutine;

     context.perform = RunLoopSourcePerformRoutine;

     */

    CFRunLoopSourceContext context = {0selfNULLNULLNULLNULLNULL,

        &RunLoopSourceScheduleRoutine,

        &RunLoopSourceCancelRoutine,

        &RunLoopSourcePerformRoutine};

    runLoopSource = CFRunLoopSourceCreate(NULL0, &context);

    commands = [[NSMutableArray allocinit];

    return self;

}

 

 

- (void)addToCurrentRunLoop

{

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();

    //Add the new CFRunLoopSourceRef to the indicated runloop, 并且回调RunLoopSourceScheduleRoutine函数

    CFRunLoopAddSource(runLoop, runLoopSourcekCFRunLoopDefaultMode);

}

3)协调输入源的客户端(将输入源注册到客户端)

输入源的主要工作就是将与输入源相关联的线程置于休眠状态,直到有事件发生。要达到这个目的,首先客户端(其他线程)知道有该输入源信息,并且有办法与之通信。

通知客户端关于这个输入源信息的方法之一,就是当该输入源开始安装到你的run loop上面后发送注册请求给相关的客户端,该输入源可以注册到任意数量的客户端

也可以通过由代理将输入源注册到感兴趣的客户端

===下面显示了应用委托(AppDelegate)定义的注册方法及从应用委托(AppDelegate)中移除的方法

 

- (void)registerSource:(RunLoopContext *)sourceInfo

{

    NSMutableArray *sourceToPing;

    [sourceToPing addObject:sourceInfo];

}

 

- (void)removeSource:(RunLoopContext *)sourceInfo

{

    id objToRemove = nil;

    

    NSMutableArray *sourceToPing;

    

    for(RunLoopContext *context in sourceToPing)

    {

        if([context isEqual:sourceInfo])

        {

            objToRemove = context;

            break;

        }

    }

    

    if(objToRemove)

    {

        [sourceToPing removeObject:objToRemove];

    }

}

注:上面两个函数分别在RunLoopSourceScheduleRoutine/RunLoopSourceCancelRoutine函数中被调用

4)通知输入源---客户端(其他线程)发数据到输入源,分两步首先发信号给输入源(signal source),然后唤醒输入源的run loop

===下面显示了客户端发送数据到输入源的方法,在本例中这个方法被放在RunLoopSource对象里面

 

// Listing 3-9 Waking up the run loop

- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runLoop

{

    //当手动调用此方法的时候,将会触发 RunLoopSourceContextperformCallback

    CFRunLoopSourceSignal(runLoopSource);

    CFRunLoopWakeUp(runLoop);

}

注:1,输入源就是一类事件(命令)处理机制。他是线程间的事件(命令)异步通讯机制,所以不能试图通过这个机制实现进程间的通讯

2,因为CFRunLoopWakeUp函数不是信号安全的,所以对run loop的唤醒,不能在应用信号处理例程(RunLoopSourcePerformRoutine)里面使用。

 
 
 
 
 评论这张
 
posted @ 2013-12-26 08:07  光光96  阅读(652)  评论(0编辑  收藏  举报