Source: Unknown

After more than a year of iOS development, I still have very limited in-depth understanding of iOS and Objective-C. Most of them are still at the API level, which is a sad thing. To learn a language well, you need to know it at a deep level, so that you don’t feel overwhelmed when you’re using it and when all sorts of weird questions come up. Let’s cut the crap and get to the point.

Don’t know if you have thought about this question, after an application running on there, if it do anything wrong, like still, this application will not have any actions occur spontaneously, but if we click a button on the interface, this time there will be a corresponding button to respond to events. It feels like the app is always on call, resting when no one else is working, and responding immediately when asked to do something. In fact, it’s run Loop’s work.

Thread and run loop

1.1 Types of threading tasks

Let’s talk about threads. Some threads perform tasks in a straight line from start to finish; Other threads have to do a circle, loop over and over until they somehow terminate it. A linear thread, like a simple Hello World, runs and prints, and its life is over, like a flash in the pan; The circular type, such as the operating system, runs until you shut down. In iOS, circular threads are implemented by running loops.

1.2 Relationship between Threads and Run Loop

Run loop, as its name suggests, is a loop of some kind, and together with Run means a loop that is always running. In fact, run loop and thread are closely related. It can be said that run loop is for thread, without thread, it has no need to exist. Run Loops are part of the thread infrastructure. Both Cocoa and CoreFundation provide Run Loop objects for configuring and managing thread Run loops. Each thread, including the main thread of the program, has a corresponding Run loop object.

1.2.1 Run loop for the main thread is started by default.

In iOS apps, there is a main() function after the app starts:

     int main(int argc, char *argv[])

     {

            @autoreleasepool {

              return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));

           }

  }

The UIApplicationMain() function, which sets an NSRunLoop object for the main thread, explains why our application can rest when no one is working and respond when it needs to.

1.2.2 For other threads, the Run loop is not started by default. If you need more thread interaction, you can configure and start the run loop manually, but not if the thread is only performing a long, determined task.

1.2.3 In any Cocoa application thread, you can pass:

NSRunLoop   *runloop = [NSRunLoop currentRunLoop];

To get the run loop of the current thread.

1.3 Some notes on Run Loop

The NSRunLoop class in Cocoa is not thread-safe

You can’t manipulate a run loop object in one thread from another thread; that might have unintended consequences. Fortunately, the opaque CFRunLoopRef class in the CoreFundation is thread-safe, and the two types of Run loop can be mixed. The NSRunLoop class in Cocoa can be used by instance methods:

– (CFRunLoopRef)getCFRunLoop;

Get the corresponding CFRunLoopRef class to achieve thread safety.

1.3.2 The management of Run loop is not completely automatic.

We still have to design thread code to start the Run loop at the right time and respond to the input event correctly, if the run loop is needed in the thread, of course. In addition, we need to use a while/for statement to drive the run loop. The following code successfully drives a run loop:

     BOOL isRunning = NO;

      do {

            isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

     } while (isRunning);

1.3.3 Run Loop is also responsible for creating and releasing autoRelease pools

In projects that use manual memory management, many automatically freed objects are often used, and if these objects cannot be freed immediately, memory usage can increase dramatically. Run Loop does this for us. At the end of each Run loop, it releases an AutoRelease pool and all autorelease type variables in the pool are released.

1.3.4 Advantages of Run Loop

A run loop is an event processing loop that continuously listens for and processes input events and assigns them to the corresponding target for processing. If you just want to do this, you might think that a simple while loop would do the trick, but it would take a lot of trouble to make such a complicated mechanism. Clearly, Apple’s architects don’t work for nothing. They’ve already thought about what you think.

First of all, NSRunLoop is a more sophisticated message processing mode. It abstracts and encapsulates the message processing so that you don’t have to deal with trivial, low-level, concrete message processing. In NSRunLoop each message is packaged in either the input source or timer source (see below).

Second, and most importantly, using run Loop allows your threads to work when they are working and sleep when they are not, which can save a lot of system resources.

 

Run loop

2.1 Enter the event source

The Run loop receives input events from two different sources: input source and timer source. Both sources use a particular processing routine of the program to process incoming events. Figure 1 shows the conceptual structure of run Loop and its various sources.

It should be noted that when you create an input source, you need to assign it to one or more modes in the Run loop (what is a mode is covered below). Patterns affect listening sources only during specific events. For the most part, run loop runs in default mode, but you can also make it run in custom mode. If a source is not listened on in the current mode, any messages generated by it will only be delivered if the Run loop runs in its associated mode.

Figure -1 Runloop structure and input source type

 

2.1.1 Input Source

Deliver asynchronous events, usually messages from other threads or programs. The input source passes the asynchronous message to the corresponding processing routine and calls the runUntilDate: method to exit (called by the associated NSRunLoop object in the thread).

2.1.1.1 Port-based input source

Port-based input sources are sent automatically by the kernel.

There is built-in support for port-based sources created using port-related objects and functions in Cocoa and Core Foundation. For example, in Cocoa you never need to create input sources directly. You simply create the port object and add it to the Run loop using the NSPort method. The port object takes care of creating and configuring the input source itself.

In Core Foundation, you have to manually create the port and its Run loop source. We can use port-related functions (CFMachPortRef, CFMessagePortRef, CFSocketRef) to create appropriate objects. The following example shows how to create a port-based input source, add it to the Run loop and start it:

void createPortSource()

{

    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR(“com.someport”),myCallbackFunc, NULL, NULL);

    CFRunLoopSourceRef source =  CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);

    while (pageStillLoading) {

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        CFRunLoopRun();

        [pool release];

    }

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    CFRelease(source);

}

2.1.1.2 Customizing Input Sources

Custom input sources need to be sent manually from other threads.

To create a custom input source, you must use the CFRunLoopSourceRef type-specific function in Core Foundation. You can configure custom input sources using callback functions. The Core Fundation calls callbacks at various points in the configuration source, handles input events, and cleans up the source when it is removed from the Run loop.

In addition to defining the behavior of custom input sources when events arrive, you must also define messaging mechanisms. This part of the source runs in a separate thread and is responsible for passing data to the source and telling it to process the data while it is waiting to be processed. The definition of the messaging mechanism is up to you, but it’s best not to get too complicated. The following is an example of creating and starting a custom input source:

void createCustomSource()

{

    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};

    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    while (pageStillLoading) {

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        CFRunLoopRun();

        [pool release];

    }

    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

    CFRelease(source);

}

2.1.1.3 Selector source on Cocoa

In addition to port-based sources, Cocoa defines custom input sources that allow you to execute selector methods in any thread. As with port-based sources, performing the selector request serializes on the target thread, mitigating many of the synchronization problems that can easily arise when multiple methods are allowed on a thread. Unlike port-based sources, a selector is automatically removed from the Run loop after execution.

When performing a selector on another thread, the target thread must have an active run loop. For the thread you’re creating, this means that the thread will not execute the selector method until you explicitly start the Run loop, but will remain dormant.

The NSObject class provides a selector method like this:

– (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;

 

2.1.2 Timer Source

The timing source delivers messages synchronously at a preset point in time, either at a specific time or at repeated intervals. The timing source passes the message directly to the handler and does not exit the Run loop immediately.

It is important to note that while timers can generate time-based notifications, they are not a real-time mechanism. Like the input source, timers are dependent on the particular mode of your run loop. If the timer is in a mode that is not currently monitored by the Run Loop, the timer will not start until the Run loop is in the corresponding mode. Similarly, if the timer starts during the run loop’s processing of an event, the timer waits until the next run loop starts the corresponding handler. If the Run loop stops running, the timer will never start.

There are two ways to create a timer source,

Method one:

NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 4.0

                                                     target:self

                                                    selector:@selector(backgroundThreadFire:)

                                                    userInfo:nil

                                                    repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];

 

Method 2:

[NSTimer scheduledTimerWithTimeInterval:10

                                        target:self

                                       selector:@selector(backgroundThreadFire:)

                                       userInfo:nil

                                       repeats:YES];

2.2 RunLoop observer

The source fires when an appropriate synchronous or asynchronous event occurs, while the Run Loop observer fires at a specific time when the Run loop itself is running. You can use run Loop observers to prepare for processing a particular event or for a thread to go to sleep. You can associate run Loop observers with the following events:

1. The Runloop entrance

2. Runloop When to process a timer

3. When Runloop processes an input source

4. When Runloop goes to sleep

5. Events to be processed before the Runloop is awakened when it is

6. Runloop terminated

Similar to timers, you can specify that run Loop observers can be used only once or in a loop at the time of creation. If used only once, it will remove itself from the Run loop when it starts, but the observer of the loop will not. Defining an observer and adding it to the Run loop can only be done using Core Fundation. The following example demonstrates how to create an observer for a run loop:

– (void)addObserverToCurrentRunloop

{

    // The application uses garbage collection, so noautorelease pool is needed.

    NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];

   

    // Create a run loop observer and attach it to the runloop.

    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};

    CFRunLoopObserverRef   observer =CFRunLoopObserverCreate(kCFAllocatorDefault,

                                                              kCFRunLoopBeforeTimers, YES, 0, &myRunLoopObserver, &context);

   

    if (observer)

    {

        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];

        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);

    }

}

Where, kCFRunLoopBeforeTimers indicates that the event is selected to be processed before the listening timer is triggered, followed by YES indicating loop listening.

 

2.3 Event queue of RunLoop

Each time you run the Run loop, your thread’s Run Loop pair automatically processes the previously unprocessed message and notifies relevant observers. The specific order is as follows:

  1. Notifies the observer that run loop has started

  2. Notifies the observer of any timer that is about to start

  3. Notifies the observer of any non-port-based sources that are about to start

  4. Start any prepared non-port-based sources

  5. If the port-based source is ready and in a waiting state, start immediately; Go to Step 9.

  6. Notifies the observer that the thread is asleep

  7. The thread is put to sleep until one of the following events occurs:

    • An event reaches a port-based source
    • Timer start
    • The Run loop time has timed out. Procedure
    • Run loop is explicitly woken up
  8. Notify the observer that the thread will be awakened.

  9. Handle unprocessed events

    • If the user-defined timer starts, process the timer event and restart the Run loop. Enter Step 2.
    • If the input source is started, the corresponding message is passed
    • If the run loop is awakened explicitly and the time has not expired, restart the run loop. Enter Step 2.
  10. Notifies the observer that run loop ends.

Because the timer and the observer of the input source deliver the message before the corresponding event occurs, there can be a discrepancy between the notification time and the actual event time. If you need precise time control, you can use sleep and wake notifications to help you correct the time of the actual event.

Because timers and other periodic events often need to be delivered when you run the Run loop, undoing the run loop also terminates message delivery. The classic example is mouse path tracking. Because your code gets the message directly rather than passing it through the program, the active timer doesn’t start until the mouse trace ends and gives control to the program.

A Run loop can be woken up explicitly by a Run loop object. Other messages can also wake up the Run loop. For example, adding a new non-port-based source wakes up the Run loop so that it can process the input source immediately without waiting for other events to occur.

As you can see from this event queue:

(1) If an event arrives, the message is passed to the appropriate handler, and when the runloop finishes processing the event, the Run loop exits, regardless of whether the scheduled time is up. You can restart the Run loop to wait for the next event.

② If a thread has a source that needs to be processed, but the event does not arrive, the thread will sleep and wait for the corresponding event to occur. This is why a Run loop can keep a thread busy when it is working and dormant when it is not.

 

2.4 When to use Run Loop

You only need to explicitly run a Run loop when creating a helper thread for your program. Run loop is a key part of the program’s main thread infrastructure. So the Cocoa and Carbon programs provide a loop for code to run the main program and start the Run loop automatically. The RUN method of UIApplication in IOS applications (or NSApplication in Mac OS X), as part of the program startup step, starts the main loop of the program when it normally starts. Similarly, the RunApplicationEventLoop function starts the main loop for the Carbon program. If you use the templates provided by Xcode to create your programs, you never need to explicitly call these routines yourself.

For worker threads, you need to determine if a run loop is required. If necessary, configure and start it yourself. You don’t need to start a thread’s Run loop in any case. For example, you should avoid starting run Loop when using threads to handle a predefined, long-running task. Run loop is only needed when you want more interaction with the thread, as in the following case:

  1. Use ports or custom input sources to communicate with other threads
  2. Use thread timers
  3. Any performSelector you use in Cocoa… The method of
  4. Make threads work periodically

If you decide to use Run loop in your application, it’s easy to configure and start. As with all threaded programming, you need to plan for situations in which the helper thread exits. Letting a thread exit naturally is often better than forcing it to close.

  ——– by wangzz

Reference Documents:

Developer.apple.com/library/ios…