Most of the content of this article is from this article, interested can go to see the original

  • See article # for an in-depth understanding of RunLoop

RunLoop is a basic concept in iOS and OSX. It is an Event Loop mechanism. Many functions in iOS are supported by RunLoop. For example, auto-release pools, touch events, screen refreshes, delayed callbacks, process/thread communication, network requests, and so on are essentially runloops handling signals. Based on RunLoop we can screen caton monitoring the similar functionality, anyhow is very strong, if we from the application level to analyze a certain functions may be easily lost in details of the entanglement of go, but if we dig their graves, directly from the RunLoop to analysis, it is easy to grasp its essence, to achieve the effect of get twice the result with half the effort.

OSX/iOS provides two such objects: NSRunLoop and CFRunLoopRef.

CFRunLoopRef is within the CoreFoundation framework and provides apis for pure C functions, all of which are thread-safe.

NSRunLoop is a wrapper based on CFRunLoopRef that provides object-oriented apis, but these apis are not thread-safe.

The code for CFRunLoopRef is open source

This article source code based on CF-1153.18

RunLoop introduction

If we execute a simple script file, it’s likely to be linear, running from top to bottom, but our App can’t do that. We have to keep it alive to receive interactions. Then it’s easy to think of implementing it using while

While (1){// Move the brick... }Copy the code

If we run at full load all the time, this can also meet our needs, but it is a waste of time. RunLoop is actually optimized on the basis of while so that it can do something, sleep when it is not busy, and wake it up when it is busy again. The combination of work and rest can save processor performance.

RunLoop is described in the official documentation

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

Runloops are part of the basic infrastructure associated with threads. RunLoop is an event loop that schedules and processes tasks. The purpose of RunLoop is to keep the thread busy when there is work and to put it to sleep when there is no work.

How does RunLoop work

void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; Do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); . } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

You can see that RunLoop does run in do-while mode, controlling based on some state.

RunLoop and thread relationship

RunLoop CFRunLoopRef CFRunLoopGetMain(void) {... static CFRunLoopRef __main = NULL; // no retain needed if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; } // Get the current thread RunLoop CFRunLoopRef CFRunLoopGetCurrent(void) {... return _CFRunLoopGet0(pthread_self()); }Copy the code
  • pthread_main_thread_np()Get main thread
  • pthread_self()Get current thread
// 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(); } // If no thread has been created, create a mutable dictionary. // The dictionary stores values with thread keys and RunLoop values. // By default create a main thread RunLoop and bind it to the main thread. __CFRunLoops) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); . RunLoop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // Create a RunLoop to store if (! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; }... } return loop; }Copy the code
  • From here we know that there is a global mutable dictionary that is associated with thread keys and RunLoop values.
  • Runloops for child threads do not exist by default and need to be actively fetched and run

The fact that runloops for child threads need to be started manually can cause problems, so let’s take a look at a few minor issues

Problem a

What is the output of the code?

- (void)performSelector{ NSLog(@"performSelector"); } - (void)performSelectorAfterDelay{ NSLog(@"threadFunAfterDelay"); } -(void)thread{ NSLog(@"1"); [self performSelector:@selector(performSelector) onThread:[NSThread currentThread] withObject:nil waitUntilDone:false]; [self performSelector:@selector(performSelectorAfterDelay) withObject:nil afterDelay:0]; [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"scheduledTimerWithTimeInterval"); }]; [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"timerWithTimeInterval"); }]; NSLog(@"2"); } NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(thread) object:nil]; [thread start];Copy the code

Analyze these methods separately

  • performSelector:onThread:withObject:waitUntilDone:

In the document said

This method queues the message on the run loop of the target thread using the default run loop modes

This method sends a message to the message queue of RunLoop, running in default mode

That is, RunLoop needs to start before it can run

  • performSelector:withObject:afterDelay:

This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.

This method creates a timer that sends a message to the Runloop’s default mode task queue when the time is up. If the runloop is not running, it will not succeed

You still need the RunLoop to run

  • scheduledTimerWithTimeInterval:repeats:block:
// Creates and returns a new NSTimer object initialized with // the specified block object and schedules it on the // Current runloop in the default mode. // Run + (NSTimer in the default mode of runloop *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE (macosx (10.12), the ios (10.0), watchos (3.0), tvos (10.0));Copy the code

Note If yes, run it in default mode of RunLoop

  • timerWithTimeInterval:repeats:block:
// Creates and returns a new NSTimer object initialized with the // specified block object. This timer needs to be Scheduled on // a run loop (via -[NSRunLoop addTimer:]) before it will fire. + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer) * Timer))block API_AVAILABLE(MacOSx (10.12), ios(10.0), Watchos (3.0), TVOs (10.0));Copy the code

This method does not send a message to the Runloop, we need to manually add to the Runloop, and then start the Runloop

Therefore, the execution result is:

1
2
Copy the code

The runloop-based functions of the child thread will not be implemented because the RunLoop has not been started

- (void)performSelector{ NSLog(@"performSelector"); } - (void)performSelectorAfterDelay{ NSLog(@"threadFunAfterDelay"); } -(void)thread{// NSRunLoop currentRunLoop run]; NSLog(@"1"); [self performSelector:@selector(performSelector) onThread:[NSThread currentThread] withObject:nil waitUntilDone:false]; [self performSelector:@selector(performSelectorAfterDelay) withObject:nil afterDelay:0]; [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"scheduledTimerWithTimeInterval"); }]; NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"timerWithTimeInterval"); }]; NSLog(@"2"); [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; } NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(thread) object:nil]; [thread start];Copy the code

Do we start RunLoop in the child thread and then execute the method? The execution result

1
2
Copy the code

Still not implemented, this is why ????

The RunLoop has a sleep mechanism, so when we start the RunLoop, it finds nothing to do and then goes to sleep, so we put the start of the RunLoop at the bottom

- (void)performSelector{ NSLog(@"performSelector"); } - (void)performSelectorAfterDelay{ NSLog(@"threadFunAfterDelay"); } -(void)thread{ NSLog(@"1"); [self performSelector:@selector(performSelector) onThread:[NSThread currentThread] withObject:nil waitUntilDone:false]; [self performSelector:@selector(performSelectorAfterDelay) withObject:nil afterDelay:0]; [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"scheduledTimerWithTimeInterval"); }]; NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"timerWithTimeInterval"); }]; NSLog(@"2"); [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; // Add method below is still not executed, } // create thread and execute NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(thread) object:nil]; [thread start];Copy the code

The execution result

1
2
performSelector
threadFunAfterDelay
scheduledTimerWithTimeInterval
timerWithTimeInterval
Copy the code

It is executed, but notice that the RunLoop stops again after it is executed, so if you add a method below it, it will still not execute.

There are two points to note here:

  • The RunLoop of the child thread needs to be started manually
  • 2. RunLoop goes to sleep with nothing to do, so start RunLoop after adding events

RunLoop structure

RunLoop is actually an object that manages things to process, listens for events, and so on.

/* All CF "instances" start with this structure. Never refer to * these fields directly -- they are for CF's use and may  be added * to or removed or change format without warning. Binary * compatibility for uses of this struct is not */ typedef struct __CFRuntimeBase {uintptr_t _cfisa; . } CFRuntimeBase; Struct __CFRunLoop {CFRuntimeBase _base; . pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; . };Copy the code

We see that the CFRunLoop structure is mainly dealing with Modes. Let’s see what Mode is

struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask; . #if USE_MK_TIMER_TOO mach_port_t _timerPort; Boolean _mkTimerArmed; #endif };Copy the code
  • _sources0The store isCFRunLoopSourceRefobject
  • _sources1Stored as wellCFRunLoopSourceRefobject
  • _timersThe store isCFRunLoopTimerRefobject
  • _observersThe store isCFRunLoopObserverRefobject

A RunLoop contains several modes, and each Mode contains several sources, timers, and observers. Each time a RunLoop is started, a Mode must be specified. Each time the Mode of the RunLoop is switched, the RunLoop can only exit the RunLoop and re-enter it.

  • CFRunLoopSourceRefIt’s where things happen. Source has two versions: Source0 and Source1.
    • Source0Contains only one callback (function pointer), which does not actively fire events. To use this, you need to call CFRunLoopSourceSignal(source), mark the source as pending, and then manually call CFRunLoopWakeUp(runloop) to wake up the Runloop to handle the event.
    • Source1Contains amach_portAnd a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source actively wakes up the RunLoop thread, as described below.
  • CFRunLoopTimerRefIt’s a time-based trigger. It’s toll-free bridged with NSTimer, and can be mixed. It contains a length of time and a callback (function pointer). When it is added to the RunLoop, the RunLoop registers the corresponding point in time, and when that point is reached, the RunLoop is woken up to execute that callback.
  • CFRunLoopObserverRefObserver. Each Observer contains a callback (function pointer) that the Observer receives when the state of the RunLoop changes. The following time points can be observed:
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), KCFRunLoopExit = (1UL << 7), // Exit Loop kCFRunLoopAllActivities = 0x0FFFFFFFU //};Copy the code

The Source/Timer/Observer above is collectively referred to as a mode item, and an item can be added to multiple modes at the same time. However, it does not work if an item is repeatedly added to the same mode. If there is no item in a mode, the RunLoop exits without entering the loop.

CommonModes

There is a concept called CommonModes: A Mode can mark itself as the Common property (by adding its ModeName to the RunLoop CommonModes). Whenever the contents of the RunLoop change, the RunLoop automatically synchronizes the Source/Observer/Timer in _commonModeItems to all modes with the “Common” flag.

Application scenario: The main RunLoop has two preset modes: kCFRunLoopDefaultMode and UITrackingRunLoopMode. Both modes have been marked with the “Common” attribute. DefaultMode is the normal state of App, TrackingRunLoopMode is to track the state of ScrollView sliding. When you create a Timer and add DefaultMode, the Timer gets repeated callbacks, but when you slide a TableView, RunLoop switches mode to TrackingRunLoopMode, and the Timer doesn’t get called back. And it doesn’t affect the sliding operation.

Sometimes you need a Timer that gets callbacks in both modes. One way to do this is to add the Timer to both modes. Another option is to add the Timer to the commonModeItems of the top RunLoop. CommonModeItems’ is automatically updated by RunLoop to all modes with the Common attribute.

Question 2

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer running");
    }];

    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    scrollView.backgroundColor = [UIColor redColor];
    scrollView.contentSize = CGSizeMake(100, 10000);
    scrollView.showsVerticalScrollIndicator = YES;
    [self.view addSubview:scrollView];
}
Copy the code

Why does the timer stop when the scrollView is scrolling ???? This is a problem related to the Mode of RunLoop

// Creates and returns a new NSTimer object initialized with the specified block object and schedules it on the current run loop in the default mode. + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats Block (void (^)(NSTimer *timer))block API_AVAILABLE(Macosx (10.12), ios(10.0), watchos(3.0), tVOs (10.0));Copy the code

This method is to add the timer event to the kCFRunLoopDefaultMode. When the scrollView scrolls, the RunLoopMode becomes UITrackingRunLoopMode, so the timer cannot be timed when the scrollView scrolls. We need to add the timer to commonItems to do this

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer running");
    }];

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

    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    scrollView.backgroundColor = [UIColor redColor];
    scrollView.contentSize = CGSizeMake(100, 10000);
    scrollView.showsVerticalScrollIndicator = YES;
    [self.view addSubview:scrollView];
}
Copy the code

At this point, no matter whether scrollView rolls timer or not, it can be timed normally.

Runloop-based functionality

Question 3: When does the automatic release pool release the object?

The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ().

The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Its order is -2147483647, the highest priority, ensuring that the release pool is created before all other callbacks.

The second Observer monitors two events: calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() while waiting (ready to sleep) torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, the lowest priority, ensuring that its release pool occurs after all other callbacks.

The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there are no memory leaks, and developers don’t have to explicitly create pools unless too many temporary variables are generated at once in a task.

So the answer is: Objects in the auto-release pool are released when RunLoop is about to exit or sleep.

Incident response

This was introduced in a previous post # Responder Chain. The touch event is first wrapped by IOKit as an IOHIDEvent object and passed to the SpringBoard process. The SpringBoard process passes the object to the main RunLoop of the active App. In this case, it sends a Source1 event to the main RunLoop. Active activation of the main thread RunLoop, followed by the complex responder chain processing logic.

Interface to update

When the UI elements need to be updated, or manually invoked the UIView/CALayer setNeedsLayout/setNeedsDisplay method, after the UIView/CALayer is marked as pending, and be submitted to a global vessel.

Apple registers an Observer to listen for BeforeWaiting(about to go to sleep) and Exit (about to Exit Loop) events that trigger UI updates in callback functions

Question four: How many kinds of timer please introduce respectively?

  • The first kind of NSTimer

NSTimer is essentially a CFRunLoopTimerRef. It’s toll-free bridged between them. Once an NSTimer is registered with a RunLoop, the RunLoop registers events for its repeated time points. Such as 10:00, 10:10, 10:20. To save resources, RunLoop does not call back this Timer at a very precise point in time. The Timer has a property called Tolerance that indicates the maximum error allowed when the time point is up.

If a point in time is missed, such as when a long task is executed, the callback at that point will also be skipped without delay. Like waiting for a bus, if I’m busy with my phone at 10:10 and I miss the bus at that time, I’ll have to wait for the 10:20 bus.

PerformSelecter deferred execution is implemented based on NSTimer, when calling NSObject PerformSelecter: afterDelay: after its internal will actually create a Timer and add to the current thread RunLoop. So if the current thread does not have a RunLoop, this method will fail. When the performSelector: onThread: when, in fact, it will create a Timer is added to the corresponding thread, in the same way, if the corresponding thread no RunLoop this method will fail.

  • The second CADisplayLink

CADisplayLink is a timer with the same refresh rate as the screen (but the implementation is more complicated, unlike NSTimer, which actually operates on a Source). If a long task is executed between screen refreshes, one frame will be skipped (similar to NSTimer) and the interface will feel stuck.

There are two cases that need to be explained here, the first is when the screen is still, and the second is when the CPU is too busy to complete the calculation in a cycle.

The screen refresh process is simply understood as follows

  1. CPU layout calculation, picture decoding, image drawing and other calculations.
  2. Update App layer tree, package layer tree code and submit data toRender Server
  3. Render ServerandGPUAnd perform a series of operations to display the image

When the CPU completes the calculation, it adds a source to the RunLoop. This source is implemented by sending the layer tree to the Render Server, which triggers the CADisplayLink callback. In the first case, when the screen is still, every frame is displayed the same. In this case, the App sends the layer tree to the Render Server all the time, so CADisplayLink’s callback function can be triggered all the time. The second case is that the CPU does not complete the calculation within a period, resulting in no layer tree sent to the Render Server during this period, then CADisplayLink does not trigger the callback during this period, and the page is also displaying the content of the last frame, which is read from the GPU cache. This is why CADisplayLink is a bad timer. This is just my personal understanding.

  • dispatch_source_t

Dispatch_source_t is more accurate as a timer, because it is implemented by registering callback functions with the kernel. After a certain point in time, the kernel triggers the execution of the callback function, which does not depend on RunLoop, so the RunLoop delay does not affect the accuracy of the timer event execution.

Q5: How to implement a keepalive thread

If we have a large number of background tasks to handle, we can create a resident thread. If any asynchronous tasks are thrown at it, how can we create a resident thread? There are two things to note here. The first thread is an object itself, and we can’t let the object be released. We can create a singleton to implement this.

The key is to keep the child thread RunLoop alive

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // Add a Source1 event, but the Source1 event is also ok, so the thread will sleep there and will not go down, -- the end -- has not printed runLoop addPort: [NSMachPort port] forMode: NSRunLoopCommonModes]; [runLoop run];Copy the code

Why does AFNetworking3.0 no longer require resident threads?

Refer to the article

  • Why are resident threads no longer required after # AFNetworking3.0?
  • # Understand RunLoop in depth

In iOS, the interface for network requests has the following layers from bottom to top:

CFSocket

CFNetwork       ->ASIHttpRequest

NSURLConnection ->AFNetworking

NSURLSession    ->AFNetworking2, Alamofire

  • CFSocket is the lowest level interface and is only responsible for socket communication.
  • CFNetwork is the upper layer encapsulation based on CFSocket and ASIHttpRequest works on this layer.
  • NSURLConnection is a higher level encapsulation based on CFNetwork that provides an object-oriented interface. AFNetworking works at this layer.
  • NSURLSession is a new interface in iOS7, ostensibly alongside NSURLConnection, But the underlying still use the NSURLConnection features (such as com. Apple. NSURLConnectionLoader thread), AFNetworking2 and Alamofire work in this layer.

Usually with NSURLConnection, you pass in a Delegate, and when [Connection start] is called, the Delegate will receive event callbacks. In fact, the start function gets the CurrentRunLoop internally and adds four source0s (sources that need to be triggered manually) to DefaultMode. CFMultiplexerSource handles the various Delegate callbacks, CFHTTPCookieStorage handles the various cookies.

When the network transmission, we can see NSURLConnection created two new thread: com. Apple. NSURLConnectionLoader and com. Apple. CFSocket. Private. The CFSocket thread processes the underlying socket connection. The NSURLConnectionLoader thread uses RunLoop internally to receive events from the underlying socket and notify the upper Delegate via the previously added Source0.

The RunLoop in the NSURLConnectionLoader receives notifications from the underlying CFSocket through some Mach Port-based Source. When received, it sends notifications to Source0 such as CFMultiplexerSource when appropriate, and wakes up the Delegate thread’s RunLoop to handle the notifications. The CFMultiplexerSource performs the actual callback to the Delegate in the Delegate thread’s RunLoop.

We learned that when NSURLConnection sends a network request, the delegate thread needs to be kept alive in response to the NSURLConnection callback

If we use the main thread as a delegate thread
[[NSURLConnection alloc]initWithRequest:request delegate:[NSRunLoop mainRunLoop] startImmediately:YES];
Copy the code

By default, the callback task is added to the defaultMode of the main thread. The response is not processed when the page scrolls, but this is not a big problem. Just add the callback task to commonModes

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:[NSRunLoop mainRunLoop] startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
Copy the code

However, using the main thread as a proxy thread for callbacks affects performance and is not desirable

Each network request opens a thread and keeps alive

This approach is also very high performance consumption, not desirable

Create a keepalive thread

All requests are initiated and called back from this thread.

Each request corresponds to an AFHTTPRequestOperation instance object (operation), and each operation is added to an NSOperationQueue after initialization. By the NSOperationQueue concurrency control, the system will according to the currently available on the number of core and load dynamically adjust the maximum number of concurrent operation, we can also through setMaxConcurrentoperationCount: methods to set the maximum concurrency. Note: Concurrency is not equal to the number of threads opened. It is up to the system to decide how many threads to open.

That is, the operation execution is concurrent and multithreaded.

Why does AFNetworking 2.x require resident threads? Why does AFNetworking not need resident threads?

One of the big pain points of NSURLConnection is that once a request is made, the thread can’t just go away and wait for a callback.

Apple has also understood this pain point and has deprecated NSURLConnection since iOS9.0. The alternative is NSURLSession.

self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
Copy the code

NSURLSession requests no longer require callback to proxy methods in the current thread! We can specify the callback’s delegateQueue so we don’t have to hold the thread alive waiting for the proxy callback method.

What does this line of code do?

self.operationQueue.maxConcurrentOperationCount = 1;
Copy the code

NSOperationQueue is used to receive callbacks in case of threshold access conflicts. And AFN2. X maxConcurrentOperationCount Settings is a network request, so there’s no need to set up.

Refer to the article

  • # Understand RunLoop in depth
  • # iOS: RunLoop
  • do {… } The role of while (0) in the macro definition
  • # Responder Chain
  • # iOS graphics rendering process
  • # Timer CADisplayLink
  • # iOS thread alive