The principle of Runloop and its basic application

One, what is runloop?

Literally means message loop, running loop”. It is not thread, but it is closely related to thread.
generally, a thread can only perform one task at a time, and when it is finished, the thread will quit. For example, in an C language program, the main function exits when the program is executed, so sometimes a while dead loop is needed to keep the program from dying. When we open a IOS application and do nothing, this time it seems that no code is executing, but the program does not quit. IOS can still run programs without tasks, thanks to runloop.

The runloop internal is actually a DO-WHILE loop that listens to various event sources, messages, and manages them and distributes them to threads for execution.

RunLoop is actually an object that manages the events and messages that it needs to handle, and provides an entry function to execute the logic of the above Event Loop. When the thread executes the function, it is always inside the function, “accept the message -&gt, wait for the -&gt, process” in the loop until the loop is finished (for example, incoming quit messages), and the function returns.

IOS program, main.m file will have such a section of code:

Int main (int, argc, char * argv[]) {@autoreleasepool {return UIApplicationMain (argc, argv, nil, NSStringFromClass ([AppDelegate, class]))}

Among them, the UIApplicationMain function starts the runloop internally, so the UIApplicationMain function has never been returned, keeping the program running continuously. The default startup runloop is related to the main thread, and
summarizes the role of runloop:

  • Ensure that the program does not quit
  • Responsible for handling various events (source, timer, observer)
  • If no event occurs, the program is left asleep. This saves cup resources, improves program performance, handles everything, and breaks everything.

API
iOS provides two sets of API to access and use runloop:

  • CFRunLoopRef (CoreFoundation framework), pure C function API, all API are thread safe.
  • The NSRunLoop (Foundation framework) is a CFRunLoopRef based OC package that provides object oriented API, but these API are not thread safe.

Two, runloop and thread

CFRunLoop is managed based on pthread, and pthread and NSThread are one-to-one.

The runloop of the primary thread is created automatically, and the child thread’s runloop is not created by default.
runloop is not created through alloc init. To get the runloop, you can get the primary thread and the current thread runloop (essentially lazy loading) through the two functions CFRunLoopGetMain () and CFRunLoopGetCurrent (). (in NSRunloop, the corresponding is – mainRunloop and – currentRunloop methods)

By Apple source code, the internal realization of these two functions, see the thread and runloop is one-to-one correspondence, this correspondence is saved with a dictionary, key is pthread, value is CFRunLoopRef:

The principle of Runloop and its basic application
source code
The principle of Runloop and its basic application
source code

runloop is created at the first acquisition and then destroyed at the end of the thread. So, if the child thread does not manually obtain the runloop, it will never do so.

Three, RunLoop related classes

In CoreFoundation, there are 5 classes of RunLoop: CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, CFRunLoopTimerRef, CFRunLoopObserverRef,
, and their relationships are as follows:

The principle of Runloop and its basic application
cut deep understanding of RunLoop
  • A RunLoop can have multiple Mode, and each Mode contains several Source/Timer/Observer.
  • Source/Timer/Observer is also called mode item. Mode and item under different mode do not affect each other
  • A item can be added to different mode. But when a item is repeatedly added to the same mode, it doesn’t work. If there is no item in a mode, RunLoop exits. (but it’s not reliable if you just rely on mode item to get runloop out.)

Mode Item

  • Source. CFRunLoopSourceRef
    event source according to the official document of CFRunLoopSourceRef into 3 categories, but the data structure has two types (source0, source1)
    The principle of Runloop and its basic application
    official document, official document classification a screenshot Edition:
    RunLoop only two sources: input source, source of time.
    , and the input source can be divided into: NSPort, custom source, performSelector:OnThread:delay: port based source: related to kernel port. You simply need to create port objects and use the NSPort method to add port objects to run loop. The port object handles, creates, and configures the input source. Corresponding to the source1. custom source: use the CFRunLoopSourceRef type – related function to create custom input sources, such as CFRunLoopSourceCreate?. Generally less than selector: runloop will be automatically cleared after execution (this is the document’s statement, and the actual test is not the same.). Port based sources will not). The
    of Selector Sources said: there are several general
    performxxx performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: / / / / / / main thread thread performSelector: onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: specified for the current thread performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes: / / / / / / cancel, in the current thread, and the above two methods should be tested: cancelPreviousPerformRequestsWithTarget:selector:object: cancelPreviousPerformRequestsWithTarget:
    1. for the two cases of onMainThread and onthread, create the task is source0.
    if the calling thread and the specified thread for the same thread:
    1.1wait parameter is set to YES (the current thread is blocked until selector is executed), then aSelector will run directly on the specified thread, not added to runloop. (in fact, it’s a bit similar to thread deadlocks), the
    1.2wait parameter is NO, the selector source is added to the runloop, and the runloop is not cleared automatically after execution.
    if the calling thread and the specified thread are not the same thread: the selector source is added to the runloop and the runloop is not cleared automatically after execution.
    2., and performSelector:withObject:afterDelay is not source0, but timer, the use is added to runloop, the execution will automatically remove the runloop. There are some methods: – (ID) performSelector: (SEL) aSelector; (ID) – performSelector: (SEL) aSelector withObject: (ID) object; (ID) – performSelector: (SEL) aSelector withObject: (ID) object1 withObject: (ID) object2; they are executed synchronously, and the main thread to process thread can use. Not added to the runloop, but executed directly, so the [self xxx] call is just the compiler and runtime processing differences. Time source: a time based flip-flop with the upper level corresponding to NSTimer. Two, source code version (source0/source1): source0 is not based on port: responsible for App internal events, App responsible for managing the trigger, such as UIEvent, UITouch events. A callback is included that does not automatically trigger events. When using, you need to call CFRunLoopSourceSignal (source) first, mark the Source as pending, and then manually call CFRunLoopWakeUp (runloop) to wake up the RunLoop to handle the event.
    -performSelector:onThread:withObject:waitUntilDone: inModes: creates the source0 task. Source1 based port: contains a mach_port and a callback, you can listen to system ports and messages sent through the kernel and other threads, you can actively wake up runloop, receive and distribute system events. Both Source1 and Timer belong to port event sources. The difference is that all Timer share one port (Timer, Port), and each Source1 has a different corresponding port.
    Source0 is part of the input Source, and Input Source also includes the cuntom custom source, which is manually issued by other threads.
  • Timer
    CFRunLoopTimerRef is a time based trigger, basically NSTimer. Wakes the runloop to execute the callback at the preset time point. Because it is based on RunLoop, it is not real-time (that is, NSTimer is inaccurate). Because RunLoop is only responsible for the news of the source. If the thread is currently handling a heavy task, it may cause Timer to delay or perform less than once.
  • The observer
    CFRunLoopObserverRef viewer listens for the status of runloop. Receive state changes via callbacks. It does not belong to the event source of runloop. Typedef CF_OPTIONS (CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry (1UL = < < 0), is about to enter the Loop kCFRunLoopBeforeTimers / / (1UL = < < 1), is Timer kCFRunLoopBeforeSources = / / (1UL < < 2), is Source kCFRunLoopBeforeWaiting = / / (1UL < < 5). / / kCFRunLoopAfterWaiting = is about to hibernate (1UL < < 6), just wake up from hibernation / / kCFRunLoopExit (1UL = < < 7) / / Loop} will exit;

The Mode exposed management mode item interface has the following:

CFRunLoopAddSource (CFRunLoopRef RL, CFRunLoopSourceRef source, CFStringRef modeName); CFRunLoopAddObserver (CFRunLoopRef RL, CFRunLoopObserverRef observer, CFStringRef modeName); CFRunLoopAddTimer (CFRunLoopRef RL, CFRunLoopTimerRef timer, CFStringRef mode); CFRunLoopRemoveSource (CFRunLoopRef RL, CFRunLoopSourceRef source, CFStringRef modeName); CFRunLoopRemoveObserver (CFRunLoopRef RL, CFRunLoopObserverRef observer, CFStringRef modeName CFRunLoopRemoveTimer (CFRunLoopRef RL); CFRunLoopTimerRef, timer, CFStringRef mode);

Mode

CFRunLoopModeRef. Each time you start RunLoop, you can only specify one of the Mode, which is CurrentMode. To switch Mode, you can only exit Loop and then reassign a Mode entry.

Data structure:

Struct __CFRunLoopMode {CFRuntimeBase _base; pthread_mutex_t _lock; must have the run loop locked / before locking this / //mode CFStringRef _name; Boolean _stopped; char _padding[3]; //source0 CFMutableSetRef _sources0 //source1 source source; CFMutableSetRef _sources1; //observer CFMutableArrayRef _observers //timer source source; CFMutableArrayRef _timers; //mach port mapping to mode, in order to filter the port message runloop the main logic in runloop. CFMutableDictionaryRef _portToV1SourceMap; / / record monitoring needs of all the current mode port, called as a parameter monitoring message function. __CFPortSet _portSet; CFIndex _observerMask #if; USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; set to true by the source / when a timer has fired Boolean _dispatchTimerArmed ENDIF #if USE_MK_TIMER_TOO / / #; use MK timer, use the Mach port, like source1, are dependent on the Mach port mach_port_t _timerPort Boolean _mkTimerArmed; #endif; //timer trigger the ideal time for uint64_t _timerSoftDeadline; real time TSR / //timer / * trigger, the ideal time plus tolerance (deviation) uint64_t _timerHardDeadline; / * TSR * /};

The system default registered 5 mode, the following two are commonly used:
1.kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
2.UITrackingRunLoopMode Scrollview, the default mode, when sliding is in this mode. Ensure that the interface is not affected by other mode when sliding

CFRunLoop external exposure management Mode interface only the following 2:

CFRunLoopAddCommonMode (CFRunLoopRef, runloop, CFStringRef, modeName); CFRunLoopRunInMode (CFStringRef, modeName,...);

For incoming mode name, runLoop creates the corresponding CFRunLoopModeRef automatically if there is no corresponding mode inside the runLoop. Mode can only be added and cannot be deleted

KCFRunLoopCommonModes (NSRunLoopCommonModes)

It is a placeholder for mode, and it is not mode in the true sense.
if you want to open runloop in a thread, it’s wrong to write: [[NSRunLoop, currentRunLoop], runMode:NSRunLoopCommonModes, beforeDate:[NSDate, distantFuture]];

Let’s take a look at the structure of CommonMode in CFRunLoop:

Struct __CFRunLoop {CFMutableSetRef _commonModes; / / Set / / CFMutableSetRef _commonModeItems; Set< Source/Observer/Timer> CFRunLoopModeRef _currentMode; Current Runloop Mode CFMutableSetRef / / _modes; / / Set}..;

There are two things to note:
1., about mode: a mode can be marked as the common attribute (with the CFRunLoopAddCommonMode function), and then it will be stored in _commonModes. The two modekCFRunLoopDefaultMode and UITrackingRunLoopMode that already exist on the main thread are already CommonModes.
2. is about item:_commonModeItems, observer, timer, etc. that are stored in source, which are synchronized to the Modes with Common tags each time the runLoop is running. For example: [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; that is, put timer in commonItem.

Four, runloop implementation

In this section will involve a lot of source code. Source code is relatively long (two hundred or three hundred lines, and later I also wrote notes) If you just want to know about the runloop, the general process can be skipped, and then there’s a chart summary.
of course. The source code, I am not completely read every sentence, and if there is written wrong place, please correct me.

SInt32 CFRunLoopRunSpecific (CFRunLoopRef RL, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {CHECK_FOR_FORK (if (__CFRunLoopIsDeallocating); return kCFRunLoopRunFinished (RL)); __CFRunLoopLock (RL); / / mode name according to find the corresponding mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode (RL, modeName, false); / / if there is no source/timer/observer mode, direct return. If (NULL = = currentMode || __CFRunLoopModeIsEmpty (RL, currentMode, rl-> _currentMode)) {Boolean did = false; if (currentMode) __CFRunLoopModeUnlock (currentMode); __CFRunLoopUnlock (RL); return did kCFRunLoopRunFinished;}? KCFRunLoopRunHandledSource: volatile _per_run_data *previousPerRun (RL) CFRunLoopModeRef = __CFRunLoopPushPerRunData; previousMode = rl-> _currentMode; rl-> _currentMode int32_t = currentMode; result = kCFRunLoopRunFinished; //1. Observers: runloop is about to enter the notice. If (currentMode-> _observerMask & kCFRunLoopEntry) __CFRunLoopDoObservers (RL, currentMode, kCFRunLoopEntry); / / internal function, enter the loop result = __CFRunLoopRun (RL, currentMode, seconds, returnAfterSourceHandled, previousMode); //10. Observers:RunLoop will exit notice. If (currentMode-> _observerMask & kCFRunLoopExit) __CFRunLoopDoObservers (RL, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock (currentMode); __CFRunLoopPopPerRunData (RL, previousPerRun); rl-> _currentMode = previousMode; __CFRunLoopUnlock (RL); return result;}
Static int32_t __CFRunLoopRun (CFRunLoopRef RL, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {uint64_t (startTSR = mach_absolute_time); / / after obtaining the system boot kernel time / / if the current runLoop or runLoopMode to stop words directly return to if (__CFRunLoopIsStopped (RL)) {__CFRunLoopUnsetStopped (RL); return kCFRunLoopRunStopped; else} if (rlm-> _stopped) {rlm-> _stopped = false; return kCFRunLoopRunStopped;} mach_port_name_t dispatchPort = MACH_PORT_NULL; / / to save the column (mainQueue) port. The communication between Mach / Port - thread to determine whether the current thread main thread (the current runloop main thread is runloop), if it is on the distribution of a queue scheduling libdispatchQSafe port Boolean = pthread_main_np (&); & (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY; (& & NULL = previousMode (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY) ||! & & 0 (_CFGetTSD = = __CFTSDKeyIsInGCDMainQ))); / / only in MainRunLoop, will have the following line assignment, or dispatchPort NULL if (libdispatchQSafe & & (CFRunLoopGetMain) (RL = &); & CFSetContainsValue (rl-> _commonModes, rlm-> _name)) = _dispatch_get_main_queue_port_4CF (dispatchPort #if USE_DISPATCH_SOURCE_FOR_TIMERS); / / to the current mode distribution port Ma queue Ch_port_name_t modeQueuePort = MACH_PORT_NULL; if (rlm-> _queue) {modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF (rlm-> _queue); if (! ModeQueuePort) {CRASH ("Unable to get port for run loop mode queue (%d), -1);}} #endif //gcd anything related to the implementation of runloop timeout management. __timeout_context / * struct {dispatch_source_t ds; CFRunLoopRef RL; //runloop uint64_t termTSR; / / timeout point? * /}; / / not very familiar with some API on GCD, but probably to understand is to create the timer by dispatch_source_t; high precision, automatic trigger system, is a system level source dispatch_source_t timeout_timer = NULL; struct = __timeout_context *timeout_context (struct * __timeout_context) malloc (sizeof (*timeout_context)); / / if (seconds context timeout < = 0) {//seconds: runloop set timeout seconds = 0; timeout_context-> termTSR = 0ULL;} else (seconds < if = TIMER_INTERVAL_LIMIT) {// set the timeout in the maximum limit, to create timeout_timer dispatch_queue_t queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT); timeout_timer = dispa Tch_source_create (DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain (timeout_timer); timeout_context-> timeout_context-> DS = timeout_timer; RL = (CFRunLoopRef) CFRetain (RL); timeout_context-> termTSR = startTSR + __CFTimeIntervalToTSR (seconds); dispatch_set_context (timeout_timer, timeout_context); / / source / / gets ownership of context binding dispatch_source_set_event_handler_f (timeout_timer __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f (timeout_timer, __CFRunLoopTimeoutCancel); uint64_t (ns_at = (uint64_t) (__CFTSRToTimeInterval (startTSR) + seconds); dispatch_source_set_timer (timeout_timer * 1000000000ULL), dispatch_time (1, ns_at), DISPATCH_TIME_ FOREVER, 1000ULL); dispatch_resume (timeout_timer);} else {/ / infinite timeout timeout endless seconds = 9999999999; timeout_context-> termTSR = UINT64_MAX;} Boolean didDispatchPortLastTime int32_t = true; retVal = 0; do {uint8_t msg_buffer[3 mach_msg_header_t * 1024]; *msg = NULL; mach_port_t = MACH_PORT_NULL __CFPortSet waitSet = livePort; rlm-> _portSet; //_portSet: the need to monitor all records in the current mode port, called as a parameter monitoring message function. __CFRunLoopUnsetIgnoreWakeUps (RL); / / don't ignore port wake up //2. Observers: notice notice is to deal with timer if (rlm-> _observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopBeforeTimers); //3. Observers: Source0 will notice notice (non port). If (rlm-> _observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopBeforeSources); / / treatment added to runLoop block. (non delayed primary thread) __CFRunLoopDoBlocks (RL, RLM); //4. sourceHandledThisLoop = __CFRunLoopDoSources0 Boolean source0 (RL, RLM, stopAfterHandle); / / block if (sourceHandledThisLoop) {__CFRunLoopDoBlocks (RL, RLM);} //poll indicates that there is no processing of source0 message, if not for false, and true //poll=NO: no source0 and the =0 Boolean poll timeout! || = sourceHandledThisLoop (0ULL = = timeout_context-> termTSR); / / (the latter condition seems inevitable for false), the main thread / / runloop port didDispatchPortLastTime is false (for the first time the execution will not enter judgment, because the didDispatchPortLastTime true if (MACH_PORT_NULL) = dispatchPort & &am! P;! DidDispatchPortLastTime) {MSG = (mach_msg_header_t * msg_buffer); //__CFRunLoopServiceMachPort (a designated port for receiving it can be more than one) news, the last parameter represents when no news whether the port 0 is immediately returned to sleep, not sleep, TIMEOUT_INFINITY sleep on behalf of the main thread to distribute / processing tasks by GCD, these tasks will be the highest priority first if there is //5. Source1, will jump directly to the news. (document that is the detection of source1, but it is the source dispatchPort---gcd port event detection (__CFRunLoopServiceMachPort) if (dispatchPort, & MSG, sizeof (msg_buffer), & livePort, 0)) {goto handle_msg; / / if the port event jump to handle_msg}} didDispatchPortLastTime = false; / / before treatment source0. There is no source1 message, let the thread to sleep. //6. notice Observers: RunLoop thread is about to hibernate (if poll! & (rlm-> _observerMask; & & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopBeforeWaiting); / / mark current runLoop sleep state __CFRunLoopSetSleeping (RL); __CFPortSetInsert (dispatchPort, waitSet); __CFRunLoopModeUnlock (RLM); __CFRunLoopUnlock (RL); #if USE_DISPATCH_SOURCE_FOR_TIMERS / / enter the cycle began to read port information, if the port information is a wake wake current runLoop do if (kCFUseCollectableAllocator) {{objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer) = MSG (MA);} Ch_msg_header_t *) msg_buffer; //7. calls mach_msg to wait for the message to receive mach_port. The thread will go into hibernation until it is awakened by an event below. Just - a port based on the Source event. X - a Timer - RunLoop to the time the timeout / / / / / / - to be what other callers wake up if poll is no / / manual, and waitset with no port message, the thread enters dormancy; otherwise wake up __CFRunLoopServiceMachPort (waitSet, & MSG, sizeof (msg_buffer), & livePort, poll? 0: TIMEOUT_INFINITY); //livePort modeQueuePort, is represented by the current mode queue port if (modeQueuePort! = MACH_PORT_NULL & & livePort = = modeQueuePort) {/ / don't understand while (_dispatch_runloop_root_queue_perform_4CF (rlm-> _queue)); / / that Timer activates jump out of the two cycle to cycle a cycle if (rlm-> _timerFired) {_timerFired = rlm-> false; break;} else {if (MSG & & MSG! = (mach_msg_header_t *) msg_buffer) free (MSG);}} / / if livePort is not modeQueuePort, runLoop wakes up. This represents the livePort given by __CFRunLoopServiceMachPort with only two possibilities: one is MACH_PORT_NULL, and the other is the port of the really acquired message. Else ahead and leave {/ / Go the inner loop. break while;}} (1); #else if (kCFUseCollectableAllocator) {objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer));} (MSG = mach_msg_header_t * msg_buffer (__CFRunLoopServiceMachPort); waitSet, & MSG, sizeof (msg_buffer). & livePort, poll; #endif: 0? TIMEOUT_INFINITY) __CFRunLoopLock (RL); __CFRunLoopModeLock (RLM); __CFPortSetRemove (dispatchPort, waitSet); / / ignore port wake up __CFRunLoopSetIgnoreWakeUps (RL); / / user callouts now OK again __CFRunLoopUnsetSleeping (RL); //8. Observers:RunLoop thread just notice Wake up. If (poll &! (rlm-> _observerMask; & & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopAfterWaiting); / / handle message ports handle_msg:; __CFRunLoopSetIgnoreWakeUps (RL); / / set at runLoop to ignore (ensure thread safety) port wake / / the 9. pending event processing if (MACH_PORT_NULL = livePort) {// what don't do (or timeout..? (); / / CFRUNLOOP_WAKEUP_FOR_NOTHING a handle nothing //struct __CFRunLoop: __CFPort _wakeUpPort}, for the current manual runloop thread wake-up, completed by calling the CFRunLoopWakeUp, CFRunLoopWakeUp will send a message to _wakeUpPort else if (livePort = = rl-> _wakeUpPort) {// only external calls CFRunLoopWakeUp will enter the branch, it is the external interface of CFRUNLOOP_WAKEUP_FOR_WAKEUP runLoop (active wake); / / do / / nothing on Mac OS wake #if else if} USE_DISPATCH_SOURCE_FOR_TIMERS (modeQueuePort! = MACH_PORT_NULL & & livePort = = modeQueuePort) {(CFRUNLOOP_WAKEUP_FOR_TIMER); if (__CFRunLoopDoTimers! (RL, RLM, mach_absolute_time

Inside the function, a DO-WHILE loop is basically made. The thread has been in the waiting message -&gt, accepting the -&gt, processing loop until the loop is over.

According to Apple’s official documentation, the above code is summed up in the following ten steps:

The principle of Runloop and its basic application
The principle of Runloop and its basic application
process induction

The
main thread runloop handles the GCD port event source. The fifth step above, from the source point of view, is to check the GCD port event.. here, there is still some doubt.

Sleep and wake up on runloop

The most important thing for runloop is to ensure that threads hibernate when no messages are available, and wake up when messages are available to improve program performance. The runloop mechanism relies on the kernel of the system (Mach, the core component of the Apple operating system, Darwin).

Mach provides basic services such as processor scheduling and IPC (interprocess communication). In Mach, processes, threads, and virtual memory are called objects”. Mach objects can only communicate between objects by message passing. Messages are passed between two ports (port), which is the core of Mach’s IPC (interprocess communication).

Runloop essence: mach_msg ()
Runloop receives and sends messages via the mach_msg () function. Its essence is to call the function mach_msg_trap (), which is equivalent to a system call that triggers kernel state switching. When you call mach_msg_trap () in user mode, it triggers the trap mechanism and switches to the kernel state. The kernel’s mach_msg () function implements the actual work.

The principle of Runloop and its basic application
cut deep understanding of runloop

Runloop uses the mach_msg () function to receive messages, and if the kernel does not send port messages, the kernel places the thread in wait state mach_msg_trap () (the current thread is blocked). If a message returns (the kernel opens a new thread, returns the message), the message type is determined, the event is processed, and the callback is passed through the callback of the modeItem. (summary: port source1, based on the monitor port, port news, trigger callback; while source0, manually marked for processing and manual wake runloop) about Mach
message transmission mechanism, can see this article

Now go back to runloop’s discussion of sleep and arousal.
we focus on the seventh step in the ten steps above:

Enter the cycle began to read / port information, if the port information is a wake wake current runLoop do if (kCFUseCollectableAllocator) {{objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer));} MSG = (mach_msg_header_t * msg_buffer); //7. mach_msg call waiting to receive mach_port messages. The thread will go into hibernation until it is awakened by an event below. Just - a port based on the Source event. X - a Timer - RunLoop to the time the timeout / / / / / / - to be what other callers wake up if poll is no / / manual, and waitset with no port message, the thread enters dormancy; otherwise wake up __CFRunLoopServiceMachPort (waitSet, & MSG, sizeof (msg_buffer), & livePort, poll? 0: TIMEOUT_INFINITY); //livePort modeQueuePort, is represented by the current mode queue port if (modeQueuePort! = MACH_PORT_NULL & & livePort = = modeQueuePort) {/ / don't understand while (_dispatch_runloop_root_queue_perform_4CF (rlm-> _queue)); / / that Timer activates jump out of the two cycle to cycle a cycle (if rlm-> _timerFired) {_timerFired = rlm-> false; br Eak;} else {if (MSG & & MSG! = (mach_msg_header_t *) msg_buffer) free (MSG);}} / / if livePort is not modeQueuePort, runLoop wakes up. This represents the livePort given by __CFRunLoopServiceMachPort with only two possibilities: one is MACH_PORT_NULL, and the other is the port of the really acquired message. Else ahead and leave {/ / Go the inner loop. break while;}} (1);

The sleep state of runloop is also a DO-WHILE loop. The core processing function __CFRunLoopServiceMachPort:

What makes runloop / / resting place static Boolean __CFRunLoopServiceMachPort (mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {Boolean originalBuffer = true; kern_return_t = RET (KERN_SUCCESS; for; In; that) {/ * sleep of death what nightmares may come... / mach_msg_header_t *msg = (mach_msg_header_t * *buffer); msg-> msgh_bits = 0; msgh_local_port = msg-> port; msg-> msgh_remote_port = MACH_PORT_NULL; msgh_size = msg-> buffer_size; msg-> msgh_id = 0; / / according to the value of the timerout parameter to determine where the runloop is dormant or continue to query (poll) if (TIMEOUT_INFINITY = = timeout) {CFRU NLOOP_SLEEP (else);} {(CFRUNLOOP_POLL)}; / / call mach_msg () function to receive messages (MSG, RET = mach_msg (MACH_RCV_MSG|MACH_RCV_LARGE| (TIMEOUT_INFINITY! = timeout)? MACH_RCV_TIMEOUT: 0) |MACH_RCV_TRAILER_TYPE (MACH_MSG_TRAILER_FORMAT_0) |MACH_RCV_TRAILER_ELEMENTS (MACH_RCV_TRAILER_AV), 0, msg-> msgh_size, port, timeout, MACH_PORT_NULL; CFRUNLOOP_WAKEUP (RET) wake up if (MACH_MSG_SUCCESS); / / = = RET) {// successfully obtained the message *livePort = MSG? Msg-> msgh_local_port: MACH_PORT_NULL; return true;} if (MACH_RCV_TIMED_OUT = RET) {// get message timeout if (! OriginalBuffer) free (MSG); *buffer = NULL; *livePort = MACH_PORT_NULL; retur N false;} if (MACH_RCV_TOO_LARGE! = RET) buffer_size = round_msg (break; msg-> msgh_size + MAX_TRAILER_SIZE); if (originalBuffer) *buffer = NULL; originalBuffer = false; *buffer = realloc (*buffer, buffer_size); HALT return false;}};

__CFRunLoopServiceMachPort is used to accept the designated port (one or a plurality of) news, the last parameter represents when no news whether the port 0 is not immediately return to dormancy, dormancy, dormancy TIMEOUT_INFINITY.
then call mach_msg () function to receive messages (if there is no port message, the kernel thread (mach_msg_trap) placed in a waiting state, so here, nothing to do runloop if it sleeps), assigned to livePort according to the received message results in a successful access to news, according to the value for the msg-&gt msgh_local_port; or MACH_PORT_NULL, and another message will get timeout value for the MACH_PORT_NULL.

Then the cycle at DO-WHILE, on livePort modeQueuePort (the mode queue port) a few lines of code that if do not understand, if there is to know that a few lines of code is doing what the students please contact me. If livePort is not modeQueuePort, it is the message port or MACH_PORT_NULL that wakes up runloop.

Then, in the ninth step, you’ll see how the livePort is handled differently:

If (MACH_PORT_NULL = livePort) {// (what do not timeout or CFRUNLOOP_WAKEUP_FOR_NOTHING..? (); / / a} handle nothing //struct __CFRunLoop: __CFPort _wakeUpPort, the current runloop for manual thread wake-up, completed by calling the CFRunLoopWakeUp, CFRunLoopWakeUp will send a message to _wakeUpPort else if (livePort = = rl-> _wakeUpPort {//) only external calls CFRunLoopWakeUp will enter the branch, it is the external interface of CFRUNLOOP_WAKEUP_FOR_WAKEUP runLoop (active wake); / / #if USE_DISPATCH_SOURCE_FOR_TIMERS else if wake} (modeQueuePort! = MACH_PORT_NULL & & livePort = = modeQueuePort) {(CFRUNLOOP_WAKEUP_FOR_TIMER);...... #endif #if USE_MK_TIMER_TOO} timer / / for wake up. 9.1 if a Timer arrives at time, the Timer callback is triggered. Else (if rlm-> _timerPort & &! = MACH_PORT_NULL; livePort = rlm-> _timerPort) {(CFRUNLOOP_WAKEUP_FOR_TIMER)}; else if... #endif... (livePort = = dispatchPort) {CFRUNLOOP_WAKEUP_FOR_DISPATCH (//9.2); wake processing asynchronous method. Handle GCD dispatch to main_queue block, execute block. Else} {/ / 9.3 Source1 (based on port) (CFRUNLOOP_WAKEUP_FOR_SOURCE);... If... (RLS) {__CFRunLoopDoSource1 (RL, RLM, RLS, MSG, msg-> msgh_size, & reply) || sourceHandledThisLoop;...}...

Runloop’s CallOut

Runloop callback, is generally a function through a long name, such as the following six points: interrupt when debugging can be seen in the
function call stack (in other words your code actually is finally through the following functions for the call, even if you monitor Observer will first call the following then indirectly inform you):

Static void (__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__); static void (__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__); static void (__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__); static void (__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__); static void (__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__); static void (__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__);

Runloop exit conditions

One time execution; app exits; thread closes; setting maximum time expires; modeItem is empty (in fact, observer is not a source, so even observer will return);

Five, how to use

Open and close interfaces

1.CFRunLoopRef (CoreFoundation framework)

CFRunLoopRef void (CFRunLoopRun) / / run; / / run CFRunLoopRef: parameters for the operation mode, and whether the time in dealing with Input Source after the exit sign, the return value is exit SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled); / / stop running CFRunLoopRef void CFRunLoopStop (CFRunLoopRef RL);

One point one

Void, CFRunLoopRun ();
Source void CFRunLoopRun (void) {/ * DOES CALLOUT * / int32_t result; do {result = CFRunLoopRunSpecific (CFRunLoopGetCurrent) (kCFRunLoopDefaultMode, 1.0e10, false, CHECK_FOR_FORK); (while);} (kCFRunLoopRunStopped! = result & amp; & kCFRunLoopRunFinished = result;}!)
  • Run in NSDefaultRunLoopMode mode. Until the call CFRunLoopStop () is forced to stop (kCFRunLoopRunStopped), or source/timer/, none is left (kCFRunLoopRunFinished). That is, the source code int32_t, __CFRunLoopRun () (that is, the previous few hundred lines):
    , The principle of Runloop and its basic application,

One point two

SInt32, CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);
SInt32 CFRunLoopRunInMode (CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {/ * DOES CALLOUT * CHECK_FOR_FORK (return); CFRunLoopRunSpecific (CFRunLoopGetCurrent), modeName (returnAfterSourceHandled, seconds);}
  • Parameter. First: runloop running mode. Second: run time (timeout). The third one: do you want runloop to exit after you have finished processing source (source0/source1)?.
  • Next to the two interface about NSRunloop: – (void) runUntilDate: (NSDate * limitDate); and (BOOL) – runMode: (NSString * mode) beforeDate: (NSDate * limitDate); this is the package and function based on the (guess).
  • From the source point of view, as with the last function, all use CFRunLoopRunSpecific (), so eventually call int32_t __CFRunLoopRun (). Therefore, you can use CFRunLoopStop () to exit runloop.
  • Return value. Is the cause of runloop’s exit. Enum {kCFRunLoopRunFinished = 1, kCFRunLoopRunStopped = 2, kCFRunLoopRunTimedOut = 3, kCFRunLoopRunHandledSource = 4};

One point three

Void CFRunLoopStop (CFRunLoopRef RL);

You can stop the runloop that is started by the two functions above.

2.NSRunloop (Foundation framework)
/ / running mode is the default NSDefaultRunLoopMode mode, no timeout - run (void); / / operation mode is the default NSDefaultRunLoopMode model parameters for the transport time - (void) runUntilDate: (NSDate * limitDate); / / can set to runloop mode of operation, time period - (BOOL) runMode: (NSString *) mode beforeDate: (NSDate * limitDate);

Two point one

- - (void) run;
  • Once the loop is turned on, the code cannot be shut down and the code cannot be executed. Mentioned in the API document: if there is no input source and timing source added to runloop, runloop immediately exit, or through frequent calls the -runMode:beforeDate: method to make runloop run in NSDefaultRunLoopMode mode.
    but..! Manually removing an input source, timer does not guarantee that runloop will quit because the system might add some source itself to handle the event. (the next two are also.)
  • You cannot exit with CFRunLoopStop (runloopRef). This method of starting runloop is not conducive to control and is not recommended.

Two point two

- - (void) runUntilDate: (NSDate *) limitDate;
  • Run in NSDefaultRunLoopMode mode with timeout limit. In fact, it constantly calls the -runMode:beforeDate: method to let runloop run in NSDefaultRunLoopMode mode until the arrival timeout.
    calling CFRunLoopStop (runloopRef) cannot stop the operation of Run Loop. Why? Because this method will only end the call of the current -runMode:beforeDate:, and then the -runMode:beforeDate: will continue to call… Until timeout.
  • Corresponding to CFRunLoopRunInMode (kCFRunLoopDefaultMode, limiteDate, false)

Two point three

- - (BOOL) runMode: (NSString *), mode, beforeDate: (NSDate *) limitDate;
  • The run mode can be specified compared to the previous method.
  • Corresponding to the CFRunLoopRunInMode (mode, limiteDate, true) method, execute only once and quit after execution.
  • You can exit runloop using CFRunLoopStop (runloopRef).
  • The API document states that runloop exits after the first input source (non timer) is processed or arrives at limitDate. – (void) viewDidLoad viewDidLoad] self.view.backgroundColor {[super; [UIColor = whiteColor]; UIButton = *btn [[UIButton alloc]initWithFrame:CGRectMake (0, 80, 50, 50)]; btn.backgroundColor = [UIColor redColor]; [self.view addSubview: btn]; [btn addTarget:self action:@selector (clicked) forControlEvents:UIControlEventTouchUpInside]; NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector (threadMethod) object:nil]; self.thread = thread; [self.thread start]}; – (void) threadMethod{@autoreleasepool [[NSRunLoop currentRunLoop]addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode] {[[NSRunLoop currentRunLoop]; runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog (@ t Hread end “);}} – (void) clicked{[self performSelector:@selector (doSomething) onThread:self.thread withObject:nil waitUntilDone:NO];}} – (void) doSomething{NSLog (@ doSomething); 2017-06-04 21:05:55.316 Test[61381: 5557174] doSomething} 2017-06-04 21:05:55.317 Test[61381:5557174] thread end runloop [NSMachPort port] to add the source of keeping alive, after the execution of perform selector (recall. This will add a source to runloop out of the runloop). From here can explain the source code:
    review on the hundreds of lines of source code, we can see the runloop DO-WHILE loop is composed of a variable retVal to control values, i.e. the last few lines: / / code into the loop processed event returns said parameters. If (sourceHandledThisLoop & & stopAfterHandle) {retVal = kCFRunLoopRunHandledSource;} / / beyond the incoming timeout parameter marker else (timeout_context-&gt if; termTSR < mach_absolute_time (retVal)) {} = kCFRunLoopRunTimedOut; / / was forced to stop the external caller (__CFRunLoopIsStopped else if (RL)) {__CFRunLoopUnsetStopped (RL; retVal) else = kCFRunLoopRunStopped;} if (rlm-> _stopped) {rlm-> _stopped = false; retVal = kCFRunLoopRunStopped;} / / source/timer/observer a no else if (__CFRunLoopModeIsEmpty (RL, RLM, previousMode)) {retVal = kCFRunLoopRunFinished;} / / if no overtime, no mode, loop could not be stopped Stop, then go on, loop. While (retVal = = 0}); – (BOOL) runMode: (NSString * mode) beforeDate: (NSDate * limitDate), corresponding to CFRunLoopRunInMode; (mode, limiteDate, true). Therefore, if (sourceHandledThisLoop, & & stopAfterHandle) {retVal = kCFRunLoopRunHandledSource;} the stopAfterHandle value is true. Because here the perform source is source0, so that //4. Boolean sourceHandledThisLoop = __CFRunLoopDoSources0 source0 (RL, RLM, stopAfterHandle); the sourceHandledThisLoop value for the true here.
    if the source1 source is the same analysis, the runloop exits after the first input source is processed.

Some basic common problems

The basic use of three step
add events to runloop: 1. create event (source) 2. specifies the event (source) operation mode in runloop, and added to the runloop in 3., and in runloop mode, the event (source) operation

1.ModeItem will be processed by runloop under the corresponding Mode

The following code is executed in the main thread:

- (void) viewDidLoad {[super viewDidLoad]; / / timer / / 1: create parameters parameters of time interval of 2: 3: 4 parameter method parameters: custom parameters 5: whether repeat NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector userInfo:nil repeats:YES] (task); / / the timing source added to the current thread under the message loop [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];} - (void) task{/ / NSLog output current cycle model (@ "% @", [[NSRunLoop currentRunLoop]currentMode] (@ NSLog); "task is running");}

Set timer to run in NSDefaultRunLoopMode mode. At first, nothing was done; the timer was running; when the screen interface was rolling, the timer stopped running. This is because: without dragging the interface is kCFRunLoopDefaultMode, dragging the interface is UITrackingRunLoopMode mode, and the settings do not match the pattern, it can not run.

If you need to be able to run in two Mode, one way to do this is to add the Timer to the two Mode. Another approach is to add Timer to the commonItem of the top-level RunLoop, which is synchronized to the Modes with the Common tag each time the runLoop is running.
, [[NSRunLoop, currentRunLoop], addTimer:timer, forMode:NSRunLoopCommonModes];

Ps:[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector (run) userInfo:nil repeats:YES]; equivalent: above viewDidLoad inside the two code, automatically added to the current runloop and default in mode mode. If you want to change the schema, call the addTimer:forMode: method once, [[NSRunLoop, currentRunLoop], addTimer:timer, forMode:NSRunLoopCommonModes];

The runloop of the 2. child thread should be turned on manually

Add the source to the runloop of the specified thread: performSelector…

- (void) viewDidLoad {[super viewDidLoad]; / / create sub thread NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector (task2) object:nil]; [thread start]; / / to add the specified thread message loop source / / 1: method parameter 2: parameter specifies the thread parameters 3: object parameter 4: wait for execution (addtask) [self performSelector:@selector onThread:thread withObject:nil waitUntilDone:NO];} - (void) task2{NSLog (@ task2 is running, [NSThread currentThread]% @ ");} - (void) addtask{NSLog (@" addtask is running ");}

The method performSelector in addtask does not execute because the thread method is executed instantaneously, the thread is finished, the thread is not returned, and the thread does not listen to see if there is a way to continue to perform it.
each thread has its own runLoop, which we can obtain by means of [NSRunLoop, currentRunLoop], or CFRunLoopGetCurrent (). However, only the runLoop of the main thread is started by default, and the runloop of other threads needs to be started manually.

You need to open runloop in the child thread:

- (void) task2{NSLog (@ task2 is running, [NSThread currentThread]% @ "); / / A / / [[NSRunLoop currentRunLoop]run]; / / NSLog (@ over); / / not print, because has been in circulation, there is no exit / / two / / [[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; / / NSLog (@ over); / / three, apple / / BOOL shouldKeepRunning = YES; *theRL = NSRunLoop / / global [NSRunLoop currentRunLoop] while (shouldKeepRunning; & & [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate; / / distantFuture]]) by switching NSLog global variable control runloop (@ over); / / this sentence is not performed}
The principle of Runloop and its basic application
over will not be printed out

if you want runloop to terminate, the official recommendation: – (BOOL) runMode: (NSString *), mode, beforeDate: (NSDate *) limitDate, official example:

BOOL shouldKeepRunning = YES; *theRL = [NSRunLoop NSRunLoop / / global variable currentRunLoop] (shouldKeepRunning; while & & [theRL; runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

Runloop switches are controlled by global variables.

3., add modeItem to runloop, or else runloop exits

If there is no mode in item, runloop exits. Here’s a slight modification of the code above 2: put the performSelector method in touchesBegan

- (void) viewDidLoad {[super viewDidLoad]; / / create sub thread self.thread = [[NSThread alloc] initWithTarget:self selector:@selector (task2) object:nil]; [self.thread start]; / / to add the specified thread message loop source / / 1: method parameter 2: parameter specifies the thread parameters 3: object parameters 4: / / [self performSelector:@selector wait for completion (addtask) onThread:thread withObject:nil waitUntilDone:NO];} - (void) task2{NSLog (@ task2 is, [NSThread running% @ "currentThread]"); / / NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode] while (YES; & & [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]] (@ NSLog); "over");} - (void) addtas K{NSLog (@ "addtask is running");} - (void) touchesBegan: (NSSet * touches) withEvent: (UIEvent * event) {[self performSelector:@selector (addtask) onThread:self.thread withObject:nil waitUntilDone:NO];}
The principle of Runloop and its basic application

runs, “over” is printed out, which proves that runloop exits. Click the screen, and the addtask method does not execute.
this method creates the Source 0 task and distributes it to the RunLoop of the specified thread.

- - (void) performSelector: (SEL), aSelector, onThread: (NSThread *), THR, withObject: (ID), Arg, waitUntilDone: (BOOL), wait, modes: (NSArray *) array;

Then why is it different from the result before the code changes? The personal understanding is that, in 2., the source0 event source has been added before the child thread runloop opens. And now the revised code, the event source added separate, first off Zi Xiancheng, and when the user clicks on the screen will add an event source; for the thread, although runloop is created and opened, but because there has been no mode item, so runloop immediately quit.

How do you fix that? A simple, tough way to add a mode item to runloop before it opens:

- (void) task2{NSLog (@ task2 is running, [NSThread currentThread]% @ "); / / add a port of the currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode] [[NSRunLoop while (YES; & & [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); NSLog (@ over);}

If so, change:

- (void) task2{NSLog (@ task2 is running, [NSThread currentThread]% @ "); while (1) [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate {distantFuture]]}};

There’s no problem running it, and the addtask method executes when the user clicks on the screen. Still, runloop didn’t have a modeitem problem at first, and this caused runloop to create, start, and exit in the while loop until the screen clicked and the event source was added.

Ps:

When the NSObject of the performSelecter:afterDelay: is called, it actually creates a Timer inside it and adds it to the RunLoop of the current thread. So, if the current thread has no RunLoop, this method will fail.

The 4.observer setting listens for the runloop status
Create a observer CFRunLoopObserverRef observer / / (CFAllocatorGetDefault) = CFRunLoopObserverCreateWithHandler (kCFRunLoopAllActivities, YES, 0, (CFRunLoopObserverRef, observer, CFRunLoopActivity ^ activity) {NSLog (@ "- listen to the RunLoop state change ---%zd", activity);}); / / add state observer: CFRunLoopAddObserver RunLoop monitoring (CFRunLoopGetCurrent), observer (kCFRunLoopDefaultMode); / / release of Observer CFRelease (observer);

Create observer, use CFRunLoopObserverCreateWithHandler function, parameter 1: allocate memory parameter 2: to listen to which runloop status of the tag parameter 3: whether to repeat. This observer is called only once, or runloop, each loop is adjusted parameters 4: priority, the general pass 0, parameter 5: callback

Memory management for 5.CF

1., all with Create, Copy, Retain and other words function, created objects, need to do in the last release
2.release function: CFRelease (object);

The above code is put into VC’s viewDidLoad, and nothing else is added. The console print will end up at 32:

The principle of Runloop and its basic application
button before clicking

This is kCFRunLoopBeforeWaiting =
(1UL < < 5), / / runloop will enter dormancy. Then click on a button on the screen, and then look at the console output, you know, runloop first wake up, and then deal with a variety of events (including click events), and finally back to sleep,

After the The principle of Runloop and its basic application
button is clicked, there are many lines that are not printed yet, but end up in sleep 32

Other runloop related

When will the autoreleasepool automatic release pool be released?

First code, run in MRC environment:

Person.h @interface Person: NSObject @property (nonatomic, copy) NSString *name; (instancetype) + personInitWithName: (NSString * name); @end Person.m @implementation Person (instancetype) + personInitWithName: (NSString *) name{/ / MRC memory management principle: who created Person *person = [[[Person alloc]init]autorelease] who released the release of person.name = name; / / autorelease pool extension return; person;} @end vc.m #import "Person.h" @property (nonatomic, assign) - (void) Person *person1; viewDidLoad{self.person1 [Person personInitWithName:@........ = "name1"]; NSLog (@ "% @", self.person1.name);} - (IBAction) clicked: (ID sender) {NSLog (@ person.name:%@ ", self.person1.name); //MRC report wild pointer error} click the button

MRC memory management principles: who created who released. Autorelease is used to release the person object in the code. When the auto release pool is released, release is sent to all the objects in the pool, and the person object is released.

Run results: after clicking the button, the wild pointer is wrong, which means that the object has been released. In this regard, you can release the auto release pool from the object and have been released. Why was it released at this time?

Take a look at this picture below:

The principle of Runloop and its basic application

iPhone applications run -> touch event -> cocoaTouch to create events, generating the event object -> cocoaTouch create autorelease pool -> application of event processing (that is, some of our own code, and may have some intermediate and temporary objects, these objects placed in the auto release pool) -> event processing is completed the auto release release
official documents: The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. in the main thread to create the event loop autorelease pool, when the event the end of the cycle of release autorelease pool, resulting in the process of handling events Some automatically released objects will be released

Now look back at the code above: think of the viewDidLoad as an event, create the auto release pool at the start of viewDidLoad, automatically release the pool release at the end, and release the person object. Then, when you click on the event, you create a new automatic release pool, because the person object has been freed, and then the access to the wild pointer error.

In fact, Apple has registered two Observer in the main thread RunLoop. The first Observer monitoring event is about to enter Loop, which has the highest priority, ensuring that the creation of the release pool occurs before all other callbacks.
second Observer monitors two events: when you are ready to hibernate, release the old pool and create a new pool; release the automatic release pool when you are about to exit Loop. The lowest priority ensures that its release pool occurs after all other callbacks.

In the code, the viewDidLoad code is executed and runloop is asleep, when the old pool is freed and the person object is freed. Click on the event to wake up runloop, and then the new pool access to the released object, the wild pointer wrong.

The code executed on the main thread is usually written in such events as callbacks and Timer callbacks. These callbacks are surrounded by the RunLoop created by AutoreleasePool, so there is no memory leak, and developers do not have to display the creation of Pool.

When will the automatic release pool be used?

  • When you open a child thread, create an auto release pool yourself, otherwise memory leaks may occur. When
    uses NSThread to do more thread development, you need to manually add an automatic release pool in the thread scheduling method. For example: NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector (execute) object:nil]; [thread start];… -… (void) execute{@autoreleasepool{NSTimer *timer [[NSRunLoop currentRunLoop]addTimer:timer forMode:… NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop]run];}}
  • Many temporary objects are created in the loop, and automatic release pools are used inside the loop to reduce high memory footprint. For example: for (int i = 0; I < largeNumber; ++i) {NSString *str = @ “Hello World”; STR [str stringByAppendingFormat:@ = “%d”, i]; STR = [str uppercaseString]; NSLog (@ “% @”, STR);} run the code, will see memory usage has been in the growth, because the temporary object in the circulation is not released.
    improvements: for (int = I = 0; I < largeNumber; ++i); {@autoreleasepool{… To execute code}}

More about the apple RunLoop with the function such as incident response, gesture recognition, interface update, timer, and GCD performSelector I have no in-depth study, here is not carried out, if used after another to write notes.

Article:
official document
classic runloop technique:
RunLoop
runloop source code analysis (runloop source code to read several important function): analysis of
Runloop
Mach Mach source:
message transmission mechanism
interprocess communication (OSX/iOS)

runloop: NSRunLoop: other nested principle explain — with no blind spot
Cocoa: the in-depth study of the relationship between NSOperationQueue, NSRunLoop and
analysis of RunLoop source code security thread Xiangjie
IOS development GCD, Autorelease Pool and RunLoop