In front of the word
In writing this article, I also referred to and read a lot of relevant articles and examination questions, and found that some articles provided answers, but the answers lacked the context, so I could not understand them basically, let alone remember them. I think I write blog, the premise is their review and study, they should understand and understand, or write out what is the use. Everyone’s goal is for the interview, I try to leave out some of the interview can not be written orally or written things. It’s a little cleaner, and it keeps the logic consistent.
The interview builds the rocket, the entry screws. In order to good offer, also have to fight.
Begin by
I was in the conference room nervously waiting for an interview when A paunchy middle-aged man in a plaid shirt approached me with his resume. I looked at his hair and thought he must be an old-school iOS developer with great skills. Also looked at the cup to write “iOS together into the factory” series, MY knowledge is very profound, the foundation is very firm, do not panic. The heart, nervous and in its throat, pressed down again. Calm calm, not empty good cut, is so confident calm.
When am I ever gonna be that good
There was a kind smile in the interview, and you mention runloops a lot on your resume. Let’s talk about RunLoop. I have a lot of questions. See if you can handle it.
What is RunLoop?
A Runloop is an object that manages events and messages by internally maintaining an event loop. Yes, it’s an object. If you’ve ever used main in C, you know that when main is done, the program ends and exits. But why can iOS apps continue to run after the main function runs? This is where Runloop comes in. This is the most basic use of Runloop. See the following iOS main function:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil.NSStringFromClass([AppDelegate class]));
}
}
Copy the code
Runloop is an object. How do I get it?
-
Foundation [NSRunloop currentRunLoop]; Get the current thread’s RunLoop object [NSRunLoop mainRunLoop]; Get the Runloop object for the main thread
-
Core Foundation CFRunLoopGetCurrent(); Get the RunLoop object CFRunLoopGetMain() for the current thread; Get the Runloop object for the main thread
What is the implementation mechanism of RunLoop?
Inner OS: There is a deeper layer, fortunately I am ready. To make it easier to understand the Runloop mechanism, let’s write some pseudocode to represent the Runloop.
function runloop() {
initialize();
do {
var message = get_next_message();// Get messages from the queue
process_message(message);// Process the message
} while(message ! = quit);// When the quit condition is triggered, Runloop exits } Copy the code
As you can see from the code, Runloop is processed in an “receive message -> wait -> process” loop until the loop ends (such as an incoming quit message). What is the core of RunLoop?Is how it sleeps when there is no message processing and wakes up when there is. This improves CPU resource efficiencyRunLoop, of course, is not a simple while loop. It does not sleep with sleep, which is also CPU intensive. So how does it achieve true hibernation? When there are no messages to be processed, it switches from user mode to kernel mode. When the user mode enters kernel mode, the current thread controller is handed over to kernel mode. The dormant thread is suspended and no longer consumes CPU resources.Notice hereUser mode and kernel modeThose two concepts, and the mach_msg() method. Kernel-state this mechanism relies on the system kernel (Mach in Darwin, the core component of the Apple operating system).
The following is a RunLoop implementation process source code:
// The implementation of RunLoop
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
// select a mode based on modeName
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
// If there is no source/timer/observer in mode, return it directly. if (__CFRunLoopModeIsEmpty(currentMode)) return; /// 1. Notify Observers: RunLoop is about to enter loop. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); /// inner function, enter loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { Boolean sourceHandledThisLoop = NO; int retVal = 0; do { /// 2. Notify Observers: RunLoop is about to trigger a Timer callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); /// 3. notify Observers: RunLoop is about to trigger the Source0 (non-port) callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); // execute the added block __CFRunLoopDoBlocks(runloop, currentMode); /// 4. RunLoop triggers the Source0 (non-port) callback. sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); // execute the added block __CFRunLoopDoBlocks(runloop, currentMode); /// 5. If there is a port based Source1 in ready state, process the Source1 directly and jump to the message. if (__Source0DidDispatchPortLastTime) { Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) if (hasMsg) goto handle_msg; } ///6. Notify Observers: RunLoop threads that are about to enter sleep. if(! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } /// 7. Call mach_msg and wait for the message to accept mach_port. The thread will sleep until it is awakened by one of the following events. /// • A port-based Source event. // a Timer is running out /// • RunLoop's own timeout is up /// • Manually awakened by what other caller __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg } /// 8. Notify Observers: threads of RunLoop have just been awakened. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); // the message is received and processed. handle_msg: 9.1 If a Timer runs out of time, trigger the Timer's callback. if (msg_is_timer) { __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) } 9.2 If there are blocks sent to main_queue, execute the block command. else if (msg_is_dispatch) { __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } // If there is no timeout, mode is not available, and loop is not stopped, continue loop. } while (retVal == 0); } /// 10. Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); } Copy the code
Source code I cut a lot, see the source code in the comments, you can understand a Runloop to run the process. Let’s stick to the core of RunLoop. Since sleep is done by the kernel, what about wake up conditions? Here are the main events that wake up Runloop:
- The port – based Source1 event was received
- Timer Indicates that the Timer is executed
- RunLoop’s own timeout is up
- Manually awakened by another caller
Source1 and source0 for RunLoop
Source1, described above, includes system event capture and port-based interthread communication. What is System event capture? What about port-based interthread communication? In fact, when we tap a finger on the screen, we first generate a system event, which is captured by Source1, and then packaged as Source0 by the Springboard program and distributed to the application for processing. Therefore, we receive touch events inside the App, which is source0, and this relationship is front and back.Source1 is programmatically wrapped to become source0
There are several modes of RunLoop. What does RunLoop set Mode to do?
There are five RunLoop modes. RunLoop only runs in one mode. To switch modes, pause the current mode and restart another mode
- KCFRunLoopDefaultMode, the default running mode of the App, in which the main thread usually runs
- UITrackingRunLoopMode, tracking user interaction events (used for ScrollView to track touch sliding, ensuring interface sliding is not affected by other modes)
- Kcfrunloopcommonmode, pseudo-mode, is not a true operating mode
- UIInitializationRunLoopMode: in the first to enter the first Mode when just start the App, start after the completion of the will no longer be used
- GSEventReceiveRunLoopMode: accept system internal event, is usually not used
RunLoop Setting Mode setting Mode specifies the priority of events in the running Loop. Threads need different modes to run, to respond to different events, to handle different situational patterns. (For example, when you can optimize the TableView, you can set UITrackingRunLoopMode not to perform some operations)
Why is only the main thread Runloop turned on automatically?
If you look at the iOS main function code, the code automatically generates autoreleasepool, which in this case calls runloop. The main function automatically opens the main thread runloop when the app starts.
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil.NSStringFromClass([AppDelegate class]));
}
}
Copy the code
PerformSelector: afterDelay: this method is in the child thread work? Why is that? How to solve it?
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.
UITableViewCell has a UILabel that shows the stopwatch time implemented by NSTimer. Does the label refresh when I scroll the Cell of the TableView? Why is that?
I don’t refresh. When the NSTimer object is added to the main running loop with NSDefaultRunLoopMode, the Mode of TableView (ScrollView) will be switched during the scrolling process, resulting in the NSTimer will not be scheduled any more. When we scroll, we want to not schedule, so we should use the default mode. If you want the timer to run while scrolling, you should use Common Mode. This is implemented by CFRunloopAddTimer(runloop,timer,commonMode). Synchronize the event source timer with the same mode.
How to use Runloop in AFNetworking?
The AFURLConnectionOperation class is built on NSURLConnection and is expected to receive Delegate callbacks in the background thread. AFNetworking creates a separate thread for this purpose and starts a RunLoop in this thread:
+ (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
Before RunLoop starts, there must be at least one Timer/Observer/Source, so AFNetworking creates a new NSMachPort and adds it to it before [RunLoop run]. Normally, the caller needs to hold this NSMachPort (mach_port) and send messages inside the loop through this port from the external thread; But port is added here only to keep the RunLoop from exiting, not to actually send messages.
- (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
When need the background threads execute tasks, AFNetworking by calling [NSObject performSelector: onThread:..] Throw the task into the RunLoop of the background thread.
Explain the role of Runloop in NSTimer
NSTimer is essentially CFRunLoopTimerRef, and you can interchangeably use these two classes. When an NSTimer registers with a RunLoop, the RunLoop registers events for its repeated time points. Such as 10:00, 10:10, 10:20. In order to save resources, RunLoop does not call back to Timer on time when a block occurs. A point in time has been missed and will not be executed for you after the extension. Like waiting for a bus, if there’s a bus at 10:10 and I miss it, I have to wait for the 10:20 bus. The 10:10 is not coming back.
How does Runloop relate to threads?
Runloop and are one-to-one, one thread for each Runloop. The main thread has Runloop by default. As you can see from the data structure, threads are created without runloops by default. You need to manually create runloops for threads.
With threads, why do you have runloops?
The primary function of Runloop is how it sleeps when there is no message processing and wakes up when there is. This improves CPU resource efficiency. Another function of runloop is message processing. You can’t do that with threads alone.
GCD in Runloop?
GCD is returned from the child thread to the main thread, and RunLoop is only triggered in this case. The Source 1 event of the RunLoop is raised.
How to use Runloop in AFNetworking?
The AFURLConnectionOperation class is built on NSURLConnection and is expected to receive Delegate callbacks in the background thread. AFNetworking creates a separate thread for this purpose and starts a RunLoop in this thread:
+ (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
Before RunLoop starts, there must be at least one Timer/Observer/Source, so AFNetworking creates a new NSMachPort and adds it to it before [RunLoop run]. Normally, the caller needs to hold this NSMachPort (mach_port) and send messages inside the loop through this port from the external thread; But port is added here only to keep the RunLoop from exiting, not to actually send messages.
- (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
When need the background threads execute tasks, AFNetworking by calling [NSObject performSelector: onThread:..] Throw the task into the RunLoop of the background thread.
PerformSelector: afterDelay: this method is in the child thread work?
Does not work, child threads have no Runloop by default. 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. AfterDelay requirements like this can be implemented using GCD’s dispatch_after.
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 be failure,
Which is more accurate, CADispalyTimer or Timer
CADisplayLink is more accurate, of course.
IOS devices refresh their screens at a fixed rate, and CADisplayLink is normally called at the end of each refresh with a high degree of accuracy.
If the Runloop is blocked, the trigger time of the NSTimer will be delayed until the next Runloop cycle. So the timing of NSTimer is very unreliable.
CADisplayLink can be used for more specific purposes, such as constant redrawing of UI, such as custom animation engines or rendering of video playback. NSTimer can be used for a much wider range of tasks that require single or cyclic timing. The advantage of CADisplayLink over NSTimer for UI-related animation or display content is that we don’t need to worry too much about the screen refresh rate, because it is already synchronized with the screen refresh rate.
The end of the interview
The young man is very nice. He asked so many questions and answered them all. It makes me lose face. Inside the interviewer (I have to come back tomorrow). There’s more to the interview tomorrow. I thought: Oh my god, there’s more tomorrow! This is going bald, going bald. (Forced introduction for next article lol)
Insist to see the students here, you are all talented, I like it.
Creation is not easy, hello everyone, I am empty cup, the article continues to update every week, if this article is written well, think I have written something useful to you, please give me a thumbs-up 👍 follow ❤ share
White piao is not good, kneeling for a point of attention, plus collection, for sharing
If there are any mistakes in this blog, please comment, thank you very much!
Search for “empty cup One” in wechat or scan the qr code below to read and urge the first time (One or two articles earlier than the blog), you can also get the most organized iOS interview questions, answers and iOS learning materials. This article is available at GitHub github.com/schelling/i… Have included, there is a big factory pilot mind map, also sorted out a lot of my blog documents, welcome Star and perfect, everyone interview can refer to the examination points review, learn and learn together, not to mention.
This article is formatted using MDNICE