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 in relation to threads

RunLoop is described in the Thread Programming guide in apple’s official documentation, which shows that RunLoop has a unique relationship with threads. Threading Programming Guide

  • RunLoopThere is a one-to-one correspondence between objects and threads;
  • RunLoopSave in a globalDictionaryIn, threads act askey.RunLoopAs avalue;
  • If there is noRunLoop, the thread will exit after completing the task; If there is noRunLoop, the main thread completesmain()The function exits and the program cannot run;
  • RunLoopCreation time: Not when the thread was first createdRunLoopObject,RunLoopIs created the first time it is fetched;
  • RunLoopDestruction time:RunLoopWill be destroyed at the end of the thread;
  • The main thread of theRunLoopThe child thread is not enabled by defaultRunLoop;
  • The main thread of theRunLoopObject is inUIApplicationMainThrough the[NSRunLoop currentRunLoop]Gets and is created as soon as it is found not to existRunLoopObject.

The child thread of RunLoop is not started

Create a subclass of NSThread, HTThread, and rewrite the dealloc method to observe thread state. Execute the following code to find that the child thread exits from the test task after executing it once, and does not execute the test task again because it did not start the RunLoop of the thread.

- (void)viewDidLoad {
    [super viewDidLoad];

    HTThread *thread = [[HTThread alloc] initWithTarget:self selector:@selector(test) object:nil];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];
}

- (void)test {
    NSLog(@"test on %@"[NSThread currentThread]);
}
// test on <HTThread: 0x600003cb52c0>{number = 7, name = (null)}
// HTThread dealloc
Copy the code

The process of opening RunLoop for child threads

Get the RunLoop object

RunLoop objects can be obtained by:

    // Foundation
    [NSRunLoop mainRunLoop];     // Get the main thread RunLoop object
    [NSRunLoop currentRunLoop];  // Get the RunLoop object for the current thread
    // Core Foundation
    CFRunLoopGetMain(a);// Get the main thread RunLoop object
    CFRunLoopGetCurrent(a);// Get the RunLoop object for the current thread
Copy the code

Let’s look at how the CFRunLoopGetCurrent() function gets the RunLoop object:

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());  // Call _CFRunLoopGet0 and pass in the current thread
}
Copy the code
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if(! __CFRunLoops) { __CFUnlock(&loopsLock);CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // ️ retrieves runloops from the __CFRunLoops dictionary as the Key
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if(! loop) {// ️ if the dictionary does not exist
	    CFRunLoopRef newLoop = __CFRunLoopCreate(t);  // Create RunLoop for the current thread
        __CFLock(&loopsLock);
	    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    	if(! loop) {CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);  // Save to the dictionary
	        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;
}
Copy the code

Start the RunLoop of the child thread

You can start the RunLoop of a child thread by:

    // Foundation
    [[NSRunLoop currentRunLoop] run];
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // Core Foundation
    CFRunLoopRun(a);CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0 e10.false);  // Set to true to exit the loop after executing Source/Port
Copy the code

CFRunLoopRun()/CFRunLoopRunInMode()

void CFRunLoopRun(void) {
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
        CHECK_FOR_FORK();
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); } SInt32CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {   
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
Copy the code

As you can see, it starts the RunLoop by calling the CFRunLoopRunSpecific() function, the implementation of which was explained in RunLoop (3) : The Event Loop mechanism.

Implement a resident thread

  • Benefits: No need to create and destroy child threads all the time when they are frequently used, which improves performance;
  • Condition: The task must be serial, not concurrent;
  • Steps:
    • ① Get/create the current threadRunLoop;
    • (2) to theRunLoopAdd aSource/PortSuch as to maintain theRunLoopIf there is none in ModeSource0/Source1/Timer/Observer.RunLoopWill quit immediately);
    • (3) start theRunLoop.
  • Sample code and test output are as follows:
// ViewController.m
#import "ViewController.h"
#import "HTThread.h"

@interface ViewController(a)
@property (nonatomic.strong) HTThread *thread;
@property (nonatomic.assign.getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[HTThread alloc] initWithBlock:^{
        NSLog(@"begin-----%@"[NSThread currentThread]);
        
        // ① Get/create the RunLoop of the current thread
        // add a Source/Port to the RunLoop to maintain the event loop of the RunLoop
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
        while(weakSelf && ! weakSelf.isStoped) {// ③ Start the RunLoop
            /* [[NSRunLoop currentRunLoop] run] If RunLoop's run method is called, an undestroyable thread is started because the run method repeatedly calls runMode:beforeDate: Method to run in NSDefaultRunLoopMode in other words, the method effectively opens an infinite loop that processes data */ from the input Sources of RunLoop and Timers 
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"end-----%@"[NSThread currentThread]);    
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (!self.thread) return;
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// The task that the child thread needs to perform
- (void)test
{
    NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
}

// Stop RunLoop for child thread
- (void)stopThread
{
    // Set the flag to YES
    self.stopped = YES;   
    / / stop RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());    
    NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
    // Empty the thread
    self.thread = nil;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    if (!self.thread) return;
    (waitUntilDone is set to YES, the current method will not continue until the child thread code has finished executing)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

@end


// HTThread.h
#import <Foundation/Foundation.h>
@interface HTThread : NSThread
@end

// HTThread.m
#import "HTThread.h"
@implementation HTThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end
Copy the code

Click view, and then exit the current ViewController. The output is as follows:

begin—–<HTThread: 0x600002b71240>{number = 6, name = (null)}

-[ViewController test]—–<HTThread: 0x600002b71240>{number = 6, name = (null)}

-[ViewController dealloc]

-[ViewController stopThread]—–<HTThread: 0x600002b71240>{number = 6, name = (null)}

end—–<HTThread: 0x600002b71240>{number = 6, name = (null)}

-[HTThread dealloc]

A link to the

Threading Programming Guide