此篇文章我将用最短的篇幅列举RunLoop在实际项目中几种具体的用法,以便以后使用时查阅。
应用1:创建常驻线程
首先上经典代码:
+ (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;
}
类似的,项目中,我们也可以在单例工具中实现一个健康长寿的子线程了。
应用2:优化定时器NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTask) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
上面代码以下情形可以直接用:
我们都知道,可滑动视图在滑动的时候,runloop会切换到UITrackingRunLoopMode,这时NSDefaultRunLoopMode下的NSTimer是不会跑的,在实际运用中,这可能会是一个问题。
举个具体的例子,如今类似钉钉签到的功能非常常见,一般会有一个准确的时间在屏幕上跑,这个时间需要准确无误(为了踩点8:59:59)的话,我们就要做到界面滑动时timer依然在运行。
应用3:发现和消除卡顿
这个一看就说来话长,首先是如何发现卡顿,用到的是CFRunLoopObserverRef,这家伙可以监听RunLoop的各个状态,利用这一点可以检测出RunLoop 在进入睡眠之前和唤醒后的两个状态的耗时。推荐看看微信iOS卡顿监控系统。
而发现并不是最终目的,如何消除才是关键,最经典的例子就是18图分步加载这个了。
应用4:让APP秽土转生
是不是有越来越🐂🍺的感觉?是的,RunLoop让我们可以使用火影忍者中那个究极禁术“秽土转生”让我们得APP更强大。哈哈,其实是使用异常捕捉技术和RunLoop让APP一些一般的崩溃能够继续往下运行,直接看具体代码吧。
//
// BBCrashHandler.h
// RunloopDemo
//
// Created by xyb on 2019/8/2.
// Copyright © 2019年 XYB. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BBCrashHandler : NSObject
+ (instancetype)sharedInstance;
@end
NS_ASSUME_NONNULL_END
//
// BBCrashHandler.m
// RunloopDemo
//
// Created by xyb on 2019/8/2.
// Copyright © 2019年 XYB. All rights reserved.
//
#import "BBCrashHandler.h"
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
NSString * const kSignalExceptionName = @"kSignalExceptionName";
NSString * const kSignalKey = @"kSignalKey";
NSString * const kCaughtExceptionStackInfoKey = @"kCaughtExceptionStackInfoKey";
void HandleException(NSException *exception);
void SignalHandler(int signal);
@implementation BBCrashHandler
static BBCrashHandler *instance = nil;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setCatchExceptionHandler];
}
return self;
}
- (void)setCatchExceptionHandler
{
// 1.捕获一些异常导致的崩溃
NSSetUncaughtExceptionHandler(&HandleException);
// 2.捕获非异常情况,通过signal传递出来的崩溃
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
}
+ (NSArray *)backtrace
{
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (int i = 0; i < frames; i++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
- (void)handleException:(NSException *)exception
{
NSString *message = [NSString stringWithFormat:@"崩溃原因如下:\n%@\n%@",
[exception reason],
[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
NSLog(@"%@",message);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"程序崩溃了"
message:@"如果你能让程序起死回生,那你的决定是?"
delegate:self
cancelButtonTitle:@"崩吧"
otherButtonTitles:@"秽土转生", nil];
[alert show];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (1) {
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
signal(SIGABRT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
if ([[exception name] isEqual:kSignalExceptionName]) {
kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);
} else {
[exception raise];
}
}
@end
void HandleException(NSException *exception)
{
// 异常堆栈信息
NSArray *callStack = [exception callStackSymbols];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setObject:callStack forKey:kCaughtExceptionStackInfoKey];
BBCrashHandler *crashObject = [BBCrashHandler sharedInstance];
NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
void SignalHandler(int signal)
{
NSArray *callStack = [BBCrashHandler backtrace];
BBCrashHandler *crashObject = [BBCrashHandler sharedInstance];
NSException *customException = [NSException exceptionWithName:kSignalExceptionName
reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
[crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
上面代码的原理是使用异常捕捉回调获取到崩溃的相关信息,然后获取当前runloop,并执行所有Mode,不过只能有一次机会,第二次就不好使了。
本文完🌹。