iOS development - RunLoop understanding

Posted by steve448 on Mon, 15 Jul 2019 00:47:51 +0200

RunLoop concept

A run loop is a cycle of event handling, which is used to schedule work and process events continuously.

Effect

  • Keep the program running continuously
  • Monitor and process various events in App (touch events, timer events, selector events)
  • Save CPU resources and improve program performance: do things while doing, rest when doing things
  • A RunLoop loop is responsible for drawing all points on the screen.

Entry function

int main(int argc, char * argv[]) {
   @autoreleasepool {
       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}

UIApplicationMain () This function starts a RunLoop internally, so this function has not been returned and keeps the program running continuously. The default RunLoop started is related to the main thread.

RunLoop Object

  • Under the Foundation Framework: NSRun Loop (based on FRunLoopRef encapsulation, provides object-oriented APIs, but these APIs are not thread-safe)
  • Under the Core Foundation framework: CFRun LoopRef (API s for pure C functions, all of which are thread-safe)

RunLoop and Threads

  • Each thread has a unique RunLoop object corresponding to it
  • Runlop for the main thread has been created automatically, and Runlop for the sub-thread needs to be created manually.
  • RunLoop is created on the first acquisition and destroyed at the end of the thread
     NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//The main thread corresponds to RunLoop
     [NSRunLoop mainRunLoop];//The current thread corresponds to RunLoop
     currentRunloop.getCFRunLoop;//translate into CFRunLoop
     
     CFRunLoopGetMain();
     CFRunLoopGetCurrent();
     
     //Open a subthread
     [[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start];
     
     -(void)run {
     
        //Create subthreads corresponding to RunLoop,currentRunLoop Lazy Loading
        [NSRunLoop currentRunLoop];
     
     }

Following is Apple's official source code. By analyzing the source code, we can see that pthread is used as the key in the global dictionary, and the corresponding RunLoop is created as Value. RunLoop is created when we get it, and RunLoop of the main thread is automatically created at the beginning. Threads and RunLoop are one-to-one correspondences

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}



//Global Dictionary,key yes pthread_t, value yes CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//Visit Dictionary Time lock
static CFLock_t loopsLock = CFLockInit;
//Get a pthread Corresponding RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //If the incoming thread equals 0
    if (pthread_equal(t, kNilPthreadT)) {
        //The current thread is equal to the main thread
        t = pthread_main_thread_np();
    }
    //Lock operation
    __CFLock(&loopsLock);
    //If at present RunLoop Create for empty.
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        // Create a dictionary
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // Create the main thread
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // Save the main thread
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // Getting the current thread's RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        // If the current thread's runloop Non-existent,Then create a corresponding for the thread runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        // Put the current subthread and its corresponding runloop Save it in a dictionary
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

RunLoop related classes

Five classes of RunLoop in Core Foundation

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef (Time-based Trigger)
  • CFRunLoopObserverRef

Description: A RunLoop contains several modes, and each mode contains several Sources, Timers, Observers. Each time RunLoop starts, only one mode can be specified. This mode is called Current Mode. If you need to switch mode, you can only exit Loop and re-specify a mode to enter. This is mainly to separate no mode. Source/Timer/Observer of the same group, so that they do not affect each other.

CFRunLoopSourceRef: Event Source (Input Source)

- Source0: Non-Port-based (user-initiated events)

- Source1: Port-based (message events within the system)

Port is a way of communication between threads. If two threads want to communicate, they can communicate through Port. )

 

2.CFRunLoopTimerRef

Based on the time flip-flop, when it joins RunLoop, RunLoop registers the corresponding time point, when the time point arrives, RunLoop will be waked up to perform the callback inside.

 

3.CFRunLoopObserverRef 

Observers can monitor RunLoop's state changes.

/* Run Loop Observer Activities */

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

kCFRunLoopEntry = (1UL << 0),//Imminent Entry Loop

kCFRunLoopBeforeTimers = (1UL << 1),//To be handled Timer

kCFRunLoopBeforeSources = (1UL << 2),//To be handled Source

kCFRunLoopBeforeWaiting = (1UL << 5),//Coming to sleep

kCFRunLoopAfterWaiting = (1UL << 6),//Just awakened from dormancy

kCFRunLoopExit = (1UL << 7),//Imminent withdrawal Loop

kCFRunLoopAllActivities = 0x0FFFFFFFU

};

 

4. CFRun Loop ModeRef: Run Loop Running Mode

There are multiple modes of operation in RunLoop, but RunLoop can only run in one Mode, with at least Timer or Source in the Mode.

The system registered five modes by default:

  • kCFRunLoopDefaultMode: App's default Mode, where the main thread normally runs
  • UITracking Run Loop Mode: Interface Tracking Mode for ScrollView Tracking Touch Slide to ensure that the interface is not affected by other modes when sliding
  • UI Initialization Run Loop Mode: The first mode you enter when you first enter App, which is not used after booting.
  • GCEventReceive Run Loop Mode: An internal mode that accepts system events, usually not needed
  • NSRun Loop Common Modes: This is a placeholder Mode, not a real Mode

RunLoop-related issues and explanations

1.Timer and Sliding Control

Problem: The timer doesn't work while the sliding control is being dragged continuously.

Wrong Answer: runloop Priority

Analysis: Several common modes of runloop: DefaultMode default mode, UITracking Mode mode, CommonModes occupancy mode, and runloop entry into one mode, the events of another mode will not be processed, Timer is in DefaultMode mode when running, and runloop stands when dragging sliding controls. That is, after processing UI events and switching to UITracking Run Loop Mode mode, the Timer in DefaultMode mode will not work at this time.

Solution: Place Timer in NSRun Loop Common Modes placeholder mode

- (void)viewDidLoad {

    [super viewDidLoad];

    //1.create-timer

    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    
    //2.take Timer add to RunLoop in

    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];

    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

}

- (void)run {

    NSLog(@"-------- %@",[NSThread currentThread]);

}

 

    

Question: In multi-threaded development, time-consuming operations are usually executed in sub-threads. What are the characteristics of such threads?

Example: If a time-consuming operation is executed in the run method of the timer above, the main thread will be stuck and the sliding control will not be smooth. How should we solve this problem?

Analysis: By default, the RunLoop loop will not be opened in the sub-thread, so the sub-thread will be recycled after the task has been executed.

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        //1.create-timer

        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

        //2.take Timer add to RunLoop in

        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];        

        //3.Give Way RunLoop Running

        [[NSRunLoop currentRunLoop] run];//Dead loop, later code will not execute

        NSLog(@"+++++++++");

    });

}

- (void)run {

    //time-consuming operation

    [NSThread sleepForTimeInterval:1.0];

    NSLog(@"-------- %@",[NSThread currentThread]);

}

Or:

- (void)viewDidLoad {

    [super viewDidLoad];

   [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil];

}

-(void)time2{

    //Creating the current thread's RunLoop

    NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];

    //This method automatically adds internal RunLoop In, and the run mode is the default mode

    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //open RunLoop

    [currentLoop run];

}

- (void)run {

    //time-consuming operation

    [NSThread sleepForTimeInterval:1.0];

    NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);

}

 

 

This blog will continue to improve and update the knowledge related to runloop. If you have incorrect understanding or application examples related to runloop, please leave a message and welcome discussion.

Reference article: http://blog.ibireme.com/2015/05/18/runloop/

Topics: iOS REST