RunLoop series of articles

RunLoop (2) : Data structure RunLoop (3) : event loop mechanism RunLoop (4) : RunLoop and thread RunLoop (5) : RunLoop and NSTimer iOS – AutoRelease and @Autoreleasepool: RunLoop and @Autoreleasepool

RunLoop and NSTimer

  • As we know from previous articles,NSTimerIs made up ofRunLoopTo manage,NSTimerIn fact, isCFRunLoopTimerRefBetween them is toll-free bridged, can be exchanged;
  • If we use it on child threadsNSTimer, the child thread must be openedRunLoopOtherwise, the timer cannot take effect.

Solve tableView slide NSTimer failure problem

  • Question: As we know from previous articles,RunLoopCan only run in one mode at a time when we swipetableview/scrollviewwhenRunLoopWill switch to theUITrackingRunLoopModeIn trace mode. If ourNSTimerIs added to theRunLooptheKCFRunLoopDefaultMode/NSDefaultRunLoopModeIn default mode, it will be disabled at this point.
  • Solution: We canNSTimerAdded to theRunLooptheKCFRunLoopCommonModes/NSRunLoopCommonModesIn general mode, to ensure that both the default mode and the interface tracking modeNSTimerCan be executed.
  • NSTimerCreate method of

NSTimer is automatically added to the default mode of RunLoop if we create it using the following method

    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@ "123");
    }];
Copy the code

We can create NSTimer to be added to a mode of RunLoop by custom in the following way

    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@ "123");
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

Note: NSTimer created using the timerxxx method will not be automatically added to the RunLoop, so be sure to add it manually or NSTimer will not work.

CFRunLoopAddTimer function is implemented

The CFRunLoopAddTimer() function checks whether the name of the modeName mode passed in is kcFRunloopCommonMode. If so, it adds the timer to the _commonModeItems collection of the RunLoop. And synchronize the timer to all modes in _commonModes so that NSTimer works in both default and interface tracking modes.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if(! __CFIsValid(rlt) || (NULL! = rlt->_runLoop && rlt->_runLoop ! = rl))return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {       // Check whether modeName is kCFRunLoopCommonModes
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {    // check whether _commonModeItems is empty if it is
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    	}
    	CFSetAddValue(rl->_commonModeItems, rlt);  // Add timer to _commonModeItems
    	if (NULL! = set) {CFTypeRef context[2] = {rl, rlt};  // Wrap timer and RunLoop into context
    	    /* add new item to all common-modes */
            // Iterate over commonModes and add timer to all Modes of commonModes
    	    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    	    CFRelease(set); }... }}static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef) (((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef) (((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
	CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
	CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
	CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code

Problems with NSTimer and CADisplayLink

  • Unpunctuality:NSTimeandCADisplayLinkThe bottom is all based onRunLooptheCFRunLoopTimerRefThat is, they all depend onRunLoop. ifRunLoop“Is too onerous, so they are not on time.

For example, NSTimer will execute a task every 1.0 seconds, and Runloop will check to see if NSTimer’s time reaches 1.0 seconds every time it loops, and execute the task if it does. However, since the tasks of each Runloop are different, the time taken is not fixed. If the first loop takes 0.2s, the second 0.3s, and the third 0.3s, then the NSTimer task will be executed after 0.2s. At this point, the Runloop task may be too heavy, and the fourth loop may take 0.5s, which adds up to 1.3s, causing the NSTimer to be inaccurate.

Solution: Use GCD timer. The GCD timer is tied directly to the kernel and does not rely on RunLoop, so it is very punctual. The following is an example:

    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    
    // Create a timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
    // Set the time (start: execute after a few seconds; Interval:
    uint64_t start = 2.0;    // execute after 2s
    uint64_t interval = 1.0; // Execute every 1s
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC.0);
    // Set the callback
    dispatch_source_set_event_handler(timer, ^{
       NSLog(@ "% @"[NSThread currentThread]);
    });
    // Start timer
    dispatch_resume(timer);
    NSLog(@ "% @"[NSThread currentThread]);
    
    self.timer = timer;
/* 2020-02-01 21:34:23.036474+0800 threads [7309:1327653] < thread: 0x600001a5cfc0>{number = 1, name = main} 2020-02-01 21:34:25.036832+0800 Threads [7309:1327705] 0x600001ACB600 >{Number = 7, name = (null)} 2020-02-01 21:34:26.036977+0800 Multithread [7309:1327705] 0x600001ACB600 >{Number = 7, name = (null)} 2020-02-01 21:34:27.036609+0800 Threads [7309:1327707] 0x600001a1e5c0>{number = 4, name = (null)} */
Copy the code
  • A circular reference