How to Use Runloop and iosrunloop in iOS development tutorials
In the iOS development process, the use of Runloop should not be underestimated. Although it is not very common, this part is also very important for iOS development, in addition, it is also a mandatory part of the interviewer when looking for a job during the interview. Let's talk about the theory and application of Runloop.
1. Concept of Runloop
1. Concept of Runloop: Runloops is part of the thread-related basic framework. A Runloop is a loop of event processing. It is used to schedule tasks and process input events without interruption. In fact, it is a do-while loop, which continuously processes various tasks (such as Timer and Observer ). The purpose of Runloop is to make the thread busy with work when there is a job, and sleep when there is no job.
2. nsunloop and CFRunLoopRef
During development, we cannot operate the Runloop object of another thread in one thread. Doing so may cause immeasurable consequences. Fortunately, the opaque class CFRunLoopRef in CoreFundation is thread-safe, and the two types of Runloop can be used in combination.
In Cocoa, you can use the example method-(CFRunLoopRef) getCFRunLoop;
Obtain the corresponding CFRunLoopRef class to achieve thread security.
CFRunLoopRef is within the CoreFoundation framework. It provides APIs for C functions, all of which are thread-safe.
Nsunloop is a CFRunLoopRef-based encapsulation that provides object-oriented APIs, but these APIs are NOT thread-safe.
3. Relationship between Runloop and thread
Runloop indicates a loop. If it is put together with run, it indicates a loop that has been running. In fact, Runloop is inseparable from the thread. It can be said that Runloop is generated for the thread. Without a thread, Runloop is unnecessary. Runloops is the basic framework of the thread. Both Cocoa and CoreFundation provide Runloop objects for convenient configuration and management of the thread's Runloop (the following uses Cocoa as an example ). Each thread, including the main thread of the program, has a corresponding Runloop object.
4. The Runloop in the main thread is started by default.
In iOS applications, the program starts with the following main () function:
Int main (int argc, char * argv []) {
@ Autoreleasepool {
Return UIApplicationMain (argc, argv, nil, NSStringFromClass ([appDelegate class]);
}
}
The focus is on the UIApplicationMain () function. This method will set a nsunloop object for main thread, which explains why our application can rest when no one is operating, when it needs to work, it can respond immediately. For other threads, Runloop is not started by default. If you need more thread interactions, You can manually configure and start it, this is not required if the thread only executes a long-time confirmed task.
In any Cocoa program thread, you can get the runloop of the current thread through: nsunloop * Runloop = [nsunloop currentRunLoop.
5. Runloop interfaces and several classes
In CoreFoundation, RunLoop has five classes: CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, CFRunLoopTimerRef, and condition. The condition class is not exposed, but is encapsulated through the CFRunLoopRef interface. Their relationships are as follows:
A RunLoop contains several modes, and each Mode contains several Source/Timer/Observer. Each time you call the main function of RunLoop, only one of the modes can be specified. This Mode is called CurrentMode. If you need to switch the Mode, you can only exit the Loop and then specify a new Mode to enter. In this way, the Source, Timer, and Observer of different groups are separated so that they do not affect each other.
CFRunLoopSourceRef is where the event is generated. Source has two versions: Source0 and Source1.
Source0 only contains one callback (function pointer), which cannot trigger events. During use, you need to call CFRunLoopSourceSignal (source), mark the Source as pending, and manually call CFRunLoopWakeUp (runloop) to wake up the RunLoop and let it process the event.
Source1 contains a mach_port and a callback (function pointer), which is used to send messages to each other through the kernel and other threads. This type of Source can actively wake up the RunLoop thread. The principle is described below.
CFRunLoopTimerRef is a time-based trigger. It and NSTimer are toll-free bridged and can be mixed. It contains a time length and a callback (function pointer ). When it is added to RunLoop, RunLoop registers the corresponding time point. When the time point is reached, RunLoop is awakened to execute the callback.
CFRunLoopObserverRef is the Observer. Each Observer contains a callback (function pointer). When the RunLoop status changes, the Observer can accept this change through callback. The following time points can be observed:
Typedef CF_OPTIONS (CFOptionFlags, CFRunLoopActivity ){
KCFRunLoopEntry = (1UL <0), // enter Loop
KCFRunLoopBeforeTimers = (1UL <1), // Timer to be processed
KCFRunLoopBeforeSources = (1UL <2), // process Source
KCFRunLoopBeforeWaiting = (1UL <5), // enter sleep
KCFRunLoopAfterWaiting = (1UL <6), // wake up from sleep
KCFRunLoopExit = (1UL <7), // The Loop is about to exit.
};
The above Source/Timer/Observer is collectively referred to as mode item. One item can be added to multiple modes at the same time. However, when an item is added to the same mode repeatedly, it will not be effective. If no item exists in a mode, RunLoop exits directly without entering the loop.
Ii. Use Cases of Runloop
1. AutoreleasePool
After the App is started, Apple registers two observers in the main thread RunLoop, And the callback is _ wrapRunLoopWithAutoreleasePoolHandler ().
The first event monitored by the Observer is Entry (which is about to enter the Loop). In the callback, _ objc_autoreleasePoolPush () is called to create an automatic release pool. Its order is-2147483647, with the highest priority. Ensure that the creation and release Pool takes place before all other callbacks.
The second Observer monitors two events: Call _ objc_autoreleasePoolPop () and _ objc_autoreleasePoolPush () to release the old pool and create a new one when BeforeWaiting (preparing to enter sleep); Exit (about to Exit the Loop) call _ objc_autoreleasePoolPop () to release the automatic release pool. The order of this Observer is 2147483647, with the lowest priority. Ensure that the release pool occurs after all other callbacks.
Code executed in the main thread is usually written in such events as Event Callback and Timer callback. These callbacks will be surrounded by the AutoreleasePool created by RunLoop, so there will be no memory leakage, and developers do not have to display the Pool creation.
2. Timer
NSTimer is actually CFRunLoopTimerRef, and they are toll-free bridged. After an NSTimer is registered with RunLoop, RunLoop registers events for its repeated time points. For example: 10: 10, 10: 20. To save resources, RunLoop does not call back the Timer at a very accurate time point. Timer has a property called Tolerance, which indicates the maximum error allowed after the time point.
If a time point is missed, for example, if a long task is executed, the callback at that time point will also jump over without delay. For example, if I miss the bus at, I can only wait.
CADisplayLink is a timer consistent with the screen update rate (but the actual implementation principle is more complicated. Unlike NSTimer, it actually operates a Source internally ). If a long task is executed between two screen refreshes, one frame will be skipped (similar to NSTimer), resulting in a choppy interface. Even if a frame is choppy, you can quickly slide the TableView. Facebook's open-source AsyncDisplayLink is designed to solve the problem of interface freezing, and RunLoop is also used internally. Later I will write a separate blog for analysis.
3. PerformSelecter
After calling NSObject's javasmselecter: afterDelay:, an Internal Timer is actually created and added to the RunLoop of the current thread. Therefore, if the current thread does not have a RunLoop, this method will become invalid. When performSelector: onThread: is called, in fact, it will create a Timer and add it to the corresponding thread. Similarly, if the corresponding thread does not have RunLoop, this method will also become invalid.
4. Event Response
Apple registered a Source1 (based on mach port) to receive system events. Its callback function is _ IOHIDEventSystemClientQueueCallback (). When a hardware event (such as touch/screen lock/Shake) occurs, IOKit. framework generates an IOHIDEvent event and receives it from SpringBoard. SpringBoard only receives buttons (lock screen/mute), touch, acceleration, proximity sensors and other events, and then forwards them to the desired App process using the mach port. Then the Source1 registered by Apple will trigger the callback and call _ UIApplicationHandleEventQueue () for internal application distribution. _ UIApplicationHandleEventQueue () processes IOHIDEvent and packs it into a UIEvent for processing or distribution, including identifying UIGesture, processing screen rotation, and sending it to UIWindow. Generally, events such as UIButton click and touchesBegin/Move/End/Cancel are completed in this callback.
5. Gesture Recognition
When the following _ UIApplicationHandleEventQueue () identifies a gesture, it first calls Cancel to interrupt the current touchesBegin/Move/End series callback. The system then marks the corresponding UIGestureRecognizer as pending. Apple registered an Observer monitoring BeforeWaiting (Loop is about to enter sleep) event. The callback function of this Observer is _ UIGestureRecognizerUpdateObserver (), which internally obtains all the GestureRecognizer marked as to be processed, and execute the callback of GestureRecognizer. When the UIGestureRecognizer changes (creation/destruction/state change), this callback will be processed accordingly.
6. Interface update
When you change the Frame, update the UIView/CALayer hierarchy, or manually call the setNeedsLayout/setNeedsDisplay method of UIView/CALayer during UI operations, this UIView/CALayer is marked as pending and submitted to a global container. Apple registers an Observer to listen to BeforeWaiting (about to enter sleep) and Exit (about to Exit Loop) events, and calls back to execute a long function: _ ZN2CA11Transaction17observer_callbackEP19 _ CFRunLoopObservermPv (). This function traverses all the UIView/CAlayer to be processed to execute the actual painting and adjustment, and updates the UI.
The call stack inside this function is probably like this:
_ ZN2CA11Transaction17observer_callbackEP19 _ CFRunLoopObservermPv ()
QuartzCore: CA: Transaction: observer_callback:
CA: Transaction: commit ();
CA: Context: commit_transaction ();
CA: Layer: layout_and_display_if_needed ();
CA: Layer: layout_if_needed ();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA: Layer: display_if_needed ();
[CALayer display];
[UIView drawRect];
7. About GCD
In fact, GCD is also used at the underlying layer of RunLoop. However, some interfaces provided by GCD also use RunLoop, such as dispatch_async (). When dispatch_async (dispatch_get_main_queue (), block) is called, libDispatch sends a message to the RunLoop of the main thread, and the RunLoop is awakened and obtains the block from the message, run the block in the callback _ CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE. However, this logic is limited to dispatch to the main thread, and dispatch to other threads are still processed by libDispatch.
8. network requests
In iOS, network request interfaces come from the bottom up to the following layers:
CFSocket
CFNetwork-> ASIHttpRequest
NSURLConnection-> AFNetworking
NSURLSession-> AFNetworking2, Alamofire
? CFSocket is the underlying interface and is only responsible for socket communication.
? CFNetwork is an upper-layer encapsulation Based on CFSocket and Other interfaces. ASIHttpRequest works on this layer.
? NSURLConnection is a CFNetwork-based high-level encapsulation that provides object-oriented interfaces. AFNetworking works on this layer.
? NSURLSession is a newly added interface in iOS7. It is essentially tied to NSURLConnection, but some NSURLConnection functions (such as com. apple. NSURLConnectionLoader thread), AFNetworking2 and Alamofire work on this layer.
The following describes how NSURLConnection works.
Generally, when NSURLConnection is used, you will pass in a Delegate. When [connection start] is called, this Delegate will not stop receiving event callbacks. In fact, the start function will obtain the CurrentRunLoop internally, and then four Source0 (that is, the Source to be manually triggered) will be added to the defamode mode ). CFMultiplexerSource is responsible for various Delegate callbacks, and CFHTTPCookieStorage processes various cookies.
When network transmission starts, we can see that NSURLConnection creates two new threads: com. apple. NSURLConnectionLoader and com. apple. CFSocket. private. The CFSocket thread processes the underlying socket connection. The NSURLConnectionLoader thread uses RunLoop to receive underlying socket events and uses the Source0 previously added to notify the upper-layer Delegate.
The RunLoop in NSURLConnectionLoader receives notifications from the underlying CFSocket through mach port-based sources. After receiving the notification, it will send a notification to Source0, such as CFMultiplexerSource, at the right time, and wake up the RunLoop of the Delegate thread to let it process the notification. CFMultiplexerSource will execute the actual callback to the Delegate in the RunLoop of the Delegate thread.
Iii. Internal Runloop Logic
As you can see, in fact, RunLoop is such a function, which contains a do-while loop. When you call CFRunLoopRun (), the thread will stay in this loop until it times out or is stopped manually. <