Introduction to the

  • As the name implies
    • Operating cycle
    • Do something in a loop while the program is running

  • Application category
    • Timer, PerformSelector
    • GCD Async Main Queue
    • Event response, gesture recognition, interface refresh
    • Network request
    • AutoreleasePool

Join without RunLoop

  • After line 13, the program is about to exit

The RunLoop

  • UIApplicationMain creates a runLoop object
    • If we delete this code and click to open the APP it will immediately flash back.

  • Pseudocode guesses how this RunLoop works
    • RetVal is always 0 so the program does not exit
    • Let the thread sleep and wait
    • When an event occurs, the program wakes up and handles the event
    • Return 0 and continue the loop without exiting

  • The program does not exit immediately, but remains running
  • The basic functions of RunLoop
    • Keep the program running
    • Handle various events in the App (such as touch events, timer events, etc.)
    • Save CPU resources, improve program performance: work when you should work, rest when you should rest
    • .

RunLoop object

  • IOS has two sets of apis to access and use RunLoop
    • Foundation: NSRunLoop
    • The Core Foundation: CFRunLoopRef
  • Both NSRunLoop and CFRunLoopRef represent RunLoop objects
    • NSRunLoop is a layer of OC wrapper based on CFRunLoopRef
    • CFRunLoopRef is open source
    • Opensource.apple.com/tarballs/CF… download

RunLoop with thread

  • Each thread has a unique RunLoop object corresponding to it
  • Runloops are stored in a global Dictionary, with threads as keys and runloops as values

  • The thread is created without a RunLoop object; the RunLoop is created the first time it gets it
  • The RunLoop is destroyed at the end of the thread
  • The RunLoop for the main thread is automatically acquired (created), and RunLoop is not enabled for the child thread by default
    • UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); This can be interpreted as no runloop when the main thread was created, but it was called at the same time[NSRunLoop currentRunLoop]Actively get (create) a runloop for the main thread.

Get the RunLoop object

  • Foundation

    • [NSRunLoop currentRunLoop]; // Get the RunLoop object for the current thread
    • [NSRunLoop mainRunLoop]; // Get the main thread RunLoop object
  • Core Foundation

    • CFRunLoopGetCurrent(); // Get the RunLoop object for the current thread
    • CFRunLoopGetMain(); // Get the main thread RunLoop object
  • The source code to explore

    • 1. Runloop returns the key from the dictionary based on the thread
    • 2. Add a newloop to the dictionary if the loop is empty

Runloop-related classes

  • Five classes for RunLoop in Core Foundation
    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef
  • Simplify what works
    • So one runloop per thread
    • CFMutableSetRef: Collections are arrays but arrays are ordered and collections are unordered
    • _modes: Data loaded in the modes are CFRunLoopModeRef
    • _currentMode: indicates the current mode
    • Runloop has a number of modes in the modes, but only one mode is considered currentMode.

    • _NAME: indicates the name of the schema
    • 4 set
    • _sources0 and _source1 are collection objects of CFRunLoopSourceRef
    • _observers is a collection object for CFRunLoopObserverRef
    • _timers is the collection object for CFRunLoopTimerRef

CFRunLoopModeRef

  • CFRunLoopModeRef indicates the running mode of RunLoop

  • A RunLoop contains several Mode, each Mode and contains several Source0 / Source1 / Timer/Observer

  • Only one of these modes can be selected as currentMode when RunLoop starts

  • If you need to switch Mode, you can only exit the current Loop, and select another Mode to enter the program will not exit.

    • Different groups of Source0 / Source1 / Timer/Observer can be separated, each other
  • If there is no any Source0 / Source1 / Timer Mode/Observer, RunLoop immediately exit

  • Two common modes

    • KCFRunLoopDefaultMode (NSDefaultRunLoopMode) : The default Mode of the App, in which the main thread usually runs
      • The default mode of runloop
    • UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes
      • Runloop sliding mode

CFRunLoopObserverRef

Add an Observer to listen for all RunLoop states

The running logic of RunLoop

  • Source0
    • Touch event handling
    • performSelector:onThread:
  • Source1
    • Port-based communication between threads
    • System Event Capture
  • Timers
    • NSTimer
    • performSelector:withObject:afterDelay:
  • Observers
    • Used to listen for the status of RunLoop
    • UI refresh (BeforeWaiting)
    • Autorelease Pool (BeforeWaiting)
  • 01. Notify Observers: enter Loop
  • Observers: They are about to deal with Timers
  • Observers: Intentions to deal with Sources
  • 04, Process Blocks
  • 05, processing Source0 (possibly processing Blocks again)
  • 06, If Source1 exists, go to Step 8
  • 30, Notifying Observers that they are about to start hibernating.
  • 30, notifying Observers that they are astounded by a message.
    • 01 > deal with the Timer
    • 02> Handle GCD Async To Main Queue
    • 3 > deal with Source1
  • 09. Process Blocks
  • 10. According to the previous results, decide what to do
    • 01> Return to Step 02
    • 02 > exit the Loop
  • Observers: exit Loop

My understanding by analogy this is my understanding can not be used as a reference.

  • In layman’s terms, a runloop is a machine that has various modes.
  • Each mode has four arrays, like four knobs. The 4 knobs do not affect each other and can be customized by adjusting the knobs.
  • When you want to start the machine, you should first select a mode and run the machine as the current mode. To switch the mode, you should stop the current mode first.
  • If you select a mode where all four knobs are 0, the machine shuts down automatically and goes into a sleep state waiting for the event to activate.
  • The fourth knob can not only control the machine to perform certain actions, but also monitor the state of the machine
  • What a loop in a runloop does
    • To enter a mode, remove source0, source1, timers, and Observer from the mode

Source code analysis

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {/* DOES CALLOUT */ // notify observers: Loop __CFRunLoopDoObservers(rL, currentMode, kCFRunLoopEntry); / / to do specific result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // Notify obsercers: Exit Loop __CFRunLoopDoObservers(RL, currentMode, kCFRunLoopExit); return result; } /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); int32_t retVal = 0; Do {// Notify obsercers that timers __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers) are about to be processed; // Notify obsercers: sources __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeSources) will be processed; // Notify obsercers that block __CFRunLoopDoBlocks(rL, RLM) is about to be processed; // Handle sources0 if (__CFRunLoopDoSources0(rl, RLM, stopAfterHandle)) {// Handle block __CFRunLoopDoBlocks(rl, RLM); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); If (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {// if there is source1, goto handle_msg goto handle_msg; }} // Notify Observers: about to hibernate __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeWaiting); // Start sleeping __CFRunLoopSetSleeping(rl); CFAbsoluteTime sleepStart = poll ? 0.0: CFAbsoluteTimeGetCurrent (); __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(MSg_buffer), &livePort, poll? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); / / wake __CFRunLoopUnsetSleeping (rl); // Notify the observer: hibernate __CFRunLoopDoObservers(rl, RLM, kCFRunLoopAfterWaiting); handle_msg:; // Handle timers __CFRunLoopDoTimers(rl, RLM, Mach_absolute_time ()))} else if (be awakened by GCD) {// Handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); } else {//source1 __CFRunLoopDoSource1(rl, RLM, RLS, MSG, MSG ->msgh_size, &reply) || sourceHandledThisLoop; } // Handle block __CFRunLoopDoBlocks(rl, RLM); / / set the return value if (sourceHandledThisLoop && stopAfterHandle) {retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; } voucher_mach_msg_revert(voucherState); os_release(voucherCopy); } while (0 == retVal); return retVal; }Copy the code

RunLoop executes the flow

Implementation principle of RunLoop hibernation

  • Wait for another message to wake up the current thread to sleep: blocked threads no longer go down
  • Unlike while (1), which blocks, such a thread does not really rest

__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

  • This is the kernel-level API that allows threads to hibernate and send messages to the kernel-level CPU to rest and save power.
  • All we can control is the API at the page level

RunLoop applications in the real world

  • Control the thread lifecycle (thread survival)
  • Fixed NSTimer stopping working when sliding
  • Monitoring application lag
  • Performance optimization

Application 1 solves the problem that NSTimer stops working when sliding

  • 1. Create a timer

  • Put a textView

  • And it turns out that the timer stops when we drag the textView
  • The principle of analysis
    • Runloop can only run in one mode at a time.
    • The timer is not in drag mode and works in default mode.
    • If the timer is in drag mode, it can run.
  • Put the timer in drag mode as well as NSRunLoopCommonModes in default mode
  • ScheduledTimerWithTimeInterval this method will default to the timer in the default mode
- (void)viewDidLoad { [super viewDidLoad]; static int count = 0; NSTimer * timer = [NSTimer timerWithTimeInterval: 1.0 repeats: YES block: ^ (NSTimer * _Nonnull timer) {NSLog (@ "% d", ++count); }]; // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // NSDefaultRunLoopMode, UITrackingRunLoopMode are real modes. It's just a tag / / timer can work under _commonModes stored in the array patterns [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSRunLoopCommonModes]; / / [NSTimer scheduledTimerWithTimeInterval: 1.0 repeats: YES block: ^ (NSTimer * _Nonnull timer) {/ / NSLog (@ "% d", ++count); // }]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @endCopy the code