preface

Learning g is like rowing upstream, not to advance is to drop back! Mutual encouragement!!

Today is mainly to a simple analysis of the iOSRunloop running loop, mainly from the following four aspects:

  1. What is runloop?
  2. Runloop interview questions?
  3. What does the internal structure of the running loop look like?
  4. How does the runtime loop work?
  5. What is the difference between the main thread and the child thread running a loop?
  6. API for running loops
  7. IOS interview learning materials: pick up the address

What is runloop?

Runloop: Runloop is an implementation of the event receiving and distribution mechanism and is part of the thread-specific infrastructure

  • A runloop is an event processing loop that is used to continuously schedule work and handle output events. Runloop is used only when interacting with the thread.

  • A runloop has five models. A runloop, also called a runloop, maintains an internal event loop to manage various events, including timers, user interactions, and internal events. When there is a message to be processed, run the loop to wake up, and then enter the sleep state, waiting for the next wake up!

  • The internal event loop is a do-while loop that blocks the thread by inserting the port receiving the message inside, causing the thread to enter an idle state that does not consume CPU resources. It is different from the pure do-while loop we write manually, which is busy waiting and consumes CPU resources.

  • The messages that wake up the running loop are

    • Receiving system internal events via port (Source1)
    • User interaction event response, which is also essentially port wake up (Source0)
    • timer
  • Runloop status Six states: 1. Start 2. Exit 3. About to process the timer event

  • Runloop has Source event Source, Timer, Observer listener, model

Source0 Event initiated by a user Source1 Event initiated by a non-user

Note: A Runloop will render up to 18 images at a time, a Runloop will render only one image at a time, and 18 times to render 18 images. Runloop creates an automatic release pool at run time and releases it at sleep time

Runloop model

UI mode has the highest priority. When UI mode is awakened, Runloop only handles UI presentation in UI mode (tableView sliding, etc.).

UI mode UITrackingRunLoopModel can only be touched to wake up after which other events are paused

NSDefaultRunLoopModel Default Mode Apple recommends playing clock network events usually the main thread runs in this Mode.

NSRunLoopCommonModel is not a true Runloop placeholder

GSEventReceiveRunLoopModel receiving system internal model of events, usually in less than

UIinitializationRunLoopModel when just start the App the first to enter the first Mode, start after the completion of the will no longer be used.

What is the relationship between threads and runloops

Each thread has a unique runloop object corresponding to it; The main thread runloop has been created automatically, and the child thread runloop needs to be created automatically. The runloop is created on the first fetch and destroyed at the end of the thread

3. The runloop model

Specifies the priority of events in the run loop. Threads need different modes to run, to respond to different events, and to handle different situational patterns. (for example, setting UITrackingRunLoopModel when you can optimize tableView does not perform some operations, such as setting images, etc.)

2. Interview questions

1. Why does the NSTimer stop working when sliding the ScrollView

When you swipe ScrollView, the main thread RunLoop will switch to UITrackingRunLoopModel, which also performs tasks under UITrackingRunLoopModel. However, timer is added under NSDefaultRunLoopMode, so the timer task will not be executed. Only when the task of UITrackingRunLoopModel is completed and runloop switches to NSDefaultRunLoopModel, To ensure that the timer is executed, it needs to add it to the UITrackingRunLoopModel

Note that when the timer is not needed, you must remember that the invalidate method is invalid; otherwise, the timer will not be released

Third, what is the internal structure of the operation cycle?

1. RunLoop structure definition:

struct __CFRunLoop { __CFPort _wakeUpPort; // used for CFRunLoopWakeUp _CFThreadRef _pthread; CFMutableSetRef _commonModes; // commonModes is a dummy mode with default & traking CFMutableSetRef _commonModeItems; // commonModes include registration events (timer/source/observer) CFRunLoopModeRef _currentMode; // Only one mode CFMutableSetRef _modes can be used at a time; // Run all modes under loop //... Omit other members};Copy the code

2. Definition of RunLoopMode structure

struct __CFRunLoopMode { // ... Omit other members CFMutableSetRef _sources0; // source0, CFMutableSetRef _sources1; // source1, CFMutableArrayRef _observers; CFMutableArrayRef _timers; cfmutablearrayRef_timers; // The timer array added to the running loop __CFPortSet _portSet; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; // set to true by the source when a timer has fired Boolean _dispatchTimerArmed; #endif #if USE_MK_TIMER_TOO __CFPort _timerPort; Boolean _mkTimerArmed; #endif };Copy the code

1. Five types of RunLoopMode:

  • KCFRunLoopDefaultMode: The default mode in which the main thread runs
  • UITrackingRunLoopMode: traces user interaction events
    • Switching to this mode ensures that the interface is not affected by other modes when sliding, which is why iOS ScrollView slides so smoothly!
    • Therefore, all operations that affect the main thread UI slide are performed in default mode. For example, add an image loaded from the network to UITbaleview’s ImageView; Update the data returned from the network request to the tableView data source.
    • To prevent the tableView from loading images, you can also use default mode to load images.
    • After the network request data is loaded back, the UI update action is also placed in default mode, so that the user’s sliding experience is not affected.
  • UIInitializationRunLoopMode: when just start the App the first to enter the first Mode, after the completion of the start is not used again
  • GSEventReceiveRunLoopMode: accept system internal event, is usually not used
  • Kcfrunloopcommonmode: pseudo-mode, not a true operating mode
    • KCFRunLoopDefaultMode and UITrackingRunLoopMode are the default Source/Timer/Observer modes.
    • If you add an event source to common Mode, you add the event source to both Default mode and Tracking Mode. Running loops in both modes execute the event source.
    • Also throughCFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);Add a new modeEnter Common Modes

2. Event source of RunLoopMode

1 CFRunLoopSource: Input or event sources can be source0 or source1

  1. Source1: Mach port-driven events, such as CFMachport, CFMessagePort. It listens for messages sent by system ports, the kernel, and other threads, and proactively wakes up the RunLoop to receive distribution system events. Ability to wake up threads.
  2. Source0: user-triggered events, such as UIEvent and CFSocket. Instead of waking up the thread directly, you need to use source1 to wake it up and switch the current thread from kernel mode to user mode

2 CFRunLoopObserver: listener

/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), KCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopAfterWaiting = (1UL << 6) KCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code

How do I listen for events running in a loop?

- (void)viewDidLoad { [super viewDidLoad]; [self.class addRunLoopObserver:(__bridge CFRunLoopRef)([NSRunLoop currentRunLoop])]; } // RunLoop Observer #pragma mark add Observer + (void)addRunLoopObserver:(CFRunLoopRef)runLoop { static CFRunLoopObserverRef observer; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CFOptionFlags activities = (kCFRunLoopAllActivities); // before exiting a runloop run // allocator activities repeats order after CA transaction commits observer = CFRunLoopObserverCreateWithHandler(NULL, activities, YES, 720, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry: NSLog(@" enter RunLoop"); break; Case kCFRunLoopBeforeWaiting: NSLog(@" about to go to sleep "); break; Case kCFRunLoopAfterWaiting: NSLog(@" just woke up from sleep "); break; Case kCFRunLoopExit: NSLog(@" exit RunLoop"); break; Case kCFRunLoopBeforeTimers: NSLog(@" About to process Timer"); break; Case kCFRunLoopBeforeSources: NSLog(@" about to process Source"); break; default: break; }}); CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes); CFRelease(observer); }); }Copy the code
  1. CFRunLoopTimer timer

NSTimer is a wrapper around RunLoopTimer for both timed execution and deferred execution.

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; 
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; 

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; 

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; 

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
Copy the code

The relationship between the run loop and thread, mode, and register events can be seen from the definition of the above structure: \

  • Running loops has a one-to-one relationship with threads
  • There are multiple run modes inside the run loop
  • There are multiple registration events within the running mode, including source0,source1, and possibly multiple timer/ Observer events.

Four. The implementation principle of the bottom layer of the running cycle

  • The bottom layer of the run loop relies on the [Mach Port].
  • Throughout the loop, a do-while loop is started
  • Internal circulation passage__CFRunLoopServiceMachPortThe function is createdPort with receive permissionTo hold a thread in a wake-up state, which is blocked for the thread and dormant for the running loop. soHibernation of a running loop is essentially a blocked thread.
  • Upon receiving a message from the port, the unblocking state, that is, the running loop, is awakened. Then determine the wake port type:
    • This is the timer port. The timer callback function __CFRunLoopDoTimers is executed
    • Is the main thread port that executes the main thread callback function CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    • If not, the port is considered source1 and the source1 callback __CFRunLoopDoSource1 is executed

3.1 Flow chart of running cycle execution:

Or something like this:

3.2 Pseudo-code for running the circular execution process:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; 
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; 

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; 

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; 

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
Copy the code

Truncated source code screenshot: red for performing specific actions, blue for callback listening events

The difference between the main thread and the child thread running a loop

API for running loops

In iOS, there are two apis to handle running loops: \

  • NSRunLoop(in Foundation framework)
  • CFRunLoop(in the CoreFoundation framework)
  • NSRunLoop is a wrapper around the object-oriented API of CFRunLoop, so the essence is the same.

The mode must have any event source, timer, source, or observer. // There is also a loop wrapped around the running loop. CFRunLoopStop cannot be used to stop [nsRunloop. currentRunLoop run]; [NSRunLoop.currentRunLoop runUntilDate:[NSDate distantFuture]]; // CFRunLoopStop can be used to stop the loop. Because you can manually control stop [NSRunLoop currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]].

Run loop applications: 1. Thread keepalive \

  • The main thread of the thread to keep alive, access to the main thread of the modes, open an infinite loop, which traverse reopened CFRunLoopRunInMode running cycle. It can be used to collect crash logs and prevent flashbacks caused by crashes
  • The thread of the child thread is kept alive, and a port for receiving messages is inserted on the child thread, opening an infinite loop that iterates through and restarts the running loop CFRunLoopRunInMode. Can be used to:
    • Monitor Latency: a child thread continuously sends messages to the main thread and records the response time. If it does not respond after the specified time, it is considered to be a latency.
  • Cost saving: When sub-threads are required to perform tasks frequently, creating threads for multiple times incurs a large amount of memory cost. Enabling sub-thread keepalive can save some cost of starting up threads.
- **AFN prevents NSOperation objects from receiving callbacks due to thread withdrawal before downloading is complete ** : **AFN creates a singleton subthread to initiate connections and receive callbacks without using mainthread resources.Copy the code

AFN resident thread source code:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];

          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
          _networkRequestThread =
          [[NSThread alloc] initWithTarget:self
               selector:@selector(networkRequestThreadEntryPoint:)
               object:nil];
          [_networkRequestThread start];
     });

     return _networkRequestThread;
}
Copy the code

Note: CFRunLoopRunInMode The mode of this method cannot be kCFRunLoopCommonModes, because this is the logic in the source code. Return kCFRunLoopRunFinished directly. CFRunLoopRunSpecific source code:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) { ... return kCFRunLoopRunFinished; }... }Copy the code

2. Optimize the TableView rolling experience by using the feature of CFRunLoopMode, the action of updating UI can be put into the mode of NSDefaultRunLoopMode, so that UITrackingRunLoopMode can be used when rolling. NSDefaultRunLoopMode The mode action will not be executed. The smooth scrolling effect will not be affected by updating the UI.

  1. When the image is loaded and the UI is updated to ImageView, use NSDefaultRunLoopMode without affecting sliding

UIImage *downloadedImage = … ; [self.avatarImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]]; \

  1. When the UI is updated to tableView after data loading, NSDefaultRunLoopMode is used, which does not affect sliding

[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];

Give a rose, leave a lingering fragrance. If you get something, you might as well give it a thumbs-up.

IOS learning materials: get the address

Recommended reading: Interview guide