The resources

Good books are worth reading again and again, and good articles and materials are worth reading again and again. We come to the same article or material or book in different stages can have different harvest, then it is a good article, good book, good material. There is very little information on RunLoop in iOS, and the following are some of the best.

  • CF framework source code (this is a very important source, you can see each iteration of the CF framework, we can download the latest version to analyze, or with the following article comparison study. The latest one is CF-1153.18.tar.gz.)
  • RunLoop official Documentation (The official documentation is a great manual for getting started or getting in depth with any technology you need for iOS; We can also search RunLoop– > Guides (59) –> Threading Programming Guide:Run in Xcode– >Help– >Docementation and API Reference Loops (Loops)
  • Understand RunLoop in depth (don’t notice how long the scroll bar on the right is, it’s about 2/5 of the article, and there are a lot of comments below, which shows how popular this article is)
  • RunLoop Personal Summary (this is a summary of a very popular and easy to understand article)
  • Sunnyxx sharing RunLoop offline (this is a video about sharing and discussing RunLoop offline, alternate address: pan.baidu.com/s/1pLm4Vf9)
  • CFRunLoop in iPhonedevwiki (commonModes include three modes, we usually know two, and what is the other one?)
  • Event loop in Wikipedia (check out this article to learn more about Event loops)

instructions

Because RunLoop contains many new concepts and objects that are difficult to access, it is hard to understand what RunLoop is, what it contains, and why. Most RunLoop articles also start with the basics and are quite long, so you might get stuck a third of the way through and lose the motivation to follow through. So I decided to start with RunLoop scenarios and usage. After seeing some usage and phenomena, it would be much easier to understand the implementation. I’ll provide an example of RunLoop at the end of this article.

RunLoop usage scenarios

Here are a few scenarios in which RunLoop can be used.

1. Keep threads alive for a long time

In iOS development, sometimes we don’t want to block the main thread and cause the interface to stall, so we create a child thread and put those long-running operations in the child thread. However, when the task in the child thread completes, the child thread will be destroyed. How does ** 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

Copy the code

Then, create a thread with HLThread in the controller, execute a task, and observe whether the thread is destroyed after the task is completed.

- (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 { @autoreleasepool { NSLog(@"%@---- child thread task started",[NSThread currentThread]); [NSThread sleepForTimeInterval: 3.0]; NSLog(@"%@---- child thread task terminated",[NSThread currentThread]); }}Copy the code

The console output is as follows:

The 2016-12-01 16:44:25. 559 RunLoopDemo [4516-352041] < HLThread: 0x608000275680>{number = 4, name = (null)}---- Child thread task starts 2016-12-01 16:44:28.633 RunLoopDemo[4516:352041] <HLThread: 0x608000275680>{number = 4, Name = (null)}---- Sub-thread task end 2016-12-01 16:44:28.633 RunLoopDemo[4516:352041] -[HLThread dealloc]Copy the code

When the task in the child thread completes, the thread is destroyed immediately. If the program often needs to execute tasks in child threads, frequent creation and destruction of threads will cause a waste of resources. At this point we can use RunLoop to keep the thread alive for a long time without being destroyed.

To modify the example above, create a child thread. When the child thread starts, start runloop, click the view, and execute a 3-second task in the child thread.

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 performSelector:@selector(subThreadOpetion) onThread:self.subThread withObject:nilwaitUntilDone:NO];
}

- (void)threadTest
{
    HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadEntryPoint) object:nil];
    [subThread setName:@"HLThread"]; [subThread start]; self.subThread = subThread; } /** After the child thread starts, Runloop */ - (void)subThreadEntryPoint {@autoReleasepool {NSRunLoop * runloop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port]] [runLoop addPort:[NSMachPort port]forMode:NSRunLoopCommonModes];
        NSLog(@"Before starting RunLoop --%@",runLoop.currentMode); [runLoop run]; }} /** subthread task */ - (void)subThreadOpetion {NSLog(@"After starting RunLoop --%@",[NSRunLoop currentRunLoop].currentMode);
    NSLog(@"%@---- child thread task started",[NSThread currentThread]); [NSThread sleepForTimeInterval: 3.0]; NSLog(@"%@---- child thread task terminated",[NSThread currentThread]);
}

@end
Copy the code

First look at the console output:

2016-12-01 17:22:44.396 RunLoopDemo[4733:369202] --(null) 2016-12-01 17:22:49.285 RunLoopDemo[4733:369202] RunLoopDemo[4733:369202] <HLThread: 0x60000027CB40 >{Number = 4, name = HLThread}---- Sub-thread task starts 2016-12-01 17:22:52.359 RunLoopDemo[4733:369202] <HLThread: 0x60000027cb40>{number = 4, Name = HLThread}---- Sub-thread task end 2016-12-01 17:22:55.244 RunLoopDemo[4733:369202] After starting RunLoop --kCFRunLoopDefaultMode The 2016-12-01 17:22:55. 245 RunLoopDemo [4733-369202] < HLThread: 0x60000027CB40 >{Number = 4, name = HLThread}---- Sub-thread task starts 2016-12-01 17:22:58.319 RunLoopDemo[4733:369202] <HLThread: 0x60000027CB40 >{Number = 4, name = HLThread}---- The sub-thread task endsCopy the code

A few things to note:

  • 1. Only [NSRunLoop currentRunLoop] or [NSRunLoop mainRunLoop] can be used to obtain runloops.
  • 2. Even if the RunLoop starts, if the modes in the RunLoop are empty or there is no item in the mode to be executed, the RunLoop returns directly to the current loop and goes to sleep.
  • 3. The tasks of self-created threads are executed in the mode kCFRunLoopDefaultMode.
  • 4. After the child thread is created, it is best to place all tasks in AutoreleasePool.

Pay attention to the first explanation

As stated in the second paragraph of the RunLoop documentation, our application does not need to create the RunLoop itself, but rather to launch the RunLoop at the appropriate time. CFRunLoopGetCurrent(void) CFRunLoopGetMain(void) CFRunLoopGetMain(void) This will help us create a RunLoop (this is done in _CFRunLoopGet0(pthread_t t)).

Pay attention to point two

This, in the sample code can be [runLoop addPort: [NSMachPort port] forMode: NSRunLoopCommonModes]; As you can see, no matter how we click on the view, the console does not get any output because there is no item task in Mode. When encapsulated by NSRunLoop, only two types of Item tasks can be added to mode: NSPort (corresponding to source), NSTimer, if using CFRunLoopRef, you can use the C API to add source, timer, observer to mode.

If you don’t add [runLoop addPort: [NSMachPort port] forMode: NSRunLoopCommonModes]; , we output the information from runloop, we can see:

If we add [runLoop addPort: [NSMachPort port] forMode: NSRunLoopCommonModes]; , and output the information of RunLoop, you can see:

Pay attention to point three

KCFRunLoopDefaultMode = kCFRunLoopDefaultMode We just need to print the currentMode of the RunLoop when executing the task.

Since RunLoop performs tasks by switching between modes and only executing tasks in that mode, currentMode is updated every time a mode is switched. CFRunLoopRunSpecific() : CFRunLoopRunSpecific(); CFRunLoopRunSpecific() : CFRunLoopRunSpecific();

CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); . CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; . Rl ->_currentMode = previousMode;Copy the code

After I tested it, the console output was:

2016-12-02 11:09:47.909 RunLoopDemo[5479:442560] After starting RunLoop --kCFRunLoopDefaultMode 2016-12-02 11:09:47.910 RunLoopDemo[5479:442560] <HLThread: 0x608000270a80>{number = 4, name = HLThread}---- Child thread task starts 2016-12-02 11:09:50.984 RunLoopDemo[5479:442560] <HLThread: 0x608000270A80 >{Number = 4, name = HLThread}---- The sub-thread task endsCopy the code

Note 4: AutoReleasePool’s official documentation says:

If you spawn a secondary thread. You must create your own autorelease pool block as soon as the thread begins executing;  otherwise, your application will leak objects. (See Autorelease Pool Blocks and Threadsfor details.)

Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. 
If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, 
you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); 
otherwise, autoreleased objects accumulate and your memory footprint grows. 
If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.
Copy the code

RunLoop cases in AFNetworking

AFNetworking 2.6.3 or earlier uses NSURLConnection. RunLoop is available in AFURLConnectionOperation.

+ (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;
}
Copy the code

AFNetworking is by calling [NSObject performSelector: onThread:..] Throw the task into the RunLoop of the background thread.

- (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];
}
Copy the code

We also need to consider RunLoop when using NSURLConnection or NSStream, because by default, objects of these two classes are generated to perform tasks in NSDefaultRunLoopMode of the current thread. If it is on the main thread, then when ScrollView and its subviews are rolled, the RunLoop of the main thread switches to UITrackingRunLoopMode, and the NSURLConnection or NSStream callback cannot be executed.

There are two ways to solve this problem:

The first way is to create an NSURLConnection object or an NSStream object, – (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode specifies the RunLoopMode. Note that NSURLConnection must use its initialization constructor – (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable) Id)delegate startImmediately:(BOOL)startImmediately creates the object, setting Mode only takes effect.

The second method is to execute all tasks in the child thread and ensure that the RunLoop of the child thread runs normally (namely, the AFNetworking method above, because the RunLoop of the main thread is switched to UITrackingRunLoopMode, It does not affect which mode tasks are performed by other threads. The computer CPU switches to a different thread to run for a while in each time slice, presenting the effect of multi-threading.

The sample code in this article comes from RunLoopDemo01 in RunLoopDemos