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
RunLoop
It 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. UIApplicationMain
The 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 UIApplicationMain
helps us start the main thread RunLoop
.
UIApplicationMain
There 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-while
be executed in a loop.
Apple official RunLoop
model diagram
RunLoop
It is a loop in the thread, which RunLoop
continuously 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
RunLoop
Objects are encapsulated objects based on CFFoundation框
frame types.CFRunLoopRef
NSRunLoop
It is based onCFRunLoopRef
the encapsulation and provides object-oriented APIs, but these APIs are not thread-safe.
[NSRunLoop currentRunLoop];//获得当前RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象
CoreFoundation
frame CFRunLoopRef
object
CFRunLoopRef
It isCoreFoundation
within the framework and provides an API of pure C language functions. All these APIs are thread-safe.
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象
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 CFRunLoopGetCurrent
and 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 _CFRunLoopGet0
how this function is implemented and RunLoop
what 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
RunLoop
object. RunLoop
Saved in a globalDictionary
, thread askey
,RunLoop
asValue
- There is no object just created by the thread
RunLoop
.RunLoop
The thread will beRunLoop
created for the first time and destroyed when the thread ends. - The main thread
RunLoop
has been automatically obtained (created), and the sub-thread is not enabled by defaultRunLoop
.
3. RunLoop related classes
There RunLoop
are 5 related classes.
CFRunLoopRef
: representsRunLoop
the objectCFRunLoopModeRef
: representsRunLoop
the operating mode ofCFRunLoopSourceRef
:RunLoop
Input sources mentioned in the model.CFRunLoopTimerRef
: timing source
- Each
RunLoop
time the main function is called, only one of the operating modes (CFRunLoopModeRef
) is allowed to be specified, which is calledCurrentMode
; - If you need to switch
Mode
, you can only exitLoop
and enter againMode
. 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
mode
one ofSourcr/Timer/Observer
them,RunLoop
it will exit directly without entering the loop.
RunLoop
The structure is the same as that of a matryoshka doll. RunLoop
Inside Mode
, Mode
there areSouce / Observer / Timer
Implementation of RunLoop related classes
One RunLoop
contains several Mode
, and each Mode
contains several Source/Timer/Observer
.
This sentence is actually the relationship between 5 related classes
CFRunLoopModeRef
represents RunLoop
the operating mode, but please clarify the concept here. We RunLoop
can 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
CFRunLoopModeRef
object hasname
attributes, severalsource0
,source1
,timer
,observer
andport
. It can be seen that events aremode
managed by, andRunLoop
are responsible for managementMode
.
Five operating modes
Five registered by the system by default Mode
:
kCFRunLoopDefaultMode
: App's defaultMode
, usually the main threadMode
runs under this.UITrackingRunLoopMode
: Interface trackingMode
, used toScrollView
track touch sliding to ensure that the interface is not affected by other interactionsMode
(interaction events with the userMode
).UIInitializationRunLoopMode
: The first one you enter when you first start the AppMode
will no longer be used after the startup is completed and will be switched tokCFRunLoopDefaultMode
.GSEventReceiveRunLoopMode
: Internal for accepting system eventsMode
, usually not used.kCFRunLoopCommonModes
: This is a placeholderMode
, used as a markerkCFRunLoopDefaultMode
andUITrackingRunLoopMode
not a realMode
(pseudo-mode, not a real mode).
Among them kCFRunLoopDefaultMode
, UITrackingRunLoopMode
, kCFRunLoopCommonModes
are the patterns we need to use in development.
CommonModes
In RunLoop
the object, there is a CommonModes
member 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
Mode
can mark itself asCommon
a property byModeName
adding it toRunLoop
ancommonModes
. - Whenever
RunLoop
the content of an item changes, itRunLoop
will be synchronized to all items with the tag ._commonModeItems
Source/Observer/Timer
Common
Mode
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 commonModeItems
collection to the common pattern collection of the run loop, and commonModeItems
add to each in the common pattern collection Mode
to ensure that the event source of the common pattern is available under Mode
multiple can be processed. It involves CoreFoundation
the underlying operations of the run loop in the framework and is used to manage events and patterns in the run loop.
CFRunLoopSourceRef
CFRunLoopSourceRef
: RunLoop
The 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 souce
the existence source0
and source1
two versions above. What did they do respectively?
Source0
It only contains a callback (function pointer), which cannot actively trigger events. When using it, you need to call it firstCFRunLoopSourceSignal(source)
,Source
mark it as pending, and then call it manuallyCFRunLoopWakeUp(runloop)
to wake it upRunLoop
and let it handle this event.Source1
Contains onemach_port
and a callback (function pointer), which can be used by the kernel and other threads to send messages to each other, whichSource
can actively wake upRunLoop
the thread.- ⚠️:
button
The click event belongs toSource0
the execution content of the function. The click event isSource0
processed.
Source1
It is used to receive and distribute events and distribute themSouce0
for processing.
CFRunLoopTimerRef
CFRunLoopTimerRef
: Timing source - time-based trigger.
CFRunLoopTimerRef
is a time-based trigger that NSTimer
can be mixed with . It contains a duration and a callback (function pointer). When it joins RunLoop
, RunLoop
it will register the corresponding time point, and when the time point arrives, RunLoop
it will be awakened to execute that callback.
When we NSTimer
callscheduledTimerWithTimeInterval
[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 tableView
caused the timer failure of the automatic carousel chart above.
A common problem is: when we NSTimer
slide to perform something every time UIScrollView
, NSTimer
it will pause, and when we stop sliding, NSTimer
it will resume again.
The reason is:
When we don't do anything, RunLoop
we are NSDefaultRunLoopMode
down.
When we drag and drop, RunLoop
it ends NSDefaultRunLoopMode
and switches to UITrackingRunLoopMode
mode. There is no addition in this mode NSTimer
, so ours NSTimer
will not work.
When we release the mouse, RunLoop
it ends UITrackingRunLoopMode
the mode and switches back to NSDefaultRunLoopMode
the mode, so NSTimer
it starts working normally again.
solve:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
In iOS, when you swipe UITableView
or another scroll view, the on the main thread RunLoop
switches 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 NSDefaultRunLoopMode
run in the default run loop mode. Since RunLoop
only one run loop mode can be processed at a time, when you slide, NSDefaultRunLoopMode
it 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 NSDefaultRunLoopMode
and 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
CFRunLoopObserverRef
It is an observer, each Observer
containing a callback (function pointer). When RunLoop
the 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?
Mode
What types of elements are included?
As mentioned earlier CFMutableSetRef _commonModeItems
: store commonMode
all item
( source
, timer
, observer
)
The // Source
above are collectively referred to asTimer
Observer
mode item
All mode item
can be added to Mode
, Mode
multiple can be included in mode item
, and one item
can be added to multiple mode
. But when one item
is added to the same one repeatedly, mode
it will have no effect. If there is none mode
of item
them, RunLoop
it will exit without entering the loop.
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.
RunLoop
The run must specify onemode
, and themode
task event must be registered.RunLoop
Itmode
runs by default. Of course, you can also specify a type ofmode
operation, but it can onlymode
run under one type.RunLoop
A loop is actually maintained internallydo-while
, and the thread will remain in this loop until it times out or is manually stopped.RunLoop
The core is onemach_msg()
.RunLoop
Call this function to receive messages. If no one else sendsport
a message, the kernel will put the thread in a waiting state, otherwise the thread will process the event.
4. Practical application of RunLoop
- Control thread life cycle (thread keep alive)
- Fix issue where NSTimer stops working when sliding
- Monitor application lag
- Performance optimization
How to start RunLoop
- (void)run; // 默认模式
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
- 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
- runUnitDate: Set time limit.
- The timeout is set, and the RunLoop ends after this time.
- 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
- Set the running loop configuration to timeout.
- 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.