Runloop is an important technical point in the advanced development of iOS, this article will focus on my understanding of Runloop as summarized in the development process.
Runloop concept
Runloop is an event-handling mechanism related to multithreading, which is used to dispatch operations and handle the coordination of upcoming events. The IOS Developer Library's explanation of Runloop is the four role of the runloop mechanism:
Runloop management is not automated, you need to write thread code to start a runloop and respond to events at the right time. However, the Cocoa and core Foundation libraries provide runloop objects to help developers configure and manage Runloop in threads. You do not need to specify how to create these Runloop objects, and each thread includes a Runloop object associated with it, including the main threads. Only a few minor threads need to explicitly run the runloop associated with them, but Runloop is a starting process for the app, which automatically runs and sets the Runloop on the main thread.
function loop() { initialize(); do { var message = get_next_message(); process_message(message); while (message != quit);}
Let's take a look at the code above. The logic of the code is that the thread can handle the event at any time but is not forced out by the system. To understand how runloop can maintain this ability to handle events at any time, we have to figure out two internal logic, runloop how to determine if a thread is dormant and quits. To figure this out, let's first look at the relationship between Runloop and threading;
The relationship between Runloop and threads
You can get the current thread by PTHREAD_MAIN_THREAD_NP (), pthread_self (), or [Nsthread CurrentThread]. Cfrunloop is managed based on Pthread. Apple does not allow the creation of Runloop directly, it only provides two automatically acquired functions: Cfrunloopgetmain () and Cfrunloopgetcurrent (). The logic inside these two functions is probably the following:
Static Cfmutabledictionaryref loopsdic;static cfspinlock_t Loopslock; Cfrunloopref _cfrunloopget (pthread_tThread) {Osspinlocklock (&Loopslock);if(!Loopsdic) {//When first entering, initialize the global DIC and first create a runloop for the main thread. Loopsdic=Cfdictionarycreatemutable (); Cfrunloopref Mainloop=_cfrunloopcreate (); Cfdictionarysetvalue (Loopsdic, PTHREAD_MAIN_THREAD_NP (), mainloop); }//obtained directly from Dictionary. CfrunlooprefLoop =Cfdictionarygetvalue (Loopsdic,Thread));if(!Loop) {Loop =_cfrunloopcreate (); Cfdictionarysetvalue (Loopsdic,Thread,Loop);//Register a callback that, when the thread is destroyed, also destroys its corresponding runloop. _CFSETTSD (...,Thread,Loop, __cffinalizerunloop); } osspinlockunlock (&Loopslock);return Loop;} Cfrunloopref Cfrunloopgetmain () {return_cfrunloopget (PTHREAD_MAIN_THREAD_NP ());} Cfrunloopref cfrunloopgetcurrent () {return_cfrunloopget (Pthread_self ());}
The line Cheng Gang is created without runloop, and if you don't get it, it will never be there. Of course, this does not mean that the thread will not execute the event, if you get the Runloop, the function body of the event can be executed through runloop, of course, you can also write your own logic to handle the event. Runloop is created when the first fetch occurs, the destruction of runloop occurs at the end of the thread. You can only get its runloop inside a thread (except for the main threads).
Runloop external interface in iOS
There are 5 classes in the Core Foundation about Runloop:
- Cfrunloopref
- Cfrunloopmoderef
- Cfrunloopsourceref
- Cfrunlooptimerref
- Cfrunloopobserverref
Let's look at the relationship between these classes and runloop through graphs:
Runloop
| Mode |
Mode |
| Source |
Source |
| Observer |
Observer |
| Timer |
Timer |
Cfrunloopsourceref is where the event occurs. There are two versions of Source: Source0 and Source1.
- Source0 only contains a callback (function pointer), which does not actively trigger events. When used, you need to call cfrunloopsourcesignal (source), mark the source as pending, and then manually invoke Cfrunloopwakeup (Runloop) to wake the Runloop and let it handle the event.
- Source1 contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads.
Cfrunlooptimerref is a time-based trigger, and Nstimer is toll-free bridged, which can be mixed. It contains a length of time and a callback (function pointer). When it joins the Runloop, Runloop registers the corresponding point in time, and when the time point is reached, the Runloop is awakened to execute that callback.
CFRUNLOOPOBSERVERREF is the Observer, each Observer contains a callback (function pointer), and when the state of RUNLOOP changes, the observer can accept the change through a callback. There are several points of time that can be observed:
typedefCf_options (Cfoptionflags, cfrunloopactivity) {kcfrunloopentry = (1UL <<0),//will enter LoopKcfrunloopbeforetimers = (1UL <<1),//The Timer is about to be processedKcfrunloopbeforesources = (1UL <<2),//Will process SourceKcfrunloopbeforewaiting = (1UL <<5),//Going into hibernationKcfrunloopafterwaiting = (1UL <<6),//Just awakened from hibernationKcfrunloopexit = (1UL <<7),//Will exit loop};
The above source/timer/observer is collectively referred to as mode item, and an item can be added to multiple mode simultaneously. However, an item is not effective when it is repeatedly added to the same mode. If a mode does not have a single item, the Runloop will exit directly without entering the loop.
Runloop Handling Event Interface Refresh
When the UI changes (frame changes, uiview/calayer inheritance structure changes, etc.), or the Uiview/calayer Setneedslayout/setneedsdisplay method is called manually, this Uiview/calayer is marked as pending.
Apple registers a observer that listens for Beforewaiting and exit, and in its callback function iterates through all the pending uiview/calayer to perform the actual drawing and adjustment, and updates the UI interface.
Incident response
When a hardware event (Touch/Lock screen/shake/accelerate, etc.) occurs, a Iohidevent event is first generated by Iokit.framework and received by Springboard, which is then forwarded by Mach Port to the required app process.
Apple registers a Source1 (Mach Port-based) to receive system events, triggers SOURECE0 through a callback function (so uievent is actually SOURCE0 based), calls _uiapplicationhandleeventqueue ( ) for distribution within the app.
_uiapplicationhandleeventqueue () iohidevent processed and packaged as uievent for processing or distribution, including identification uigesture/processing screen rotation/sending to UIWindow, etc.
Gesture recognition
If the _uiapplicationhandleeventqueue () in the previous step identifies a guesture gesture, the Cancel method is called to break the current Touchesbegin/move/end series callback. The system then marks the corresponding Uigesturerecognizer as pending.
Apple has registered a Observer monitor beforewaiting (loop is about to enter hibernation), and its callback function is _uigesturerecognizerupdateobserver (), which internally gets all the newly marked Gesturerecognizer, and executes the Gesturerecognizer callback.
When there is a change in Uigesturerecognizer (Create/Destroy/state change), the callback will be processed accordingly.
GCD tasks
When Dispatch_async (Dispatch_get_main_queue (), block) is called, Libdispatch sends a message to the runloop of the main thread, the Runloop is awakened, and the block is taken from the message. and execute the block in the callback. Runloop only handles the block,dispatch of the main thread to other threads that are still handled by Libdispatch.
Nstimer
modeitems:-cfrunloopsourceref: Data structure (SOURCE0/SOURCE1);
- SOURCE0:
- Source1:
- CFRUNLOOPTIMERREF: Data structures, creation and entry into force; related types (GCD's timer and Cadisplaylink)
- CFRUNLOOPOBSERVERREF: data structure; creation and addition; listening status;
Network requests
On the interface of the network request: The bottom layer is the Cfsocket tier, then the cfnetwork encapsulates it, then nsurlconnection object-oriented encapsulation of cfnetwork, Nsurlsession is the new interface in IOS7, The Nsurlconnection loader thread is also used. So, take nsurlconnection as an example.
When the network transfer begins, Nsurlconnection creates two new threads: Com.apple.NSURLConnectionLoader and Com.apple.CFSocket.private. Where the cfsocket thread is handling the underlying socket connection. Nsurlconnectionloader this thread internally uses Runloop to receive the event of the underlying socket, and notifies the upper Delegate through the Source0 previously added.
Runloop application slide and image refresh, UITableView optimization
When the TableView cell has a picture that needs to be fetched from the network, the TableView is scrolled, and the async thread loads the image, and the main thread sets the cell's picture after the load is completed, but it will cause the card. Can let the task of setting up the picture under Cfrunloopdefaultmode, when rolling TableView, Runloop is under Uitrackingrunloopmode, do not set the picture, but when stop, then go to set the picture.
- (void)viewDidLoad { [super viewDidLoad]; // 只在NSDefaultRunLoopMode下执行(刷新图片) [self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]]; }
Resident child threads, keeping child threads processing events
In order to ensure the long-term operation of the thread, you can add runloop to the child thread and set the item to Runloop to prevent the Runloop from exiting automatically.
+ (void) Networkrequestthreadentrypoint: (ID) __unused Object {@autoreleasepool {[[NsthreadCurrentThread] setname:@"Afnetworking"]; Nsrunloop *runloop = [Nsrunloop currentrunloop]; [Runloop Addport:[nsmachport Port] Formode:nsdefaultrunloopmode]; [Runloop Run]; }}+ (Nsthread*) Networkrequestthread {Static Nsthread*_networkrequestthread =Nil;Static dispatch_once_tOncepredicate;dispatch_once(&oncepredicate, ^{_networkrequestthread = [[NsthreadAlloc] Initwithtarget: SelfSelector@selector(Networkrequestthreadentrypoint:) object:Nil]; [_networkrequestthread start]; });return_networkrequestthread;} - (void) Start {[ Self. LockLock];if([ SelfIsCancelled]) {[ SelfPerformselector:@selector(cancelconnection) onthread:[[ SelfClass] Networkrequestthread] Withobject:NilWaituntildone:NOmodes:[ Self. RunloopmodesAllObjects]]; }Else if([ SelfIsReady]) { Self. State= Afoperationexecutingstate; [ SelfPerformselector:@selector(Operationdidstart) onthread:[[ SelfClass] Networkrequestthread] Withobject:NilWaituntildone:NOmodes:[ Self. RunloopmodesAllObjects]]; } [ Self. LockUnlock];}
Runloop Avoid app Flash-back
By getting the current runloop to avoid process crash so that the application does not flash, it is also possible to tell the user to opt out of the application to avoid some of the operational pitfalls that result from the application, while emptying the listening information so that the loop continues.
CFRunLoopRef runloop = CFRunLoopGetCurrent();NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));while(1){ for (NSString *mode in allModes){ CFRunLoopInMode((CFStringRef)mode,0.001,false); }}
Deep understanding of iOS development Runloop