Forgive me for making a clickbait this time. I’m sure many of you who have been iOS development for a while now know that there’s a RunLoop in the Cocoa Touch framework. But I don’t know how it will be embodied in actual development. I didn’t know how to use RunLoop in real life, so I took the weekend to learn about the mysterious RunLoop. Although there are many articles about RunLoop on the Internet (I will post them at the end of this article), they are all good. However, I will publish their own, record that they have learned this mysterious thing. It is recommended to look at the practical application of the penultimate RunLoop first and then go back to the theoretical knowledge points, which is more conducive to the absorption of knowledge points!

The role of the RunLoop

  1. Keep the program running
  2. Handle all sorts of things in your App, Touch events, NSTimer, Selector events
  3. Save CPU resources, improve application performance, work when you have something to do, and rest when you have nothing to do.

RunLoop object

IOS has two sets of apis to access and use RunLoop

  1. Foundation – > NSRunLoop
  2. Core Foundation -> CFRunLoopRef

Both NSRunLoop and CFRunLoopRef represent RunLoop objects. NSRunLoop is a layer of OC wrapper based on CFRunLoopRef

RunLoop with thread

  1. Each thread has a RunLoop object, which is enabled by default in the main thread and manually enabled by the child thread
  2. The RunLoop is created on the first fetch and destroyed at the end of the thread

Get the RunLoop object

Foundation
  1. [NSRunLoop currentRunLoop];Gets the RunLoop object for the current thread
  2. [NSRunLoop mainRunLoop];Get the main thread RunLoop object
Core Foundation
  1. CFRunLoopGetCurrent();Gets the RunLoop object for the current thread
  2. CFRunLoopGetMain();Gets the RunLoop object for the main thread

RunLoop related classes

Five classes related to RunLoop in Core Foundation
  1. CFRunLoopRef
  2. CFRunLoopModeRef
  3. CFRunLoopSourceRef
  4. CFRunLoopTimerRef
  5. CFRunLoopObserverRef

CFRunLoopModeRef

CFRunLoopModeRef indicates the running mode of RunLoop
  1. A RunLoop contains several modes, each of which contains several sources/timers/observers.




    Image from _ibireme Blog

  2. Each time RunLoop starts, only one of the modes can be specified, which is called CurrentMode.
  3. To switch Mode, exit RunLoop and specify another Mode. The main purpose of this is to separate the Source/Timer/Observer groups from each other
The system registers five modes by default:
  1. KCFRunLoopDefaultMode, the default Mode of the App, in which the main thread usually runs.
  2. UITrackingRunLoopMode, the interface follows Mode, which is used for ScrollView to track touch sliding, ensuring that the program sliding is not affected by other modes.
  3. UIInitializationRunLoopMode, the first Mode entered when the App is just started will not be used after the startup is completed.
  4. GSEventReceiveRunLoopModeThe internal Mode that accepts system events is usually not needed.
  5. KCFRunLoopCommonModesThis is a placeholder Mode, not a real Mode.

CFRunLoopSourceRef

  • Source is RunLoop’s abstract data Source protocol.
  • RunLoop defines two versions of the Source
    1. Source0: Handles internal App events and is managed by the App itself (triggers), such as UIEvent, CFSocket, Cocoa Perform Selector Source
    2. Source1: Managed by RunLoop and kernel, driven by Mach port such as CFMachPort and CFMessagePort

CFRunLoopTimerRef

CFRunLoopTimerRef is a time-based trigger that basically says NSTimer.

CFRunLoopObserverRef

CFRunLoopObserverRef is an observer that listens for changes in RunLoop status. There are several times to listen.

/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),// About to enter RunLoop kCFRunLoopBeforeTimers = (1UL << 1),// About to process Timer KCFRunLoopBeforeSources = (1UL << 2),// About to process Source kCFRunLoopBeforeWaiting = (1UL << 5),// about to enter sleep kCFRunLoopAfterWaiting RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU// Listen for RunLoop };Copy the code

RunLoop handles logic




The official version




Image from _ibireme Blog

RunLoop in action

1. NSTimer
[NSTimer scheduledTimerWithTimeInterval: 1.0 f target: self selector: @ the selector (testTimer) the userInfo: nil repeats: YES];Copy the code

NSTimer in this case is added by default to NSDefaultRunLoopMode. If you have a control that inherits from a ScrollView, such as a tableView, sliding it, then your NSTimer will stop working because it’s switched from NSDefaultRunLoopMode to UITrackingRunLoopMode. Now I stop sliding the tableView, and NSTimer starts working again, because I switched from UITrackingRunLoopMode to NSDefaultRunLoopMode.

To solve this problem, set the Mode of RunLoop to NSRunLoopCommonModes when NSTimer is enabled. As follows:

NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 f target: self selector: @ the selector (testTimer) the userInfo: nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];Copy the code

Nsrunloopcommonmode this Mode is an uncertain Mode, refers to the NSDefaultRunLoopMode and UITrackingRunLoopMode, similar to the set of these two modes, I’m going to execute this NSTimer.

There is another scenario where a child thread starts an NSTimer, do you think the timer will execute? I said that the timer will not execute, because the execution of the timer depends on the RunLoop, and the child thread has not enabled the RunLoop, so it will not execute. To solve this problem, simply enable RunLoop. Thread survival/resident thread 5 will explain how to enable RunLoop for a child thread.

2. ImageView display
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@" 1kr2C08-9 "] afterDelay:2.0f];Copy the code

This is similar to NSTimer above. Mode defaults to NSDefaultRunLoopMode. When sliding a ScrollView control, the image object will not be displayed on the imageView control. To allow images to be displayed on the imageView control while sliding the ScrollView control, use another performSelector with Modes as follows:

[self_imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed: @1kr2C08-9 "] afterDelay:2.0 inModes:@[NSRunLoopCommonModes]];Copy the code
3. The larger view is displayed

There is a tableView, and each cell displays three images. A screen can display about 18 images, each image is 2034 × 1525 pixels in size. Drawing 18 images onto the cell in a single RunLoop will be extremely difficult. If using CFRunLoopObserverRef to listen for a RunLoop in the kCFRunLoopEntry state means entering a new RunLoop, then a draw at this point solves the lag problem. But I see the source code is drawn in kCFRunLoopBeforeWaiting state. Demo_RunLoopWorkDistribution, video address password :ennf

4. Monitor iOS lag

The core still uses CFRunLoopObserverRef to listen for the time difference between two runloops. If the time difference between two runloops exceeds a certain value you set when a new RunLoop is opened, it means there is a lag. Then print out all of the methods of this RunLoop execution, and you can pretty much locate the stutter that occurred when a function was executed. Demo_PerformanceMonitor

5. Thread keepalive or resident thread

We all know that after a child thread completes its task, that thread dealloc. You can create a class that inherits from NSThread and implements the dealloc function to see if it will dealloc when the thread completes its task, because RunLoop is not enabled by default on the child thread, so it will dealloc when it completes its task. Sometimes we need to let this thread continue to perform other tasks and how do we do that? Now that we know why a child thread dealloc after executing a task, let’s start at the root, turn on its RunLoop, and put the following two lines of code inside the child thread function.

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];Copy the code

It then uses performSelector to perform its task on the thread on which the RunLoop is enabled. If you just say [[NSRunLoop currentRunLoop] run]; RunLoop is not enabled if Mode is not added to the RunLoop, or if Mode is added and has no value (source/NSTimer/observer).

[self performSelector:@selector(testAgainThread) onThread:_thread withObject:nil waitUntilDone:YES];Copy the code

If the child thread does not have RunLoop enabled and waitUntilDone is YES, then there is an infinite loop in perforSelector because the thread has been destroyed, WaitUntilDone is YES (that is, the thread needs to be finished before it can be executed), but the thread cannot be executed now, so it will die there.

6. AutoreleasePool

In the Ibireme article, the code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the Pool was created. What about in the child thread? Because RunLoop has a lot of stuff in it, it would be a waste of memory not to release it while it’s sleeping. It is best to put a layer of AutoreleasePool on the outer layer when the child thread starts RunLoop.

@autoreleasepool {
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
};Copy the code

RunLoop creates an auto release pool before it enters, empties the auto release pool during sleep, and creates another auto release pool before waking up. The auto-release pool is destroyed when RunLoop exits.

Conclusion:

1. Child threads need to manually enable RunLoop
2. RunLoop is inseparable from Mode, and Mode is inseparable from Source/Timer/Observer.
You can use the Observer to listen to the states of the RunLoop to do something unusual.

Related articles & videos

What is RunLoop? Share RunLoop offline on iOS by Sunyuan @sunnyxx