https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3
The following sections show examples of how to set up different types of input sources in both Cocoa and Core Foundation.
Defining a Custom Input Source
Creating a custom input source involves defining the following:
-
The information you want your input source to process.
-
A scheduler routine to let interested clients know how to contact your input source.
-
A handler routine to perform requests sent by any clients.
-
A cancellation routine to invalidate your input source.
Because you create a custom input source to process custom information, the actual configuration is designed to be flexible. The scheduler, handler, and cancellation routines are the key routines you almost always need for your custom input source. Most of the rest of the input source behavior, however, happens outside of those handler routines. For example, it is up to you to define the mechanism for passing data to your input source and for communicating the presence of your input source to other threads.
Figure 3-2 shows a sample configuration of a custom input source. In this example, the application’s main thread maintains references to the input source, the custom command buffer for that input source, and the run loop on which the input source is installed. When the main thread has a task it wants to hand off to the worker thread, it posts a command to the command buffer along with any information needed by the worker thread to start the task. (Because both the main thread and the input source of the worker thread have access to the command buffer, that access must be synchronized.) Once the command is posted, the main thread signals the input source and wakes up the worker thread’s run loop. Upon receiving the wake up command, the run loop calls the handler for the input source, which processes the commands found in the command buffer.
Figure 3-2 Operating a custom input source![Operating a custom input source](https://i-blog.csdnimg.cn/blog_migrate/2d23cbc57827e3b3910feb07901ddf57.jpeg)
The following sections explain the implementation of the custom input source from the preceding figure and show the key code you would need to implement.
Defining the Input Source
Defining a custom input source requires the use of Core Foundation routines to configure your run loop source and attach it to a run loop. Although the basic handlers are C-based functions, that does not preclude you from writing wrappers for those functions and using Objective-C or C++ to implement the body of your code.
The input source introduced in Figure 3-2 uses an Objective-C object to manage a command buffer and coordinate with the run loop. Listing 3-3 shows the definition of this object. The RunLoopSource
object manages a command buffer and uses that buffer to receive messages from other threads. This listing also shows the definition of the RunLoopContext
object, which is really just a container object used to pass a RunLoopSource
object and a run loop reference to the application’s main thread.
Listing 3-3 The custom input source object definition
@interface RunLoopSource : NSObject |
{ |
CFRunLoopSourceRef runLoopSource; |
NSMutableArray* commands; |
} |
|
- (id)init; |
- (void)addToCurrentRunLoop; |
- (void)invalidate; |
|
// Handler method |
- (void)sourceFired; |
|
// 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 (readonly) CFRunLoopRef runLoop; |
@property (readonly) RunLoopSource* source; |
|
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop; |
@end |
Although the Objective-C code manages the custom data of the input source, attaching the input source to a run loop requires C-based callback functions. The first of these functions is called when you actually attach the run loop source to your run loop, and is shown in Listing 3-4. Because this input source has only one client (the main thread), it uses the scheduler function to send a message to register itself with the application delegate on that thread. When the delegate wants to communicate with the input source, it uses the information in RunLoopContext
object to do so.
Listing 3-4 Scheduling a run loop source
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]; |
} |
One of the most important callback routines is the one used to process custom data when your input source is signaled. Listing 3-5 shows the perform callback routine associated with the RunLoopSource
object. This function simply forwards the request to do the work to the sourceFired
method, which then processes any commands present in the command buffer.
Listing 3-5 Performing work in the input source
void RunLoopSourcePerformRoutine (void *info) |
{ |
RunLoopSource* obj = (RunLoopSource*)info; |
[obj sourceFired]; |
} |
If you ever remove your input source from its run loop using the CFRunLoopSourceInvalidate
function, the system calls your input source’s cancellation routine. You can use this routine to notify clients that your input source is no longer valid and that they should remove any references to it. Listing 3-6 shows the cancellation callback routine registered with the RunLoopSource
object. This function sends another RunLoopContext
object to the application delegate, but this time asks the delegate to remove references to the run loop source.
Listing 3-6 Invalidating an input source
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]; |
} |
Note: The code for the application delegate’s registerSource:
and removeSource:
methods is shown in “Coordinating with Clients of the Input Source.”
Installing the Input Source on the Run Loop
Listing 3-7 shows the init
and addToCurrentRunLoop
methods of the RunLoopSource
class. The init
method creates the CFRunLoopSourceRef
opaque type that must actually be attached to the run loop. It passes the RunLoopSource
object itself as the contextual information so that the callback routines have a pointer to the object. Installation of the input source does not occur until the worker thread invokes the addToCurrentRunLoop
method, at which point the RunLoopSourceScheduleRoutine
callback function is called. Once the input source is added to the run loop, the thread can run its run loop to wait on it.
Listing 3-7 Installing the run loop source
- (id)init |
{ |
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL, |
&RunLoopSourceScheduleRoutine, |
RunLoopSourceCancelRoutine, |
RunLoopSourcePerformRoutine}; |
|
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context); |
commands = [[NSMutableArray alloc] init]; |
|
return self; |
} |
|
- (void)addToCurrentRunLoop |
{ |
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); |
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode); |
} |
Coordinating with Clients of the Input Source
For your input source to be useful, you need to manipulate it and signal it from another thread. The whole point of an input source is to put its associated thread to sleep until there is something to do. That fact necessitates having other threads in your application know about the input source and have a way to communicate with it.
One way to notify clients about your input source is to send out registration requests when your input source is first installed on its run loop. You can register your input source with as many clients as you want, or you can simply register it with some central agency that then vends your input source to interested clients. Listing 3-8 shows the registration method defined by the application delegate and invoked when the RunLoopSource
object’s scheduler function is called. This method receives theRunLoopContext
object provided by the RunLoopSource
object and adds it to its list of sources. This listing also shows the routine used to unregister the input source when it is removed from its run loop.
Listing 3-8 Registering and removing an input source with the application delegate
- (void)registerSource:(RunLoopContext*)sourceInfo; |
{ |
[sourcesToPing addObject:sourceInfo]; |
} |
|
- (void)removeSource:(RunLoopContext*)sourceInfo |
{ |
id objToRemove = nil; |
|
for (RunLoopContext* context in sourcesToPing) |
{ |
if ([context isEqual:sourceInfo]) |
{ |
objToRemove = context; |
break; |
} |
} |
|
if (objToRemove) |
[sourcesToPing removeObject:objToRemove]; |
} |
Note: The callback functions that call the methods in the preceding listing are shown in Listing 3-4 and Listing 3-6.
Signaling the Input Source
After it hands off its data to the input source, a client must signal the source and wake up its run loop. Signaling the source lets the run loop know that the source is ready to be processed. And because the thread might be asleep when the signal occurs, you should always wake up the run loop explicitly. Failing to do so might result in a delay in processing the input source.
Listing 3-9 shows the fireCommandsOnRunLoop
method of the RunLoopSource
object. Clients invoke this method when they are ready for the source to process the commands they added to the buffer.
Listing 3-9 Waking up the run loop
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop |
{ |
CFRunLoopSourceSignal(runLoopSource); |
CFRunLoopWakeUp(runloop); |
} |
Note: You should never try to handle a SIGHUP
or other type of process-level signal by messaging a custom input source. The Core Foundation functions for waking up the run loop are not signal safe and should not be used inside your application’s signal handler routines. For more information about signal handler routines, see the sigaction
man page.