What is a RunLoop?
So what is a RunLoop
“Running laps” is just right
-
One of the things that keeps iOS apps responsive and running is that they have an Event Loop.
-
Event loop mechanism, that is, the thread can respond to and process the event at any time, this mechanism requires that the thread can not exit, and the efficient completion of event scheduling and processing.
-
The event loop mechanism is called a RunLoop
-
==RunLoop is actually an object ==, which is used in the loop to handle various events (such as touch events, UI refresh times, timer times, Selector events, etc.) to keep the program running and to go to sleep when no events are being handled. Thus saving CPU resources and improving program performance.
By default, the main thread is RunLoop
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code
The UIApplicationMain function internally helps us turn on the RunLoop for the main thread. UIApplicationMain has an infinite loop of code inside it.
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while(message ! = quit); }Copy the code
The program will always run in a do-while loop
Take a look at apple’s official RunLoop model
A RunLoop is a loop in a thread. In the loop, a RunLoop constantly detects messages, waits for them through Input sources and Timer sources, then processes the received event notification thread, and takes a rest when there are no events.
RunLoop object
Get the RunLoop object
- Introduce the RunLoop object
The Fundation framework (encapsulated based on CFRunLoopRef) NSRunLoop object
-
NSRunLoop
Is based onCFRunLoopRef
Provides object-oriented apis, but these apis are not thread-safe
[NSRunLoop currentRunLoop];// Get the current RunLoop object
[NSRunLoop mainRunLoop];// Get the RunLoop object for the main thread
Copy the code
CoreFoundation CFRunLoopRef object
-
CFRunLoopRef
Is in theCoreFoundation
Framework, which provides pure C function apis, all of which are thread-safe
CFRunLoopGetCurrent(a);// Get the current thread's RunLoop object
CFRunLoopGetMain(a);// Get the RunLoop object for the main thread
Copy the code
So the two ways you can do that are essentially
//Foundation
NSRunLoop *runLoop1 = [NSRunLoop currentRunLoop];
NSRunLoop *mainRunLoop1 = [NSRunLoop mainRunLoop];
//Core Foundation
CFRunLoopRef runLoop2 = CFRunLoopGetCurrent(a);CFRunLoopRef mainRunLoop2 = CFRunLoopGetMain(a);Copy the code
Take a look at the implementation of these two functions
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np());// no CAS needed
return __main;
}
Copy the code
They all call _CFRunLoopGet0, which we’ll see later
CFRunLoopRef source section (introduction thread dependent)
Take a look at the source code for CFRunLoopRef
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */__CFPort _wakeUpPort; [By this functionCFRunLoopWakeUpThe kernel sends a message to the port to wake up the runloop.volatile _per_run_data *_perRunData; // reset for runs of the run looppthread_t _pthread; 【 uint32_t _winThread;CFMutableSetRef_commonModes; [Store string, record all mode marked as common]CFMutableSetRef_commonModeItems; Store all commonMode items (source, timer, observer)CFRunLoopModeRef_currentMode; [Current mode]CFMutableSetRef_modes; 【 Stored isCFRunLoopModeRef】
struct_block_item *_blocks_head; 【doUse in blocksstruct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
Copy the code
- In addition to recording some properties, the focus is on the three member variables
pthread_t _pthread; [Thread corresponding to RunLoop]CFRunLoopModeRef_currentMode; [Current mode]CFMutableSetRef_modes; 【 Stored isCFRunLoopModeRef】
Copy the code
Which brings us to the following question
RunLoop and thread
Let’s see how _CFRunLoopGet0 is implemented. What does it have to do with runloops and threads
// Global Dictionary, key is pthread_t,value is CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// Access the lock for __CFRunLoops
static CFSpinLock_t loopsLock = CFSpinLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0 is a synonym for the always valid "main thread"
// Get the RunLoop corresponding to the pthread
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
// If pthread is null, obtain the main thread
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if(! __CFRunLoops) { __CFSpinUnlock(&loopsLock);// On the first entry, create a temporary dict dictionary
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
// Get the RunLoop corresponding to the main thread passed in
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// Save the main thread, and save the main thread -key and runloop-value into the dictionary
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// Here the NULL and __CFRunLoops Pointers both point to NULL, so write the dict to __CFRunLoops
if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
/ / release the dict
CFRelease(dict);
}
/ / release mainRunLoop
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
// The runLoop of the main thread is always created on the first entry, whether it is getMainRunLoop or getsubthread runLoop
// Get the RunLoop from the global dictionary
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if(! loop) {// If not, create a new RunLoop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// After the creation, the thread is the key and the runLoop is the value, and the dictionary is stored one to one. The next time the dictionary is retrieved, the runLoop is returned directly
if(! loop) {// Insert newLoop into the dictionary __CFRunLoops, key is thread t
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
// If the incoming thread is the current thread
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
// Register a callback to destroy the corresponding RunLoop when the thread is destroyed
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code
As you can see from this source code
- Each thread has a unique RunLoop object that corresponds to it
- The RunLoop is stored in a global Dictionary, with the thread as the key and RunLoop as the value
- There is no RunLoop object when the thread is created. The RunLoop is created when the thread’s RunLoop is first acquired, and the RunLoop is destroyed when the thread ends
- The RunLoop for the main thread is automatically obtained (created), and the child thread is not RunLoop enabled by default
Related classes for RunLoop
There are five classes associated with RunLoop
CFRunLoopRef
An object that represents a RunLoopCFRunLoopModeRef
RunLoop Running modeCFRunLoopSourceRef
Is the input source (event source) mentioned in the RunLoop model diagramCFRunLoopTimerRef
Timing sourcesCFRunLoopObserverRef
Observer, listening for changes in the RunLoop state
- a
RunLoop
Contains severalMode
, eachMode
There are several moreSource/Timer/Observer
2. Each call to RunLoop’s main function can specify only one Mode. This Mode is called CurrentMode 3. If you need to switchMode
Can only quitLoop
, and specify another oneMode
To enter. This is mostly to separate the groupsSource/Timer/Observer
4. If there is one modeSourcr/Timer/Observer
If no, RunLoop exits without entering the loop
Implementation of related classes in RunLoop
A RunLoop contains several modes, and each Mode contains several sources/timers/observers
CFRunLoopModeRef
Again, CFRunLoopModeRef represents the running mode of a RunLoop
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // The name of the mode by which the mode is identified
Boolean _stopped; // Whether mode is terminated
char _padding[3];
// The core part of the structure
------------------------------------------
CFMutableSetRef _sources0;//Sources0
CFMutableSetRef _sources1;//Sources1
CFMutableArrayRef _observers;/ / observer
CFMutableArrayRef _timers;/ / timer
------------------------------------------
CFMutableDictionaryRef _portToV1SourceMap;// Dictionary key is mach_port_t, value is CFRunLoopSourceRef
__CFPortSet _portSet;// Save all the ports to listen on, such as _wakeUpPort and _timerPort, in this array
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
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
Copy the code
- a
CFRunLoopModeRef
Objects have one name, severalsource0
.source1
.timer
.observer
andport
, you can see that the events are all caused bymode
In management, whileRunLoop
Manages theMode
Five modes of operation
The system has five modes registered by default
kCFRunLoopDefaultMode
: The default Mode in which the main thread normally runsUITrackingRunLoopMode
: Interface tracking Mode, used for ScrollView tracking touch sliding, to ensure that the interface sliding is not affected by other modesUIInitializationRunLoopMode
: The first Mode entered at the beginning of App startup. It will not be used after startup and will be switched to kCFRunLoopDefaultModeGSEventReceiveRunLoopMode:
An internal Mode that accepts system events and is usually not usedkCFRunLoopCommonModes
: this is a placeholder Mode used to mark kCFRunLoopDefaultMode and UITrackingRunLoopMode, but not a real Mode
Among them, kCFRunLoopDefaultMode, UITrackingRunLoopMode and kCFRunLoopCommonModes are the modes we need to use in the development
CommonModes
In the RunLoop object, there is a previous concept called CommonModes
// Simplified version
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;// Stores a string, recording all modes marked as common
CFMutableSetRef _commonModeItems;// Store all commonMode items (source, timer, observer)
CFRunLoopModeRef _currentMode;// The current mode
CFMutableSetRef _modes;// Stores a CFRunLoopModeRef object with different mode types and different mode names
};
Copy the code
- A Mode can mark itself as a Common attribute by adding its ModeName to RunLoop’s commonModes.
- Whenever the contents of the RunLoop change, the RunLoop will
_commonModeItems
In theSource/Observer/Timer
Sync to all modes with the Common tag. The underlying principle
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
// Get all _commonModeItems
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
// Get all _commonModes
CFSetAddValue(rl->_commonModes, modeName);
if (NULL! = set) {CFTypeRef context[2] = {rl, modeName};
Add all _commonModeItems to _commonModes for each Mode one by one
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set); }}else {
}
__CFRunLoopUnlock(rl);
}
Copy the code
Only the following two management Mode interfaces are exposed by CFRunLoop
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRefmodeName, ...) ;Copy the code
What is a Mode Item? What types of elements does Mode contain?
RunLoop
Messages that need to be processed, including time and source messages, they all belong toMode item
RunLoop
It can also be listened onobserver
Object, also belongs toMode item
- All of the
mode item
Can be added to Mode, which can contain more than onemode item
, aitem
Multiple modes can also be added. But aitem
It does not work when added to the same mode repeatedly. If there is no item in a mode, thenRunLoop
Will exit, not enter the loop
mode
The exposedmode item
The interfaces are as follows
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);
Copy the code
- We can only do it by operation
mode name
To operate the internalmode
, when you pass in a newmode name
butRunLoop
There is no internal counterpartmode
RunLoop will automatically create one for youCFRunLoopModeRef
. - For a
RunLoop
In terms of its internalmode
It can only be added but cannot be deleted
Apple publicly provides two modes
kCFRunLoopDefaultMode
(NSDefaultRunLoopMode
) andUITrackingRunLoopMode
You can use these twoMode Name
To operate its corresponding Mode- Apple also provides a string that operates on the Common token: kCFRunLoopCommonModes (NSRunLoopCommonModes). You can use this string to operate on Common Items, or to mark a Mode to “Common”. Use this string to distinguish it from other mode names.
CFRunLoopSourceRef
Where events occur
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; // Order of execution
CFMutableBagRef _runLoops;// Contains multiple runloops
/ / version
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
Copy the code
Two versions are Source0 and Source1
Source0
Contains only one callback (function pointer), which does not actively fire an event. To use, you need to first callCFRunLoopSourceSignal(source)
Will thisSource
Mark it as pending, and then call it manuallyCFRunLoopWakeUp(runloop)
To awakenRunLoop
And let it handle the eventSource1
It contains amach_port
And a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source can be actively awakenedRunLoop
The thread.
So let’s just write a button click event and look at the call stack with thread backtrace and we can see where the click event comes from, right
- First the program starts, calling main on line 18, which calls UIApplicationMain on line 17, and then all the way up to the click event on line 0
- And at the same time we can see the call up here
Source0
That is, our click event belongs to the Source0 function, and the click event is handled in Source0.
- Source1, on the other hand, is used to receive, distribute, and then distribute system events to Source0 for processing
CFRunLoopTimerRef
Time-based triggers
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;// The mode set containing the timer
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; / / timer callback
CFRunLoopTimerContext _context; // Context object
};
Copy the code
CFRunLoopTimerRef
It’s a time-based trigger. It’s the same asNSTimer
You can mix it up. It contains a length of time and a callback (function pointer). When it’s added toRunLoop
When,RunLoop
It registers the corresponding point in time, and when the point in time arrives,RunLoop
Will wake up to perform that callback
For NSTimer scheduledTimerWithTimeInterval and RunLoop relationship
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
Copy the code
It will automatically join NSDefaultRunLoopMode
Both the same
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Copy the code
The timer is not sliding accurately
We must have experienced situations in our development where when we use the NSTimer to do something every time we slide the UIScrollView, the NSTimer will pause, and when we stop the slide, the NSTimer will resume
For example
To build a tableView
Adds the timer to NSDefaultRunLoopMode of the current RunLoopNormal: Print once a second
The timer stops when we drag and drop the tableView
And the reason for that is
- When we’re not doing anything, RunLoop is in
NSDefaultRunLoopMode
Under the - When we drag, the RunLoop ends
NSDefaultRunLoopMode
, switch toUITrackingRunLoopMode
Mode, this mode is not addedNSTimer
So ourNSTimer
It doesn’t work - When we release the mouse, RunLoop ends the UITrackingRunLoopMode mode and switches back
NSDefaultRunLoopMode
Pattern, soNSTimer
It’s back to normal
So what should we do to solve this problem?
Can’t we get NSTimer to work in both modes?
Using CommonModes solves this problem (which solves the question of what Common can do)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code
At this point we can drag and drop without any problems
CFRunLoopObserverRef
CFRunLoopObserverRef is an Observer, and each Observer contains a callback (function pointer) that can be used to receive a change in the state of a RunLoop
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;// Listen to the RunLoop
CFIndex _rlCount;// Add the number of RunLoop objects for this Observer
CFOptionFlags _activities; /* immutable */
CFIndex _order;// Only one listener can be monitored at a time
CFRunLoopObserverCallBack _callout;// Listen callback
CFRunLoopObserverContext _context;Context is used for memory management
};
// There are several time points for observation
typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // You are about to enter RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), The Timer is about to be processed
kCFRunLoopBeforeSources = (1UL << 2), // Source is about to be processed
kCFRunLoopBeforeWaiting = (1UL << 5), // It is about to go to sleep
kCFRunLoopAfterWaiting = (1UL << 6),// Just woke up from hibernation
kCFRunLoopExit = (1UL << 7),// Exit RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code
The internal logic of RunLoop
The internal logic of RunLoop is described as follows
The simplified __CFRunLoopRun function preserves the main code to see the implementation
[Start with DefaultMode]void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false); } set RunLoop timeout with the specified Mode.int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } [RunLoop implementation]int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); If there is no source/timer/observer in mode, return:if (__CFRunLoopModeIsEmpty(currentMode)) return;
【1.Inform Observers: RunLoop that the loop is about to enter. 】 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); __CFRunLoopRun(RunLoop, currentMode, seconds, returnAfterSourceHandled) {Boolean sourceHandledThisLoop =NO;
int retVal = 0;
do{【2.Inform Observers: RunLoop that a Timer callback is about to be triggered. 】 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 【3.Inform Observers: RunLoop that the Source0 (non-port) callback is about to be triggered. 】 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); [Execute added block] __CFRunLoopDoBlocks(runloop, currentMode); 【4.RunLoop fires the Source0 (non-port) callback. 】 sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); [Execute added block] __CFRunLoopDoBlocks(runloop, currentMode); 【5.If any Source1 (based on port) is in the ready state, process the Source1 directly and jump to process the message. 】if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) gotohandle_msg; Inform Observers: RunLoop that the thread is about to go to sleep.if(! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } 【7.Call mach_msg to wait for the message to receive mach_port. The thread goes to sleep until it is awakened by one of the following events. 】 • A port-based Source event. __CFRunLoopServiceMachPort(waitSet, & MSG,sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
【8.Inform Observers: RunLoop that the thread has just been awakened. 】 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); Receive the message, process the message. Handle_msg: 【9.1If a Timer is out of time, the Timer callback is triggered. 】if(msg_is_timer) {__CFRunLoopDoTimers(runloop, mach_absolute_time())} 【9.2If there is a block dispatch to main_queue, execute the block. 】else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
【 9.3If a Source1 (based on port) emits an event, handle the event.else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if(sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); }} __CFRunLoopDoBlocks(runloop, currentMode);if(sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource; }else ifRetVal = kCFRunLoopRunTimedOut; (timeout) {[timeout = kCFRunLoopRunTimedOut; }else if(__CFRunLoopIsStopped(runloop)) {[forced to stop by an external caller] retVal = kCFRunLoopRunStopped; }else if(__CFRunLoopModeIsEmpty(runloop, currentMode)) {[source/timer/observer none] retVal = kCFRunLoopRunFinished; If the mode is not available and the loop is not stopped, continue the loop. }while (retVal == 0);
}
【 10.Inform Observers: RunLoop that they are about to exit. 】 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }Copy the code
In fact, RunLoop is one such function, with a do-while loop inside. When you call CFRunLoopRun(), the thread stays in the loop until it times out or is called manually
RunLoop callback
- When the App is started, the system will register five modes by default.
- When a RunLoop makes a callback, it’s usually called out by a long function call, which you’ll usually see on the call stack when you’re debugging your code for breakpoints. Here’s how RunLoop works:
{
/// 1. Inform Observers that they are about to enter RunLoop
/// An Observer will create AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. Inform Observers: The Timer callback is about to be triggered.
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
Inform Observers: Source (non-port-based,Source0) callbacks are about to be triggered.
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. Trigger the Source0 (non-port based) callback.
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. Inform Observers, they are about to go to sleep
/// Create an AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
/// 7. sleep to wait msg.
mach_msg() -> mach_msg_trap();
/// 8. Inform Observers that the thread is awake
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
/// 9. If the Timer wakes up, call back the Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. If the queue is awakened by dispatch, execute all blocks that are placed in the main queue by calling dispatch_async
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
/// 9. If Runloop is awakened by a Source1 (port-based) event, handle this event
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while(...). ;/// 10. Inform Observers that they are about to exit RunLoop
/// There is an Observer that releases AutoreleasePool: _objc_autoreleasePoolPop();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
Copy the code
The instance test
The use of NSTimer
There is about
ImageView display delay
When the interface has UITableView, and each UITableViewCell has a picture in it. This is when we scroll through the UITableView, if we have a bunch of images to display, we might get stuck.
How to solve this problem?
We should delay the implementation of the image, that is, the ImageView delays the display of the image. Don’t load the image when we slide, drag it to the end of the display
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]].Copy the code
The user clicks on the screen, and in the main thread, the image is displayed after three seconds, but after the user clicks on the screen, if the user starts scrolling textView again, then even after three seconds, the image will not be displayed. When the user stops scrolling, the image will be displayed.
This is because the method setImage is limited to NSDefaultRunLoopMode mode. While scrolling textView, the program is running in tracking mode, so the setImage method does not execute.
Permanent thread
If background operations are frequent during application development, such as playing music in the background, downloading files, etc., we want this thread to stay in memory forever
We can add a strong reference child thread for resident memory, add a source under the RunLoop of that thread, and turn RunLoop on
@property (nonatomic.strong) NSThread *thread;
Copy the code
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
[self.thread start];
}
- (void)run1 {
NSLog(@"----run1-----");
/* If you do not add this sentence, you will find that the runloop is not created, because the runloop will die immediately if there is no CFRunLoopSourceRef event source input or timer. The following method to add an NSport to a Runloop is to add an event source, and you can also add a timer or observer to keep the runloop from dying */
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// Methods 1,2, and 3 implement the same effect, allowing the runloop to run indefinitely
2 / / method
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
3 / / method
// [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] run];
// Test whether RunLoop is enabled. If RunLoop is enabled, you cannot come here because RunLoop is enabled.
NSLog(@" RunLoop not enabled"); } Let's also write the 'touchesBegan' method in our new thread - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// Use performSelector to call run2 in self.thread
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void) run2 {
NSLog(@"----run2------");
}
Copy the code
We have to make sure that the thread doesn’t die before we can accept the time processing in the background, so if we don’t implement adding an NSPort or an NSTimer, we’ll find that after the run method, the thread will die, and subsequent execution of the TouchBegan method won’t work.
Once you have implemented one of the above three methods, you can see that the run method is finished, and then click on the screen. You can continue to execute the test method, because the thread self.thread is always in the background, waiting for events to join it, and then execute.