- Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
Write in front: iOS underlying principle exploration is my usual development and learning in the accumulation of a section of advanced road. Record my continuous exploration of the journey, I hope to be helpful to all readers.Copy the code
The directory is as follows:
- IOS underlying principles of alloc exploration
- The underlying principles of iOS are explored
- The underlying principles of iOS explore the nature of objects & isa’s underlying implementation
- Isa-basic Principles of iOS (Part 1)
- Isa-basic Principles of iOS (Middle)
- Isa-class Basic Principles of iOS Exploration (2)
- IOS fundamentals explore the nature of Runtime Runtime & methods
- Objc_msgSend: Exploring the underlying principles of iOS
- Slow lookups in iOS Runtime
- A dynamic approach to iOS fundamentals
- The underlying principles of iOS explore the message forwarding process
- Dyld (part 1)
- IOS Basic Principles of application loading principle dyld (ii)
- IOS basic principles explore the loading of classes
- The underlying principles of iOS explore the loading of categories
- IOS underlying principles to explore the associated object
- IOS underlying principle of the wizard KVC exploration
- Exploring the underlying principles of iOS: KVO Principles | More challenges in August
- Exploring the underlying principles of iOS: Rewritten KVO | More challenges in August
- The underlying principles of iOS: Multi-threading | More challenges in August
- GCD functions and queues in iOS
- GCD principles of iOS (Part 1)
- IOS Low-level – What do you know about deadlocks?
- IOS Low-level – Singleton destruction is possible?
- IOS Low-level – Dispatch Source
- IOS bottom – a fence letter blocks the number
- IOS low-level – Be there or be Square semaphore
- IOS underlying GCD – In and out into a scheduling group
- Basic principles of iOS – Basic use of locks
- IOS underlying – @synchronized Flow analysis
- IOS low-level – The principle of lock exploration
- IOS Low-level – allows you to implement a read/write lock
- Implementation of Objective-C Block
- Implementation of Objective-C Block
- IOS bottom – Block, comprehensive resolution!
- IOS Basics – Startup Optimization (part 1)
- IOS Basics – Startup Optimization (2)
- Exploration of basic principles of iOS — Memory management of memory five areas
- Tagged Pointer Format Changes for memory management
- Retain & Release for iOS
- Exploring the underlying principles of iOS — Weak reference tables for memory management
- Explore the underlying principles of iOS – @Autoreleasepool for memory management
Summary of the above column
- Summary of iOS underlying principles of exploration
Sort out the details
- Summary of iOS development details
preface
As a developer, after our project is installed on the device, it can run all the time when the user opens it, and respond to the user’s click event when the user clicks. We have a scheduled task to deal with, when the time is up, the system can deal with our task; The whole process is CPU friendly under normal conditions, so how does the system achieve this? After reading today’s content, I think you will have a new understanding. So without further ado, let’s get started.
RunLoop
Running loops is part of the thread-related infrastructure. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of running a loop is to keep your thread busy when there is work to do and to put your thread to sleep when there is no work to do.
Run cycle management is not completely automatic. You still have to design the thread’s code to start the running loop at the appropriate time and respond to incoming events. Both Cocoa and Core Foundation provide run loop objects to help you configure and manage run loops for your threads. Your application does not need to explicitly create these objects; Each thread, including the main thread of the application, has an associated run loop object. However, only the worker threads need to explicitly run their run loops. As part of the application startup process, the application framework automatically sets up and runs the run loop on the main thread.
RunLoop parsing
Running a loop is very much like its name. It is the loop that your thread enters and uses to run event handlers in response to incoming events. Your code provides the control statements that implement the actual loop part of the Run loop — in other words, your code provides the while or for loop that drives the Run loop. In your loop, you use the run loop object to “run” the event handling code that receives events and calls the installed handlers.
The run loop receives events from two different types of sources. The input source delivers asynchronous events, usually messages from another thread or a different application. Timer sources provide synchronous events that occur at predetermined times or repeated intervals. Both types of sources use application-specific handler routines to handle incoming events.
Figure 3-1 shows the conceptual structure of the running loop and the various sources. The input source passes the asynchronous event to the appropriate handler and causes the runUntilDate: method (called on the thread’s associated NSRunLoop object) to exit. Timer sources pass events to their handler routines without causing the run loop to exit.
In addition to processing input sources, running loops generate notifications about the behavior of running loops. Registered run-loop observers can receive these notifications and use them for additional processing on the thread. You use Core Foundation to install the run loop viewer on the thread.
The following sections provide more information about running loop components and how they run. They also describe notifications generated at different times during event processing.
Run cycle mode
A running loop mode is a collection of input sources and timers for monitoring and running loop observer collections for notification. Each time you run a run loop, you specify (explicitly or implicitly) a particular “mode” to run. During that part of the loop, only the sources associated with the pattern are monitored and allowed to deliver their events. (Similarly, only observers associated with the pattern are notified of the progress of running the loop.) Sources associated with other schemas retain any new events until they are subsequently passed through the loop in the appropriate schema.
In your code, you can recognize patterns by name. Both Cocoa and Core Foundation define default schemas and several common schemas, along with strings to specify those schemas in your code. You can define a custom schema by simply specifying a custom string for the schema name. Although the names you assign to your custom schemas are arbitrary, the content of those schemas is not. You must be sure to add one or more input sources, timers, or run loop observers to any patterns you create for them to be useful.
You can use patterns to filter out events from unwanted sources during specific delivery of the running loop. Most of the time, you’ll want to run your run loop in the “default” mode defined by the system. However, the modal panel may run in “modal” mode. In this mode, only the source associated with the modal panel passes events to the thread. For worker threads, you can use custom patterns to prevent low-priority sources from passing events during time-critical operations.
Note: The pattern differentiates based on the source of the event rather than the type of event. For example, you wouldn’t use patterns to match only mouse-down events or keyboard events. You can use mode to listen on a different set of ports, temporarily suspend the timer, or otherwise change the source you are currently monitoring and run the loop observer.
Table 3-1 lists the standard schema defined by Cocoa and Core Foundation and a description of when to use it. The name column lists the actual constants used to specify the schema in the code.
Mode | Name | Description |
---|---|---|
Default | NSDefaultRunLoopMode (Cocoa)kCFRunLoopDefaultMode (Core Foundation) |
The default mode is the mode used for most operations. In most cases, you should use this mode to start your run loop and configure your input source. |
Connection | NSConnectionReplyMode (Cocoa) |
Cocoa compares this pattern withNSConnection Object in combination to monitor replies. You should rarely need to use this pattern yourself. |
Modal | NSModalPanelRunLoopMode (Cocoa) |
Cocoa uses this pattern to identify events for modal panels. |
Event tracking | NSEventTrackingRunLoopMode (Cocoa) |
Cocoa uses this pattern to limit incoming events during mouse-drag loops and other types of user interface trace loops. |
Common modes | NSRunLoopCommonModes (Cocoa)kCFRunLoopCommonModes (Core Foundation) |
This is a configurable set of common patterns. Associating an input source with this pattern also associates it with each pattern in the group. For Cocoa applications, this collection includes default, modal, and event tracing modes by default. Core Foundation initially only included default schemas. You can use thisCFRunLoopAddCommonMode Function to add a custom schema to a collection. |
The input source
The input source sends events asynchronously to your thread. The source of the event depends on the type of input source and is usually one of two categories. Port-based input sources monitor the Mach port of the application. Custom input sources monitor custom event sources. It doesn’t matter whether the input source is port-based or custom for your run loop. Systems typically implement two types of input sources that you can use as-is. The only difference between the two sources is how they signal. Port-based sources are automatically signaled by the kernel, while custom sources must be signaled manually from another thread.
When an input source is created, it is assigned to one or more modes of the running loop. Patterns affect which input sources are monitored at any given time. Most of the time, you run run Loop in default mode, but you can also specify a custom mode. If the input source is not in the current monitoring mode, any events it generates are retained until the run loop runs in the correct mode.
The following sections describe some of the input sources.
Port-based source
Cocoa and Core Foundation provide built-in support for creating port-based input sources using port-related objects and functions. In Cocoa, for example, you don’t have to create input sources directly at all. You simply create a port object and add the port to the run loop using the method NSPort. The port object handles the creation and configuration of the input sources you need.
In Core Foundation, you have to manually create ports and their running loop sources. In both cases, the port type you are using is opaque (related functions CFMachPortRef, CFMessagePortRef or CFSocketRef) to create the appropriate object.
For an example of how to set up and configure a custom port-based source, see Configuring port-based Input Sources.
Custom input source
To create a custom input source, you must use the function associated with the Opaque type in the CFRunLoopSourceRefCore Foundation. You can configure custom input sources using multiple callback functions. Core Foundation calls these functions at various points to configure the source, handle any incoming events, and remove the source when it is removed from the run loop.
In addition to defining the behavior of a custom source when an event arrives, you must also define an event delivery mechanism. This part of the source runs on a separate thread and is responsible for feeding its data to the input source and signaling when the data is ready for processing. The event-passing mechanism is up to you, but it doesn’t have to be too complicated.
For an example of how to create a custom input source, see Defining a Custom Input Source. For reference information on custom input sources, see also *CFRunLoopSource Reference *.
Cocoa Perform selector source
In addition to port-based sources, Cocoa defines a custom input source that allows you to execute selectors on any thread. As with port-based sources, execution selector requests are serialized on the target thread, alleviating many of the synchronization issues that can occur when multiple methods are running on a single thread. Unlike port-based sources, the executing selector source removes itself from the run loop after executing its selector.
Note: Prior to OS X V10.5, execution selector sources were primarily used to send messages to the main thread, but in OS X V10.5 and later and iOS, you can use them to send messages to any thread.
When a selector is executed on another thread, the target thread must have an active run loop. For the thread you create, this means waiting for your code to explicitly start the run loop. But, because the Lord has launched its own thread running cycle, so once the application calls applicationDidFinishLaunching: application delegate method, you can start to the calling thread. The run loop processes all queued execution selector calls through the loop at a time, rather than one during each iteration of the loop.
Table 3-2 lists the methods that NSObject can use to execute selectors on other threads. Because these methods are NSObject declared above, you can use them in any thread that can access objective-C objects, including POSIX threads. These methods do not actually create a new thread to execute the selector.
methods | describe |
---|---|
performSelectorOnMainThread:withObject:waitUntilDone:``performSelectorOnMainThread:withObject:waitUntilDone:modes: |
During the thread’s next run cycle, the specified selector is executed on the main thread of the application. These methods give you the option to block the current thread before executing the selector. |
performSelector:onThread:withObject:waitUntilDone:``performSelector:onThread:withObject:waitUntilDone:modes: |
In you haveNSThread Object on any thread that executes the specified selector. These methods give you the option to block the current thread before executing the selector. |
performSelector:withObject:afterDelay:``performSelector:withObject:afterDelay:inModes: |
Executes the specified selector on the current thread after the next run cycle and optional delay period. Because it waits until the next run loop loop to execute the selector, these methods provide an automatic minimum delay from currently executing code. Multiple queued selectors are executed one after another in the order in which they are queued. |
cancelPreviousPerformRequestsWithTarget:``cancelPreviousPerformRequestsWithTarget:selector:object: |
Allows you to useperformSelector:withObject:afterDelay: orperformSelector:withObject:afterDelay:inModes: Method to cancel messages sent to the current thread. |
For more information about these methods, see *NSObject Class Reference *.
The timer source
The timer source synchronizes events to your thread at a future preset time. Timers are a way for a thread to tell itself to do something. For example, once a certain amount of time has elapsed between consecutive keystrokes by the user, the search field can use a timer to initiate an automatic search. Using this delay gives the user the opportunity to type as many search strings as needed before starting the search.
Although it generates time-based notifications, timers are not real-time mechanisms. Like the input source, timers are associated with a particular mode of running the loop. If the timer is not in the mode currently monitored by the run cycle, it does not fire until you run the run cycle in one of the modes supported by the timer. Similarly, if a timer is fired while the Run loop is executing a handler routine, the timer will wait until the next time its handler routine is called through the Run loop. If the Run loop does not run at all, the timer will never fire.
You can configure timers to generate events only once or repeatedly. The repeat timer automatically rearranges itself based on the scheduled trigger time rather than the actual trigger time. For example, if a timer is scheduled to trigger at a specific time and every 5 seconds after that, the scheduled trigger time will always fall within the original 5-second interval, even if the actual trigger time is delayed. If the trigger time is delayed so much that one or more scheduled trigger times are missed, the timer fires only once during the missed time period. After a missed time period is triggered, the timer is rescheduled to the next scheduled trigger time.
For more information about configuring timer sources, see Configuring Timer Sources. For reference information, see *NSTimer Class Reference or CFRunLoopTimer Reference *.
Running loop observer
In contrast to a source that fires when an appropriate asynchronous or synchronous event occurs, a running loop observer fires at a particular location during the execution of the running loop itself. You can use the running loop observer to prepare the thread for a given event or before it goes to sleep. You can associate a running loop observer with the following events in a running loop:
- Entry to run the loop.
- When run Loop is about to process a timer.
- When the Run loop is about to process the input source.
- When the running cycle is about to go to sleep.
- When the Run loop wakes up, but before it processes the event that wakes it up.
- Exit the run loop.
You can use Core Foundation to add a run loop observer to your application. To create the run loop observer, you need to create a new instance CFRunLoopObserverRefopaque type. This type keeps track of your custom callback functions and the activities of interest.
Similar to timers, running loop observers can be used once or repeatedly. Single-pass observers remove themselves from the running loop after firing, while repeat observers remain connected. You can specify when creating an observer whether the observer should run once or repeatedly.
For an example of how to create a running loop observer, see Configuring a Running loop. For reference information, see *CFRunLoopObserver Reference *.
A running loop sequence of events
Each time it runs, the thread’s run loop handles pending events and generates notifications for any additional observers. The order in which it does this is very specific, as follows:
-
Notifies the observer that a run loop has been entered.
-
Notifies the observer that any ready timers are about to trigger.
-
Notifies the observer that any non-port-based input source will be triggered.
-
Triggers any non-port-based input source that is ready to trigger.
-
If the port-based input source is ready and waiting to fire, the event is handled immediately. Go to Step 9.
-
Notifies the observer that the thread is about to sleep.
-
Puts the thread to sleep until one of the following events occurs:
- The event reaches the port-based input source.
- Timer triggered.
- The timeout value set for the run loop expires.
- The run loop is awakened explicitly.
-
Notifies the observer that the thread just woke up.
-
Handle pending events.
- If a user-defined timer is triggered, the timer event is processed and the loop is restarted. Go to Step 2.
- If the input source fires, the event is passed.
- If the run loop is awakened explicitly but has not yet timed out, the loop is restarted. Go to Step 2.
-
Notify the observer that the run loop has exited.
Because the timer and observer notifications from the input source are delivered before these events actually occur, there may be a gap between the time of the notification and the actual event. If the timing between these events is important, you can use sleep and wake up notifications to help you relate the timing between actual events.
Because timers and other periodic events are passed when running the run loop, bypassing the loop interrupts the delivery of these events. A typical example of this behavior occurs whenever you implement a mouse-tracking routine by entering a loop and repeatedly requesting events from the application. Because your code fetches events directly, rather than having the application schedule them normally, the activity timer will not fire until after your mouse tracking routine exits and returns control to the application.
You can explicitly wake up a Run loop using the Run loop object. Other events may also cause the running loop to wake up. For example, adding another non-port-based input source wakes up the run loop so that the input source can be processed immediately, rather than waiting for other events to occur.
When to use Run Loop?
The only time you need to explicitly run a run loop is when you create a worker thread for your application. The running loop of the application’s main thread is a critical part of the infrastructure. Therefore, the application framework provides code to run the main application loop and start it automatically. The run method in iOS (or OS X) starts the main loop of the application as part of the normal startup sequence. If you use the Xcode template project to create your application, you never have to call these routines explicitly. UIApplication“NSApplication
For secondary threads, you need to determine whether run loop is required. If so, you need to configure the start. You do not need to start the thread’s run loop in all cases. For example, if you use a thread to perform some long-running and scheduled tasks, you may be able to avoid starting a run loop. Running loops is suitable for situations where you want more interaction with the thread. For example, if you want to do any of the following, you need to start a run loop:
- Communicate with other threads using ports or custom input sources.
- Use timers on threads.
performSelector
Use any… in Cocoa applications Methods.- Keep threads performing periodic tasks.
If you do choose to use run loops, the configuration and setup is simple. However, as with all threaded programming, you should have a plan to exit worker threads if appropriate. It is always better to terminate a thread cleanly by letting it exit rather than forcing it to terminate. Information on how to configure and exit a Run loop is described in Using Run Loop Objects.
Use run loop objects
A Run loop object provides a home interface for adding an input source, timer, and run-loop observer to your run loop, and then running it. Each thread has a run loop object associated with it. In Cocoa, this object is an instance of the NSRunLoop class. In low-level applications, it is a pointer to the opaque type of CFRunLoopRef.
Gets the run loop object
To get a run loop for the current thread, use one of the following methods:
- Used in Cocoa applications
currentRunLoop
Class methodNSRunLoop
To retrieve theNSRunLoop
Object. - The use of the
CFRunLoopGetCurrent
Function.
Although they are not free bridge types, you can get opaque types from the NSRunLoop object if needed by CFRunLoopRef. This NSRunLoop class defines a method returned by getCFRunLoop of type CFRunLoopRef that you can pass to Core Foundation routines. Because both objects refer to the same running loop, you can call a mixture of NSRunLoop objects and CFRunLoopRef opaque types as needed.
Configure the run cycle
Before you can run a Run loop on a worker thread, you must add at least one input source or timer to it. If the Run loop doesn’t have any sources to monitor, it exits as soon as you try to run it. For an example of how to add a source to a running loop, see Configuring a Running loop Source.
In addition to installing the source code, you can install run loop watchers and use them to detect the different execution stages of a run loop. To install the viewer running cycle, you need to create a CFRunLoopObserverRefopaque type and use the CFRunLoopAddObserver function to add it to your running cycle. Run loop observers must be created using Core Foundation, even for Cocoa applications.
Listing 3-1 shows the main routine for a thread that attaches a run loop observer to its run loop. The purpose of this example is to show you how to create a Run Loop observer, so the code simply sets up a Run Loop observer to monitor all run Loop activity. The basic handler routine (not shown) simply records the run loop activity while the timer request is being processed.
Listing 3-1 creates a run loop observer
- (void)threadMain {// The application uses garbage collection, so automatic pooling is not required. NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; // Create a run loop observer and attach it to the run loop. CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context); if (observer) { CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop]; CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode); } // Create and schedule timers. [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @ the selector (doFireTimer:) the userInfo: nil repeats: YES]; NSInteger loopCount = 10; Do {// Run the run loop 10 times to trigger the timer. [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; loopCount--; } while (loopCount); }Copy the code
When configuring run loops for long-lived threads, it is a good idea to add at least one input source to receive messages. Although you can connect just one timer to the run loop, once the timer is triggered, it usually fails, which causes the run loop to exit. Attaching a repeating timer makes the run loop run longer, but involves triggering the timer periodically to wake up your thread, which is essentially another form of polling. In contrast, the input source waits for the event to occur, letting your thread stay dormant until it does.
Start running cycle
Only the helper threads in the application need to start the run loop. A running loop must have at least one input source or timer to monitor. If there is no attachment, the run loop exits immediately.
There are several ways to start a Run loop, including the following:
- unconditional
- There’s a time limit
- In a particular mode
Entering your run loop unconditionally is the easiest option, but also the least desirable. Running your run loop unconditionally puts the thread into a permanent loop, which gives you little control over the Run loop itself. You can add and remove input sources and timers, but the only way to stop the running loop is to terminate it. There is also no way to run a run loop in custom mode.
Instead of running run loop unconditionally, run loop with timeout value. When you use a timeout value, the run loop runs until the event arrives or the allotted time expires. If an event arrives, it is dispatched to a handler for processing, and then a loop is run to exit. Your code can then restart the run loop to process the next event. If the allocated time expires, you can simply restart the run loop or use the time to do any needed housekeeping.
In addition to timeout values, you can run run loops using specific modes. Patterns and timeout values are not mutually exclusive and can be used when starting a run loop. Patterns restrict the types of sources that pass events to the run loop, which is described in more detail in the run loop mode.
Listing 3-2 shows a skeleton version of the main entry routine for a thread. The key part of this example shows the basic structure of a running loop. Essentially, you add an input source and a timer to the run loop, and then call one of the routines repeatedly to start the run loop. Each time the loop routine returns, you check for any conditions that might require exiting the thread. The example uses Core Foundation to run the loop routine so that it can examine the results returned and determine why the run loop exited. NSRunLoop If you use Cocoa and don’t need to check the return value, you can also run the run loop in a similar manner using the class’s methods. (For an example of a run loop that calls the NSRunLoop class method, see Listing 3-14.)
Listing 3-2 runs a run loop
- (void)skeletonThreadMain { // Set up an autorelease pool here if not using garbage collection. BOOL done = NO; // Add your sources or timers to the run loop and do any other setup. do { // Start the run loop but return after each source is handled. SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES); // If a source explicitly stopped the run loop, or if there are no // sources or timers, go ahead and exit. if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) done = YES; // Check for any other exit conditions here and set the // done variable as needed. } while (! done); // Clean up code here. Be sure to release any allocated autorelease pools. }Copy the code
Run loops can be run recursively. In other words, you can call CFRunLoopRun, CFRunLoopRunInMode, or any method NSRunLoop uses to start the run loop from the input source or timer’s handler routine. In doing so, you can use any mode you want to run nested run loops, including the mode used by external run loops.
Exit run loop
There are two ways to exit the run loop before the event is processed:
- Configure the run loop to run with a timeout value.
- Tells the run loop to stop.
If you can manage it, the preferred value is a timeout value. Specifying a timeout value allows the running loop to complete all its normal processing, including sending notifications to the running loop observer, before exiting.
Using the CFRunLoopStop function to explicitly stop the running loop produces a result similar to a timeout. The run loop sends any remaining run loop notifications and exits. The difference is that you can use this technique on run loops that start unconditionally.
While removing a run loop’s input source and timer can also cause a run loop to exit, this is not a reliable way to stop a run loop. Some system routines add input sources to the run loop to process the desired events. Because your code may not know these input sources, you will not be able to remove them, which prevents the run loop from exiting.
Thread safety and run loop objects
Thread safety depends on the API you use to operate the run loop. Functions in Core Foundation are generally thread-safe and can be called from any thread. However, if you are performing operations that change the run loop configuration, it is still a good practice to do so from the thread that owns the Run loop if possible.
The CocoaNSRunLoop class is inherently less thread-safe than the Core Foundation class. If you are using the NSRunLoop class to modify your run loop, you should only do so from the same thread that owns the run loop. Adding an input source or timer to a run loop that belongs to a different thread can cause your code to crash or run in unexpected ways.
Configure the running loop source
The following sections show examples of how to set up different types of input sources in Cocoa and Core Foundation.
Define a custom input source
Creating a custom input source involves defining the following:
- You want to enter information for source processing.
- A scheduler that lets interested customers know how to contact your input source.
- A handler routine that executes any request sent by the client.
- A cancel routine that invalidates your input source.
Because you create a custom input source to process custom information, the actual configuration is designed to be very flexible. Schedulers, handlers, and cancellation routines are the key routines that are almost always required for a custom input source. However, most of the remaining input source behavior occurs outside of these handler routines. For example, you define the mechanism for passing data to an input source and communicating the presence of an input source to other threads.
Figure 3-2 shows an example configuration of a custom input source. In this example, the main thread of the application maintains references to the input source, a custom command buffer for that input source, and a run loop to install the input source. When the main thread has a task to give to a worker thread, it issues a command to the command buffer along with any information the worker thread needs to start the task. (Since both the master and worker thread input sources have access to the command buffer, access must be synchronized.) Once the command is issued, the main thread signals the input source and wakes up the worker thread’s run loop. Upon receipt of the wake up command, a loop is run to invoke the input source’s handler, which processes the command found in the command buffer.
Figure 3-2 Operating a user-defined input source
The following sections explain the implementation of the custom input source shown above and show the key code you need to implement.
Defining the input source
Defining a custom input source requires the Core Foundation routine to configure and attach the run loop source to the run loop. Although the base handlers are C-based functions, that doesn’t prevent you from writing wrappers for those functions and implementing the code body using Objective-C or C++.
The input source shown in Figure 3-2 uses an Objective-C object to manage the 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 it to receive messages from other threads. The listing also shows the definition of the RunLoopContext object, which is really just a container object for passing the RunLoopSource object and the running loop reference to the main thread of the application.
Listing 3-3 Custom input source object definitions
@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
Copy the code
While objective-C code manages custom data from input sources, attaching input sources to running loops requires C-based callback functions. When you actually attach the run loop source to the run loop, the first of these functions is called and shown in Listing 3-4. Because this input source has only one client (the main thread), it uses the scheduler function to send messages to register itself in the application delegate on that thread. When the delegate wants to communicate with the input source, it uses the information in the RunLoopContext object to do so.
Listing 3-4 schedules the 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];
}
Copy the code
One of the most important callback routines is the one used to process custom data when the input source emits a signal. Listing 3-5 shows the execution callback routine associated with the RunLoopSource object. This function simply forwards the request to complete the job to the sourceFired method, which then processes any commands that exist in the command buffer.
Listing 3-5 performs the work in the input source
void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
Copy the code
If you use CFRunLoopSourceInvalidate function is removed from the operating cycle input source, the system will invoke the cancellation of the input source routines. You can use this routine to inform 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 in the RunLoopSource object. This function sends another RunLoopContext object to the application delegate, but this time requires the delegate to remove the reference to the run loop source.
Listing 3-6 Invalid input sources
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];
}
Copy the code
Installs the input source on the run loop segment
Listing 3-7 shows the init and addToCurrentRunLoop methods of the RunLoopSource class. The init method creates the CFRunLoopSourceRef opaque type, which must actually be attached to the run loop. It passes the RunLoopSource object itself as context information so that the callback routine has a pointer to the object. The secondary thread calls addToCurrentRunLoop method and invoke RunLoopSourceScheduleRoutine before the callback function, not install the input source. Once the input source is added to the run loop, the thread can run its run loop to wait for it.
Listing 3-7 Install run loop sources
- (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);
}
Copy the code
Coordinate with customers of the input source
To make the input source useful, you need to manipulate it and signal it from another thread. The whole point of an input source is to put its associated threads to sleep until there is something to do. This fact requires that other threads in the application know about the input source and have a way to communicate with it.
One way to notify the client of your input source is to send a registration request when the input source is first installed on its running loop. You can register your input source with any number of customers, or you can simply register it with some central agency and make it available to interested customers. Listing 3-8 shows the registration method defined by the application delegate and invoked when calling the scheduler function of the RunLoopSource object. This method receives the RunLoopSource object provided by the RunLoopContext object and adds it to its source list. The list also shows routines used to unregister when the input source is removed from the run loop.
Listing 3-8 registers and deletes input sources using application delegates
- (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];
}
Copy the code
Send input source
After passing the data to the input source, the client must signal the source and wake it up to run the loop. Signals the source to let the running loop know that the source is ready for processing. And because the thread may be asleep when the signal occurs, you should always explicitly wake up the run loop. Failure to do so may result in delayed processing of the input source.
Listing 3-9 shows the fireCommandsOnRunLoop method for the RunLoopSource object. Clients call this method when they are ready for the source to process the commands they have added to the buffer.
Listing 3-9 wakes up the run loop
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
CFRunLoopSourceSignal(runLoopSource);
CFRunLoopWakeUp(runloop);
}
Copy the code
Configuring the Timer Source
To create a timer source, you simply create a timer object and schedule it on the run loop. In Cocoa, you use the NSTimer class to create new timer objects, and in Core Foundation, you use the CFRunLoopTimerRef opaque type. Internally, the NSTimer class is just an extension of Core Foundation, providing some handy features, such as the ability to create and schedule timers using the same method.
In Cocoa, you can create and schedule timers at once using one of the following class methods:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
These methods create timers and add them to the running loop of the current thread in default mode (NSDefaultRunLoopMode). You can also schedule timers manually if you prefer by creating an NSTimer object and then adding it to the run loop using the NSRunLoop’s addTimer:forMode: method. Both techniques do essentially the same thing, but give you different levels of control over timer configuration. For example, if you create a timer and manually add it to a run loop, you can operate in a mode other than the default mode. Listing 3-10 shows how to create timers using both techniques. The first timer is initially delayed by 1 second, but fires periodically every 0.1 second after that. The second timer started firing after an initial 0.2 second delay, and then every 0.2 second.
Listing 3-10 creates and schedules timers using NSTimer
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; / / Create and schedule the first timer. NSDate * futureDate = [NSDate dateWithTimeIntervalSinceNow: 1.0]; NSTimer * myTimer = [[NSTimer alloc] initWithFireDate: futureDate interval: 0.1 target: the self selector:@selector(myDoFireTimer1:) userInfo:nil repeats:YES]; [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode]; / / Create and schedule the second timer. [NSTimer scheduledTimerWithTimeInterval: 0.2 target: the self selector:@selector(myDoFireTimer2:) userInfo:nil repeats:YES];Copy the code
Listing 3-11 shows the code required to configure the timer using the Core Foundation functions. While this example does not pass any user-defined information in a context structure, you can use this structure to pass any custom data needed by the timer. For more information about the contents of this structure, see its description in *CFRunLoopTimer Reference*.
Listing 3-11 uses Core Foundation to create and schedule timers
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);Copy the code
Configure a port-based input source
Both Cocoa and Core Foundation provide port-based objects for communication between threads or processes. The following sections show you how to set up port communication using several different types of ports.
Configure the NSMachPort object
To establish a local connection with the NSMachPort object, you need to create the port object and add it to the main thread’s run loop. When you start a worker thread, you pass the same object to the thread’s entry point function. A worker thread can use the same object to send messages back to the main thread.
Implement the main thread code
Listing 3-12 shows the main thread code to start the helper thread. Because the Cocoa framework performs many of the intervention steps to configure ports and run loops, the launchThread method is significantly shorter than its Core Foundation equivalent (Listing 3-17); However, both behave almost identically. One difference is that this method sends the NSPort object directly, rather than sending the name of the local port to the worker thread.
Listing 3-12 Main thread startup method
- (void)launchThread { NSPort* myPort = [NSMachPort port]; if (myPort) { // This class handles incoming port messages. [myPort setDelegate:self]; // Install the port as an input source on the current run loop. [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode]; // Detach the thread. Let the worker release the port. [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:) toTarget:[MyWorkerClass class] withObject:myPort]; }}Copy the code
To set up a two-way communication channel between threads, you might want the worker thread to send its local port to the main thread in a check-in message. Receiving a check-in message lets your main thread know that starting the second thread went well, and gives you a way to send more messages to that thread.
Listing 3-13 shows the handlePortMessage: method for the main thread. This method is called when data arrives at the thread’s own local port. When the check-in message arrives, the method retrieves the port of the worker thread directly from the port message and saves it for later use. \
Listing 3-13 handles Mach port messages
#define kCheckinMessage 100 // Handle responses from the worker thread. - (void)handlePortMessage:(NSPortMessage *)portMessage { unsigned int message = [portMessage msgid]; NSPort* distantPort = nil; If (message == kCheckinMessage) {// Get the worker thread's communications port. distantPort = [portMessage sendPort]; // Retain and save the worker port for later use. [self storeDistantPort:distantPort]; } else { // Handle other messages. } }Copy the code
Implement secondary thread code
For a worker thread, you must configure the thread and communicate information back to the main thread using the specified port.
Listing 3-14 shows the code for setting up the worker thread. After creating an autorelease pool for the thread, the method creates a worker object to drive the thread execution. The worker object’s sendCheckinMessage:
method (shown in Listing 3-15) creates a local port for the worker thread and sends a check-in message back to the main thread.
Listing 3-14 starting a worker thread with a Mach port
+(void)LaunchThreadWithPort:(id)inData { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // Set up the connection between this thread and the main thread. NSPort* distantPort = (NSPort*)inData; MyWorkerClass* workerObj = [[self alloc] init]; [workerObj sendCheckinMessage:distantPort]; [distantPort release]; // Let the run loop process things. do { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } while (! [workerObj shouldExit]); [workerObj release]; [pool release]; }Copy the code
With NSMachPort, local and remote threads can use the same port object for one-way communication between threads. In other words, a local port object created by one thread becomes a remote port object created by another thread.
Listing 3-15 shows the check-in routine for the worker thread. This method sets up its own local port for future communication and then sends a check-in message to the main thread. This method uses the port object received in the LaunchThreadWithPort: method as the target of the message.
Listing 3-15 sending a check-in message using the Mach port
// Worker thread check-in method - (void)sendCheckinMessage:(NSPort*)outPort { // Retain and save the remote port for future use. [self setRemotePort:outPort]; // Create and configure the worker thread port. NSPort* myPort = [NSMachPort port]; [myPort setDelegate:self]; [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode]; // Create the check-in message. NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort receivePort:myPort components:nil]; if (messageObj) { // Finish configuring the message and send it immediately. [messageObj setMsgId:setMsgid:kCheckinMessage]; [messageObj sendBeforeDate:[NSDate date]]; }}Copy the code
Configure the NSMessagePort object
To establish a local connection with the NSMessagePort object, you cannot simply pass the port object between threads. The remote message port must be obtained by name. To make this happen in cocoa, you need to register your local port with a specific name, and then pass that name to the remote thread so it can get the appropriate port object to communicate with. Listing 3-16 shows the process of creating and registering a message port in case you want to use it.
Listing 3-16 Registering message ports
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];
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
Copy the code
Configure port-based input sources in Core Foundation
This section demonstrates how to use Core Foundation to set up a two-way communication channel between the main thread and the worker thread of your application.
Listing 3-17 shows the code that the main thread of the application calls to start the helper thread. The first thing the code does is set the CFMessagePortRef opaque type to listen for messages from the worker thread. The worker thread needs the name of the port to connect in order to deliver the string value to the worker thread’s entry point function. Port names should generally be unique in the current user context; Otherwise, you may run into conflicts.
Lists 3-17 attaching Core Foundation message ports to new threads
#define kThreadStackSize (8 *4096) OSStatus MySpawnThread() { // Create a local port for receiving responses. CFStringRef myPortName; CFMessagePortRef myPort; CFRunLoopSourceRef rlSource; CFMessagePortContext context = {0, NULL, NULL, NULL, NULL}; Boolean shouldFreeInfo; // Create a string with the port name. myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread")); // Create the port. myPort = CFMessagePortCreateLocal(NULL, myPortName, &MainThreadResponseHandler, &context, &shouldFreeInfo); if (myPort ! = NULL) { // The port was successfully created. // Now create a run loop source for it. rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0); if (rlSource) { // Add the source to the current run loop. CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode); // Once installed, these can be freed. CFRelease(myPort); CFRelease(rlSource); } } // Create the thread and continue processing. MPTaskID taskID; return(MPCreateTask(&ServerThreadEntryPoint, (void*)myPortName, kThreadStackSize, NULL, NULL, NULL, 0, &taskID)); }Copy the code
After the port is installed and the thread is started, the main thread can continue to execute normally while waiting for the thread to check in. Sign-in message arrived, it will be sent to the main thread of MainThreadResponseHandler function, as shown in listing 3 to 18. This function extracts the port name of the worker thread and creates a pipe for future communication.
Listing 3-18 Receiving a check-in message
#define kCheckinMessage 100 // Main thread port message handler CFDataRef MainThreadResponseHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void* info) { if (msgid == kCheckinMessage) { CFMessagePortRef messagePort; CFStringRef threadPortName; CFIndex bufferLength = CFDataGetLength(data); UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0); CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer); threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE); // You must obtain a remote message port by name. messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName); If (messagePort) {// Retain and save the thread's comm port for future reference. AddPortToListOfActiveThreads(messagePort); // Since the port is retained by the previous function, release // it here. CFRelease(messagePort); } // Clean up. CFRelease(threadPortName); CFAllocatorDeallocate(NULL, buffer); } else { // Process other messages. } return NULL; }Copy the code
After the main thread is configured, the only thing left is for the newly created worker thread to create its own port and check in. Listing 3-19 shows the entry point function for the worker thread. This function extracts the port name of the main thread and uses it to create a remote connection back to the main thread. The function then creates a local port for itself, installs it on the thread’s run loop, and sends a check-in message to the main thread that contains the name of the local port.
Listing 3-19 sets the thread structure
OSStatus ServerThreadEntryPoint(void* param) { // Create the remote port to the main thread. CFMessagePortRef mainThreadPort; CFStringRef portName = (CFStringRef)param; mainThreadPort = CFMessagePortCreateRemote(NULL, portName); // Free the string that was passed in param. CFRelease(portName); // Create a port for the worker thread. CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID()); // Store the port in this thread's context info for later reference. CFMessagePortContext Context = {0, mainThreadPort, NULL, NULL, NULL}; Boolean shouldFreeInfo; Boolean shouldAbort = TRUE; CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL, myPortName, &ProcessClientRequest, &context, &shouldFreeInfo); if (shouldFreeInfo) { // Couldn't create a local port, so kill the thread. MPExit(0); } CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0); if (! rlSource) { // Couldn't create a local port, so kill the thread. MPExit(0); } // Add the source to the current run loop. CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode); // Once installed, these can be freed. CFRelease(myPort); CFRelease(rlSource); // Package up the port name and send the check-in message. CFDataRef returnData = nil; CFDataRef outData; CFIndex stringLength = CFStringGetLength(myPortName); UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0); CFStringGetBytes(myPortName, CFRangeMake(0,stringLength), kCFStringEncodingASCII, 0, FALSE, buffer, stringLength, NULL); outData = CFDataCreate(NULL, buffer, stringLength); CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL); // Clean up thread data structures. CFRelease(outData); CFAllocatorDeallocate(NULL, buffer); // Enter the run loop. CFRunLoopRun(); }Copy the code
Once it enters the run loop, all future events sent to the thread port are handled by the ProcessClientRequest function. The implementation of this function depends on the type of work done by the thread and is not shown here.