Thread survival
- When we start a child thread to process a task, if the task is completed, the thread is destroyed. If we have a task that needs to be processed in the child thread multiple times, then frequently opening/destroying the child thread can have a significant performance cost. We can reduce CPU overhead by creating a child thread and keeping it alive every time we process a task.
- When we start a thread to process data, but the data is returned late, to prevent the thread from being destroyed early, we need to keep the thread alive to ensure that the thread is destroyed after receiving the returned data. AFN(2.x) is a good example of this. NSURLConnection needs to wait for a callback, so the thread needs to be kept alive. AFN(3.x) will be encapsulated using NSURLSession, and the developer can set up the callback queue himself, so there is no need for the keepalive thread
// Define a custom thread that executes a method. It is guaranteed to be executed only once via GCD
+ (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;
}
// Add [NSMachPort port] to the RunLoop to ensure that there is a port in the current Mode so that the RunLoop does not exit
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool{[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}Copy the code
NSTimer will stop working in the sliding process
NSTimer works in DefaultMode of RunLoop. When the interface slides, RunLoop switches to TrackingMode, so the timer fails. When we need to add timers to RunLoop, we can use CommonMode
NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate now] interval:1.0 target:self selector:@selector(test) userInfo:NULL repeats:YES];
// Add timer to CommonModes
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code
So why does NSRunLoopCommonModes work? Let’s look at the implementation of the add method:
- The entry method first checks if it is NSRunLoopCommonModes
- If it is NSRunLoopCommonModes, it gets the _commonModes and _commonModeItems of the current runloop,
- Adds the current timer to the _commonModeItems of the runloop
- The current runloop and timer as the context, for all the _commonModes __CFRunLoopAddItemToCommonModes function execution
- __CFRunLoopAddItemToCommonModes functions in the timer and runloop recursive call CFRunLoopAddTimer method, incoming mode is concrete mode right now, will go else inside, execute the corresponding timer
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
// ...
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL! = set) { CFTypeRef context[2] = {rl, rlt};
/* add new item to all common-modes */
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set); }}else {
// ...
}
__CFRunLoopUnlock(rl);
}
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
CFTypeRef item = (CFTypeRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code
3. Monitoring application is stuck
Using RunLoop to monitor application lag The principle is to monitor the status of RunLoop. If the current RunLoop method takes too long to execute before it goes to sleep, or if the thread wakes up and takes too long to receive messages before it can go to the next step, the current thread is considered blocked, and if the current thread is the main thread, it is stalled. We can listen for the status change of the main thread RunLoop, the time between kCFRunLoopBeforeSource and kCFRunLoopAfterWaiting, and if it exceeds a certain time, we consider that a lag has occurred.
The following sample code references Daming’s code: GCDFetchFeed
- First we create an observer of the runloop to listen for changes in the state of the runloop. Also initializes a semaphore for thread synchronization
- (void)createObserver {
// Use semaphores to ensure synchronization
dispatchSemaphore = dispatch_semaphore_create(0);
// Create an observer
CFRunLoopObserverContext context = {0,(__bridge void*)self.NULL.NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES.0,
&runLoopObserverCallBack,
&context);
// Add the observer to the common mode of the main thread runloop
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
}
Copy the code
- In a listening callback, the current runloop state is recorded and a semaphore is released during each runloop state transition
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
lagMonitor->runLoopActivity = activity;
dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
dispatch_semaphore_signal(semaphore);
}
Copy the code
- Create child threads to monitor the status of runloop
// Create a child thread monitor
dispatch_async(dispatch_get_global_queue(0.0), ^ {// Create an infinite loop that continuously listens for the status of the runloop
while (YES) {
// Set the semaphore to block for 50 milliseconds.
long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
// Determine when the semaphore wait times out
if(semaphoreWait ! =0) {
// If a timeout occurs and it is BeforeSources or AfterWaiting, the delay is considered and can be handled accordingly
if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
NSLog(@"monitor trigger");
} //end activity
}// end semaphore wait
}// end while
});
Copy the code
- According to the above process, we can sum up
- If no lag occurs, our semaphore will be released continuously (less than 50ms) during the state transition of the runloop. In our monitor while loop, the semaphore will continue to be skipped if it does not time out
- If we stall, then our semaphore will be released late (say 1s), then in the while loop, the semaphore times out (meaning that the state does not change beyond 50msrunloop), If a runloop is in a BeforeSources or AfterWaiting state, we assume that a stall has occurred
- The conditions for determining caton can be defined according to one’s own situation, and the corresponding treatment can also be handled by oneself