【iOS】RunLoop

Preface-What is RunLoop?

What is RunLoop? Running laps? That's literally what it means.

Apple official documentation explains thisRunLoop

RunLoop is part of the basic structure closely related to threads. RunLoop is an event loop that schedules tasks and processes tasks. The purpose of RunLoop is to keep the thread busy when there is work, and to put the thread into sleep state when there is no work.

The reason why iOS apps can continue to respond and keep the program running is that there is an event loop ( Event Loop) mechanism: a mechanism in which threads can respond and handle events at any time. This mechanism requires that threads cannot exit to efficiently complete event scheduling and deal with.

In iOS, this event loop mechanism is calledRunLoop

RunLoopIt is actually an object. The object handles various events that occur during the running of the program (such as touch events, UI refresh events, timer events, events Selector) in a loop to keep the program running and allow the program to enter when there is no event processing. Sleep state, thereby saving CPU resources to improve program performance.

RunLoop principle of main thread by default

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    
    
    NSString * appDelegateClassName;
    @autoreleasepool {
    
    
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

return UIApplicationMain(argc, argv, nil, appDelegateClassName);This is the main run loop of an iOS application, which handles user events, interface updates, and the main logic of the application. UIApplicationMainThe function creates the application object and the main run loop, and passes control to the application's delegate class ( AppDelegate) to handle the application's logic.

The function inside UIApplicationMainhelps us start the main thread RunLoop.
UIApplicationMainThere is an infinite loop of code inside.

function loop() {
    
    
    initialize();
    do {
    
    
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

The program will always do-whilebe executed in a loop.

Apple official RunLoopmodel diagram

Insert image description here

RunLoopIt is a loop in the thread, which RunLoopcontinuously detects in the loop, waits to receive messages through two sources Input sources(input source) and Timer sources(timing source), and then processes the received event notification thread, and rests when there is no event.

1. RunLoop object

RunLoopObjects are encapsulated objects based on CFFoundation框frame types.CFRunLoopRef

  • NSRunLoopIt is based on CFRunLoopRefthe encapsulation and provides object-oriented APIs, but these APIs are not thread-safe.
[NSRunLoop currentRunLoop];//获得当前RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象

CoreFoundationframe CFRunLoopRefobject

  • CFRunLoopRefIt is CoreFoundationwithin the framework and provides an API of pure C language functions. All these APIs are thread-safe.
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象

Insert image description here

Then the two corresponding ways are:

- (void)getRunLoop {
    
    
    NSRunLoop *runloop  = [ NSRunLoop currentRunLoop];
    NSRunLoop *manRlp = [NSRunLoop mainRunLoop];
    
    CFRunLoopRef cfRlp = CFRunLoopGetCurrent();
    CFRunLoopRef mainCfRlp = CFRunLoopGetMain();
}

Let’s look at the specific implementation of CFRunLoopGetCurrentand CFRunLoopGetMain:

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

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;
}

It is found that this function is called in the process _CFRunLoopGet0, which will be explained later.

CFRunLoopRef source code part (related to introducing threads)

Source code part of CFRunLoopRef

struct __CFRunLoop {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort; //【通过该函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop】
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread; //【RunLoop对应的线程】
    uint32_t _winthread;
    CFMutableSetRef _commonModes; // 【存储的是字符串,记录所有标记为common的mode】
    CFMutableSetRef _commonModeItems;//【存储所有commonMode的item(source、timer、observer)】
    CFRunLoopModeRef _currentMode;//【当前运行的mode】
    CFMutableSetRef _modes;//【存储的是CFRunLoopModeRef】
    struct _block_item *_blocks_head;//【do blocks时用到】
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

In addition to some attributes, the focus needs to be on three member variables

pthread_t _pthread;【RunLoop对应的线程】
CFRunLoopModeRef _currentMode;【当前运行的mode】
CFMutableSetRef _modes;【存储的是CFRunLoopModeRef】

Take a look at the relationship between RunLoop and threads.

2. RunLoop and threads

Let’s first take a look at _CFRunLoopGet0how this function is implemented and RunLoopwhat it has to do with threads.

//全局的Dictionary,key是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0是始终有效的“主线程”的同义词

//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
    
    if (pthread_equal(t, kNilPthreadT)) {
    
    
    //pthread为空时,获取主线程
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
    
    
        __CFSpinUnlock(&loopsLock);
        //第一次进入时,创建一个临时字典dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //根据传入的主线程获取主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    //保存主线程,将主线程-key和RunLoop-Value保存到字典中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    
    
    //释放dict
        CFRelease(dict);
    }
    //释放mainRunLoop
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //以上说明,第一次进来的时候,不管是getMainRunLoop还是get子线程的runLoop,主线程的runLoop总是会被创建

	//从全局字典里获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
    
    
    //如果取不到,就创建一个新的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //创建好之后,以线程为key,runLoop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runLoop
    if (!loop) {
    
    
    //把newLoop存入字典__CFRunLoops,key是线程t
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
    }

	//如果传入线程就是当前线程
    if (pthread_equal(t, pthread_self())) {
    
    
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
    
    
        //注册一个回调,当线程销毁时,销毁对应的RunLoop
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

This source code tells us:

  • Each thread has a unique corresponding RunLoopobject.
  • RunLoopSaved in a global Dictionary, thread as key, RunLoopasValue
  • There is no object just created by the thread RunLoop. RunLoopThe thread will be RunLoopcreated for the first time and destroyed when the thread ends.
  • The main thread RunLoophas been automatically obtained (created), and the sub-thread is not enabled by default RunLoop.

3. RunLoop related classes

There RunLoopare 5 related classes.

  • CFRunLoopRef: represents RunLoopthe object
  • CFRunLoopModeRef: represents RunLoopthe operating mode of
  • CFRunLoopSourceRef: RunLoopInput sources mentioned in the model.
  • CFRunLoopTimerRef: timing source

Insert image description here

  • Each RunLooptime the main function is called, only one of the operating modes ( CFRunLoopModeRef) is allowed to be specified, which is called CurrentMode;
  • If you need to switch Mode, you can only exit Loopand enter again Mode. This is mainly to separate different groups of input sources ( CFRunLoopSourceRef), timing sources ( CFRunLoopTimerRef), and observers ( CFRunLoopObserverRef) so that they do not affect each other.
  • If there is not modeone of Sourcr/Timer/Observerthem, RunLoopit will exit directly without entering the loop.

RunLoopThe structure is the same as that of a matryoshka doll. RunLoopInside Mode, Modethere areSouce / Observer / Timer

Implementation of RunLoop related classes

One RunLoopcontains several Mode, and each Modecontains several Source/Timer/Observer.
This sentence is actually the relationship between 5 related classes

CFRunLoopModeRef

represents RunLoopthe operating mode, but please clarify the concept here. We RunLoopcan install multiple ones in it Mode, but we need to specify one when specifying the operation Mode.

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名称,运行模式是通过名称来识别的
    Boolean _stopped; //mode是否被终止
    char _padding[3];
    //整个结构体最核心的部分
------------------------------------------
    CFMutableSetRef _sources0;//Sources0
    CFMutableSetRef _sources1;//Sources1
    CFMutableArrayRef _observers;//观察者
    CFMutableArrayRef _timers;//定时器
------------------------------------------
    CFMutableDictionaryRef _portToV1SourceMap;//字典    key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    CFIndex _observerMask;
};
  • An CFRunLoopModeRefobject has nameattributes, several source0, source1, timer, observerand port. It can be seen that events are modemanaged by, and RunLoopare responsible for management Mode.

Five operating modes

Five registered by the system by default Mode:

  • kCFRunLoopDefaultMode: App's default Mode, usually the main thread Moderuns under this.
  • UITrackingRunLoopMode: Interface tracking Mode, used to ScrollViewtrack touch sliding to ensure that the interface is not affected by other interactions Mode(interaction events with the user Mode).
  • UIInitializationRunLoopMode: The first one you enter when you first start the App Modewill no longer be used after the startup is completed and will be switched to kCFRunLoopDefaultMode.
  • GSEventReceiveRunLoopMode: Internal for accepting system events Mode, usually not used.
  • kCFRunLoopCommonModes: This is a placeholder Mode, used as a marker kCFRunLoopDefaultModeand UITrackingRunLoopModenot a real Mode(pseudo-mode, not a real mode).

Among them kCFRunLoopDefaultMode, UITrackingRunLoopMode, kCFRunLoopCommonModesare the patterns we need to use in development.

CommonModes

In RunLoopthe object, there is a CommonModesmember variable in front.

//简化版本
struct __CFRunLoop {
    
    
    pthread_t _pthread;
    CFMutableSetRef _commonModes;//存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;//当前运行的mode
    CFMutableSetRef _modes;//存储的是CFRunLoopModeRef对象,不同mode类型,它的mode名字不同
};
  • One Modecan mark itself as Commona property by ModeNameadding it to RunLoopan commonModes.
  • Whenever RunLoopthe content of an item changes, it RunLoopwill be synchronized to all items with the tag ._commonModeItemsSource/Observer/TimerCommonMode

Its underlying principle

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    
    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    
    
    //获取所有的_commonModeItems
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    //获取所有的_commonModes
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL != set) {
    
    
        CFTypeRef context[2] = {
    
    rl, modeName};
        //将所有的_commonModeItems逐一添加到_commonModes里的每一个Mode
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set);
    }
    } else {
    
    
    }
    __CFRunLoopUnlock(rl);
}

Overall, the main function of this method is to add a specified commonModeItemscollection to the common pattern collection of the run loop, and commonModeItemsadd to each in the common pattern collection Modeto ensure that the event source of the common pattern is available under Modemultiple can be processed. It involves CoreFoundationthe underlying operations of the run loop in the framework and is used to manage events and patterns in the run loop.

CFRunLoopSourceRef

CFRunLoopSourceRef: RunLoopThe input source mentioned in the model, which is where the event occurs.

struct __CFRunLoopSource {
    
    
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;   //执行顺序
    CFMutableBagRef _runLoops;//包含多个RunLoop
    //版本
    union {
    
    
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

I just mentioned soucethe existence source0and source1two versions above. What did they do respectively?

  • Source0It only contains a callback (function pointer), which cannot actively trigger events. When using it, you need to call it first CFRunLoopSourceSignal(source), Sourcemark it as pending, and then call it manually CFRunLoopWakeUp(runloop)to wake it up RunLoopand let it handle this event.
  • Source1Contains one mach_portand a callback (function pointer), which can be used by the kernel and other threads to send messages to each other, which Sourcecan actively wake up RunLoopthe thread.
  • ⚠️: buttonThe click event belongs to Source0the execution content of the function. The click event is Source0processed.
    Source1It is used to receive and distribute events and distribute them Souce0for processing.

CFRunLoopTimerRef

CFRunLoopTimerRef: Timing source - time-based trigger.

CFRunLoopTimerRefis a time-based trigger that NSTimercan be mixed with . It contains a duration and a callback (function pointer). When it joins RunLoop, RunLoopit will register the corresponding time point, and when the time point arrives, RunLoopit will be awakened to execute that callback.

When we NSTimercallscheduledTimerWithTimeInterval

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

The system will automatically add NSDefaltRunLoopMode.

Equal to the following code

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

Timer sliding is inaccurate

In Zhihu Daily, sliding tableViewcaused the timer failure of the automatic carousel chart above.
A common problem is: when we NSTimerslide to perform something every time UIScrollView, NSTimerit will pause, and when we stop sliding, NSTimerit will resume again.

The reason is:

When we don't do anything, RunLoopwe are NSDefaultRunLoopModedown.
When we drag and drop, RunLoopit ends NSDefaultRunLoopModeand switches to UITrackingRunLoopModemode. There is no addition in this mode NSTimer, so ours NSTimerwill not work.
When we release the mouse, RunLoopit ends UITrackingRunLoopModethe mode and switches back to NSDefaultRunLoopModethe mode, so NSTimerit starts working normally again.

solve:

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

In iOS, when you swipe UITableViewor another scroll view, the on the main thread RunLoopswitches to UITrackingRunLoopMode, which is a special run loop mode used to handle user interaction events, such as scroll gestures. By default, if you use a timer on the main thread, it will NSDefaultRunLoopModerun in the default run loop mode. Since RunLooponly one run loop mode can be processed at a time, when you slide, NSDefaultRunLoopModeit is switched to UITrackingRunLoopMode, causing the timer event to pause until the slide ends.

To solve this problem, you can use kCFRunLoopCommonModes. Represents a " common mode set", which includes both NSDefaultRunLoopModeand UITrackingRunLoopMode. By using it in the addition of a timer kCFRunLoopCommonModes, the timer can be triggered in both default mode and tracking mode, thereby avoiding the problem of sliding causing the timer to pause.

CFRunLoopObserverRef

CFRunLoopObserverRefIt is an observer, each Observercontaining a callback (function pointer). When RunLoopthe state changes, the observer can receive the change through the callback.

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
    
    
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;//监听的RunLoop
    CFIndex _rlCount;//添加该Observer的RunLoop对象个数
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;//同时间最多只能监听一个
    CFRunLoopObserverCallBack _callout;//监听的回调
    CFRunLoopObserverContext _context;//上下文用于内存管理
};

//观测的时间点有一下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    
    
    kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

What is Mode Item?

ModeWhat types of elements are included?
As mentioned earlier CFMutableSetRef _commonModeItems: store commonModeall item( source, timer, observer)

The // Sourceabove are collectively referred to asTimerObservermode item

All mode itemcan be added to Mode, Modemultiple can be included in mode item, and one itemcan be added to multiple mode. But when one itemis added to the same one repeatedly, modeit will have no effect. If there is none modeof itemthem, RunLoopit will exit without entering the loop.

Insert image description here

Implementation principle of RunLoop sleep

Switch from user mode to kernel mode, let the thread sleep in the kernel mode, wake up the thread when there is a message, and return to the user mode to process the message.

RunLoop summary

The inside of RunLoop is actually a do while loop. When CFRunLoopRun() is called, the thread will stay in this loop. The function will only return when it times out or is called manually.

  • RunLoopThe run must specify one mode, and the modetask event must be registered.
  • RunLoopIt moderuns by default. Of course, you can also specify a type of modeoperation, but it can only moderun under one type.
  • RunLoopA loop is actually maintained internally do-while, and the thread will remain in this loop until it times out or is manually stopped.
  • RunLoopThe core is one mach_msg() . RunLoopCall this function to receive messages. If no one else sends porta message, the kernel will put the thread in a waiting state, otherwise the thread will process the event.

4. Practical application of RunLoop

  1. Control thread life cycle (thread keep alive)
  2. Fix issue where NSTimer stops working when sliding
  3. Monitor application lag
  4. Performance optimization

How to start RunLoop

- (void)run; // 默认模式
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
  1. run: It can be started without any conditions. Although it is simple, it does not affect the actual control results. RunLoop is a do while loop. If RunLoop is run unconditionally, the thread will be put into the loop forever, which means we have no way to control the loop itself. , RunLoop can only be stopped by killing the process
  2. runUnitDate: Set time limit.
  • The timeout is set, and the RunLoop ends after this time.
  1. runMode:beforeDate: Start in a specific mode.
  • You can specify which mode runloop runs in, but it is a single call. When the timeout reaches or an input source is processed, runLoop will automatically exit. Both of the above methods are called cyclically.

The first one will continue to run, and will always be in NSDefaultRunLoopMode mode, calling the runMode:beforeDate: method repeatedly.
The second method will always call the runMode:beforeDate: method in NSDefaultRunLoopMode mode before the timeout.
The third method will always call the runMode:beforeDate: method until the timeout reaches or the first inputsource is processed.

RunLoop closed

  1. Set the running loop configuration to timeout.
  2. Stop manually.

It should be noted here that although deleting the input source and timer of the runloop may cause the run loop to exit, this is not a reliable method. The system may add input sources to the runloop, but it may not be known in our code. These input sources, so they cannot be deleted, resulting in the runloop being unable to exit.

When we start RunLoop through the runUnitDate and runMode: beforeDate: methods, we set the timeout. However, if we need to have the most precise control over this thread and its RunLoop, instead of relying on the timeout mechanism, we can manually end a RunLoop through the CFRunLoopStop() method. . However, the CFRunLoopStop() method will only end the runMode:beforeDate: call currently being executed, but will not end subsequent runloop calls.

imageView delayed display

When the interface contains UITableView, and there are pictures in each UITableViewCell. This is when we scroll the UITableView, if there are a bunch of pictures that need to be displayed, there may be lags.

We should postpone the implementation of pictures, that is, ImageView delays displaying pictures. Don't load the image when we slide, display it after dragging:

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

The user clicks the screen, and in the main thread, the picture is displayed after three seconds. However, after the user clicks the screen, if the user starts scrolling the tableview again, the picture will not be displayed even after three seconds. When the user stops scrolling , the picture will be displayed.

This is because the method setImage is restricted to be used only in NSDefaultRunLoopMode mode. When scrolling the tableview, the program runs in tracking mode, so the method setImage will not be executed.

resident thread

During the development of an application, if background operations are very frequent, such as playing music in the background, downloading files, etc., we hope that the thread that executes the background code will always reside in the memory. We can add a strong reference for the permanent memory. Thread, add a Sources under the RunLoop of the thread and turn on the RunLoop:

@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end

 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
 [self.thread start];
- (void)runThread {
    
    
    NSLog(@"开启子线程:%@", [NSThread currentThread]);
// 子线程的RunLoop创建出来需要手动添加事件输入源和定时器 因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
    //下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    // 测试开始RunLoop
    // 未进入循环就会执行该代码
    NSLog(@"failed");
}

// 同时在我们自己新建立的这个线程中写一下touchesBegan这个方法测试点击空白处会不会在子线程相应方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  {
    
    
    [self performSelector:@selector(runTest) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)runTest {
    
    
    NSLog(@"子线程点击空白:%@", [NSThread currentThread]);
}

You can see that RunLoop successfully starts and enters the loop. When the screen is clicked, the method is also called in the sub-thread. In this way, the purpose of the resident thread is achieved after the sub-thread is started.

Thread keep alive

Scenario:
When a child thread is usually created, the thread will be destroyed after the tasks on the thread are executed.
Sometimes we need to frequently perform tasks in a child thread. Frequent creation and destruction of threads will cause a lot of overhead. At this time, we can control the life cycle of the thread through runloop.

In the following code, because the runMode:beforeDate: method is a single call, we need to add a loop to it, otherwise calling the runloop once will end, and the effect will be the same as not using a runloop.

The condition of this loop is set to YES by default. When the stop method is called, the CFRunLoopStop() method is executed to end this runMode:beforeDate:. At the same time, the condition in the loop is set to NO to stop the loop and the runloop exits.

@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL stopped;

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor greenColor];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:button];
    [button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"执行任务" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 200, 100, 20);
    
    
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    [stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
    [stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
    stopButton.frame = CGRectMake(100, 400, 100, 20);
    
    self.stopped = NO;
    //防止循环引用
    __weak typeof(self) weakSelf = self;
    
    self.thread = [[NSThread alloc] initWithBlock:^{
    
    
        NSLog(@"Thread---begin");
        
        //向当前runloop添加Modeitem,添加timer、observer都可以。因为如果mode没有item,runloop就会退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
    
    
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"Thread---end");
    }];
    [self.thread start];
}
- (void)pressPrint {
    
    
    //子线程中调用print
    [self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}

//子线程需要执行的任务
- (void)print {
    
    
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

- (void)pressStop {
    
    
    //子线程中调用stop
    if (_stopped == NO ) {
    
    
        [self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    
}

//停止子线程的runloop
- (void)stop {
    
    
    //设置标记yes
    self.stopped = YES;
    
    //停止runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    //解除引用, 停止runloop这个子线程就会dealloc
    self.thread = nil;
}

- (void)dealloc {
    
    
    NSLog(@"%s", __func__);
}

NSTimer is not accurate

In actual development, timer generally does not exist in the RunLoop of the main thread, because the timer of the timer will also cause inaccuracy when the main thread performs blocking tasks.

If the timer blocks in the main thread, how to solve the problem of inaccurate timer.

  • Put it into a child thread, but it needs to open up the thread and control the life cycle of the thread, which costs a lot.
  • Use GCD's timer to avoid blocking.

Guess you like

Origin blog.csdn.net/m0_63852285/article/details/132107089