RunLoop Summary: application scenarios of RunLoop (1): runloop scenarios
References
Good books are worth reading. Good articles and good materials are worth reading. We can get different results from the same articles, materials, or books at different stages, that is, good articles, good books, and good materials.
There is very little information about RunLoop in iOS. The following information is very good.
- CF framework source code (this is a very important source code. We can see each iteration of the CF framework. We can download the latest version for analysis or compare it with the following articles. The latest cf-1153.18.tar.gz)
- RunLoop official documentation (to learn any iOS technology, official documentation is an excellent beginner or in-depth manual; you can also go to Xcode ---> Help ---> Docementation and API Reference ---> Search for RunLoop ---> Guides (59) ---> Threading Programming Guide: Run Loops this is)
- In-depth understanding of RunLoop (do not see a long scroll bar on the right. In fact, the article occupies about 2/5 of the length. There are many comments below, which shows the popularity of this article)
- RunLoop personal summary (this is a summary article that is easy to understand)
- Sunnyxx offline sharing RunLoop (this is a video about offline sharing and discussion of RunLoop, alternate address: https://pan.baidu.com/s/1pLm4Vf9)
- CFRunLoop in iPhone devwiki (commonModes actually contains three modes. We usually know two modes. What is the other one? Do you know ?)
- Event loop in Wikipedia (let's take a look at this article to learn about Event loops)
Description
Because RunLoop contains many new concepts or objects that are hardly accessible at ordinary times, it is hard to get confused about what RunLoop is and what it contains, too obscure. Most of the articles about RunLoop are also based on the article, which is also relatively long. It may have been done after reading 1/3, and there is no motivation for technology. So I decided to start with the use cases and usage of RunLoop, and I saw some usage and phenomena, and then it would be easier to understand its implementation.
Sample Code in the article. At the end of the article, I will provide a Demo of RunLoop.
Use Cases of RunLoop
The following describes several Use Cases of RunLoop. (If you want to write an article, you can leave the article a long time in one use case. Let's talk about it in several articles ).
1. Ensure that the thread remains alive for a long time
During iOS development, sometimes we do not want to block the main thread for some long-time operations, resulting in interface freezing, so we will create a subthread, then, put these long-time operations in the Child thread for processing. However, when the task in the sub-thread is completed, the sub-thread will be destroyed.
How can we verify the above conclusion?
First, we create an HLThread class that inherits from NSThread and then override the dealloc method.
@interface HLThread : NSThread@end@implementation HLThread- (void)dealloc{ NSLog(@"%s",__func__);}@end
Then, use HLThread in the Controller to create a thread and execute a task. Observe whether the thread is destroyed after the task is executed.
-(Void) viewDidLoad {[super viewDidLoad]; // 1. test thread destruction [self threadTest];}-(void) threadTest {HLThread * subThread = [[HLThread alloc] initWithTarget: self selector: @ selector (subThreadOpetion) object: nil]; [subThread start];}-(void) subThreadOpetion {NSLog (@ "% @ ---- subThread task start", [NSThread currentThread]); [NSThread sleepForTimeInterval: 3.0]; NSLog (@ "% @ ---- subthread task end", [NSThread currentThread]);}
The console outputs the following results:
16:44:25. 559 RunLoopDemo [4516: 352041] <HLThread: 0x608000275680> {number = 4, name = (null)} ---- subthread task start 16:44:28. 633 RunLoopDemo [4516: 352041] <HLThread: 0x608000275680> {number = 4, name = (null)} ---- end of the subthread task 16:44:28. 633 RunLoopDemo [4516: 352041]-[HLThread dealloc]
When the task in the subthread is completed, the thread is immediately destroyed. If the program needs to execute tasks frequently in the Child thread, frequent creation and destruction of threads will cause a waste of resources. At this time, we can use RunLoop to keep the thread alive for a long time without being destroyed.
Let's modify the sample code above. The modified code process is to create a subthread. When the subthread starts, start runloop and click View, it will execute a task that takes 3 seconds in the Child thread (in fact, it is to sleep the thread for 3 seconds ).
The modified code is as follows:
@ Implementation ViewController-(void) viewDidLoad {[super viewDidLoad]; // 1. test thread destruction [self threadTest];}-(void) touchesBegan :( NSSet <UITouch *> *) touches withEvent :( UIEvent *) event {[self defined mselector: @ selector (subThreadOpetion) onThread: self. subThread withObject: nil waitUntilDone: NO];}-(void) threadTest {HLThread * subThread = [[HLThread alloc] initWithTarget: self selector: @ selector (subThreadEntryPoint) object: nil]; [subThread setName: @ "HLThread"]; [subThread start]; self. subThread = subThread;}/** after the sub-thread starts, start runloop */-(void) subThreadEntryPoint {nsunloop * runLoop = [nsunloop currentRunLoop]; // if the following line is commented out, the tasks in the subthread cannot be normally executed [runLoop addPort: [NSMachPort port] forMode: nsunloopcommonmodes]; NSLog (@ "before RunLoop startup -- % @", runLoop. currentMode); [runLoop run];}/** subthread task */-(void) subThreadOpetion {NSLog (@ "after RunLoop startup -- % @", [nsunloop currentRunLoop]. currentMode); NSLog (@ "% @ ---- subthread task start", [NSThread currentThread]); [NSThread sleepForTimeInterval: 3.0]; NSLog (@ "% @ ---- subthread task end", [NSThread currentThread]);} @ end
First look at the console output result:
17:22:44. 396 RunLoopDemo [4733: 369202] Before starting RunLoop -- (null) 17:22:49. 285 RunLoopDemo [4733: 369202] After RunLoop is started -- kCFRunLoopDefaultMode2016-12-01 17:22:49. 285 RunLoopDemo [4733: 369202] <HLThread: 0x60000027cb40> {number = 4, name = HLThread} ---- subthread task start 17:22:52. 359 RunLoopDemo [4733: 369202] <HLThread: 0x60000027cb40> {number = 4, name = HLThread} ---- end of subthread task 17:22:55. 244 RunLoopDemo [4733: 369202] After RunLoop is started -- kCFRunLoopDefaultMode2016-12-01 17:22:55. 245 RunLoopDemo [4733: 369202] <HLThread: 0x60000027cb40> {number = 4, name = HLThread} ---- subthread task start 17:22:58. 319 RunLoopDemo [4733: 369202] <HLThread: 0x60000027cb40> {number = 4, name = HLThread} ---- end of the subthread task
Pay attention to the following points:
1. To obtain RunLoop, you can only use [nsunloop currentRunLoop] or [nsunloop mainRunLoop];
2. Even if the RunLoop starts running, if the modes in the RunLoop is empty or there is no item in the mode to be executed, the RunLoop will return directly in the current loop and enter the sleep state.
3. Tasks in the self-created Thread are executed in the mode kCFRunLoopDefaultMode.
Note: 1.
The second section in the RunLoop official document shows that our applications do not need to create their own RunLoop, but start the runloop at the appropriate time.
The CF framework source code containsCFRunLoopGetCurrent(void)AndCFRunLoopGetMain(void)The source code shows that the two APIs are obtained from the Global dictionary first. If there is no RunLoop corresponding to the thread, then it will help us create a RunLoop (the process of creating a RunLoop is in the function_CFRunLoopGet0(pthread_t t)).
NOTE 2:
In this example[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];, You can see that no output exists in the console no matter how we click the view, because there is no item task in the mode. After the nsunloop encapsulation, you can only add two types of item tasks to the mode: NSPort (corresponding to source) and NSTimer. If you useCFRunLoopRefYou can use the C language API to add source, timer, and observer to the mode.
If you do not add[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];, We output the runloop information. We can see that:
RunLoop before adding a port
If we add[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];And output the RunLoop information. You can see:
RunLoop after adding port
NOTE 3:
How can I ensure that tasks on the created sub-thread are executed in the mode of kCFRunLoopDefaultMode?
We only need to print the currentMode of the RunLoop when executing the task.
Because the RunLoop execution task will switch between modes and only execute the tasks in this mode. Every time you switch to a mode, currentMode will be updated. Download the source code: CF framework source code
CFRunLoopRun()Will be calledCFRunLoopRunSpecific()Method, whileCFRunLoopRunSpecific()There are two lines of key code in the method:
CFRunLoopModeRef currentMode = _ CFRunLoopFindMode (rl, modeName, false );...... there are still a lot of logic code in the middle: CFRunLoopModeRef previusmode = rl-> _ currentMode; rl-> _ currentMode = currentMode ;...... there is also a heap of logic rl-> _ currentMode = previusmode;
After testing, the console outputs the following:
11:09:47. 909 RunLoopDemo [5479: 442560] After RunLoop is started -- kCFRunLoopDefaultMode2016-12-02 11:09:47. 910 RunLoopDemo [5479: 442560] <HLThread: 0x608000270a80> {number = 4, name = HLThread} ---- start of the subthread task 11:09:50. 984 RunLoopDemo [5479: 442560] <HLThread: 0x608000270a80> {number = 4, name = HLThread} ---- subthread task ended
RunLoop case in AFNetworking
In versions earlier than AFNetworking 2.6.3, NSURLConnection is used.AFURLConnectionOperationFind the source code using RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}+ (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread;}
AFNetworking always throws the task to the RunLoop of the background thread by calling [NSObject into mselector: onThread.
- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock];}
We are usingNSURLConnectionOrNSStreamYou also need to consider the RunLoop problem, because by default, the objects of these two classes are generated in the current threadNSDefaultRunLoopModeRun the task. If it is in the main thread, the main thread's RunLoop switchesUITrackingRunLoopModeMode, thenNSURLConnectionOrNSStreamThe callback cannot be executed.
There are two ways to solve this problem:
The first method is to createNSURLConnectionObject orNSStreamObject before calling- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode, Set RunLoopMode. Note thatNSURLConnectionThe initialization constructor must be used.- (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediatelyTo create an object. Setting Mode takes effect.
The second method is to execute all tasks in the Child thread and ensure that the RunLoop of the child thread runs properly (that is, the above AFNetworking practice, because the RunLoop of the main thread switchesUITrackingRunLoopModeIt does not affect the mode in which other threads execute the task. The CPU of the Computer switches to a different thread for a while at each time slice, and the multi-thread effect is displayed ).
The sample code in this article is from RunLoopDemo01 in RunLoopDemos.