什么是RunLoop
默认情况下,线程执行完任务后就会退出,不再执行任务。我们需要采用一种方式来让线程能够不断的处理任务,并且不退出,所有就有了RunLoop。
顾名思义,RunLoop就是一直运行着的循环。
RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说touch事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行。Runloop在没有事件处理时,会让线程进入休眠,只有在接收到事件时才会被唤醒,然后再做出相应的处理。
一条线程对应一个RunLoop对象(内部实现好比字典[key:线程,value:runloop]),主线程的RunLoop对象由系统自动创建。
线程与RunLoop的关系(通俗比方)
线程比作汽车,RunLoop比做发动机,所以汽车才能一直跑下去。
RunLoop Mode
NSDefaultRunLoopMode
app默认Mode,通常主线程就在该Mode下运行。
NSTrackingRunLoopMode
界面跟踪mode,用于scrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响。
UIInitializationRunLoopMode
在刚启动app时进入的第一个Mode,启动完成后就不再使用。
GSEventReceiveRunLoopMode
接收系统事件的内部Mode,通常用不到。
NSRunLoopCommonModes
这是一个占位Mode,不是一个真正的Mode,会同时处理默认模式和UI模式中的事件。
RunLoop Source
定时源,基于时间的触发器,基本上就是NSTimer
NSTimer默认被加入RunLoop的default mode下
source0:非基于Port的源,基本就是应用层事件,比如点击事件
source1:基于Port的源,通过内核和其他线程通信,接收、分发系统事件
我们点击app内的某个按钮,先触摸到屏幕(硬件),屏幕表面的事件会先封装成Event传递给source1(mach_port),source1唤醒runloop,让后将Event分发给source0处理。
RunLoop Observer
CFRunLoopObserverRef,监听RunLoop的状态改变
即将进入Loop:1
即将处理Timer:2
即将进入source:4
即将进入休眠:32
即将从休眠中唤醒:64
即将从Loop中退出:128
监听全部状态改变
RunLoop运行逻辑
1.通知观察者即将进入RunLoop
2.通知观察者即将处理Timer
3.通知观察者即将处理source0
4.处理source0
5.如果有source1,跳至第9步
6.通知观察者线程即将进入休眠状态
7.线程进入休眠,等待直到以下任一事件发生唤醒线程
某一事件到达基于端口的源
定时器启动
RunLoop设置的时间超时
RunLoop被显式唤醒
8.通知观察者线程即将被唤醒
9.处理唤醒时收到的事件
如果用户定义的定时器启动,处理定时器事件并重启RunLoop,跳回步骤2;
如果输入源启动,传递相应消息;
如果RunLoop被显式唤醒且时间未超时,重启RunLoop,跳回步骤2
10.通知观察者,即将退出RunLoop
RunLoop 可以做什么
1.crash起死回生
好在苹果自带的crash检测api,以及runloop可以让应用复活一次,不过第二次依旧会崩溃,但是还是一个很实用的小技巧。
以下是IOS崩溃复活的类和使用方法
#import "CrashHandler.h"
//Appdelagte中注册crash记录上报
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[CrashHandler sharedInstance];
return YES;
}
//.h文件
#import <Foundation/Foundation.h>
@interface CrashHandler : NSObject
{
BOOL ignore;
}
+ (instancetype)sharedInstance;
@end
//.m文件
#import "CrashHandler.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 CrashHandler
static CrashHandler *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)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
if (anIndex == 0) {
ignore = YES;
} else if (anIndex == 1) {
NSLog(@"起死回生");
}
}
- (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 (!ignore) {
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];
CrashHandler *crashObject = [CrashHandler 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 = [CrashHandler backtrace];
NSLog(@"信号捕获崩溃,堆栈信息:%@",callStack);
CrashHandler *crashObject = [CrashHandler 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];
}
2.线程保活
如果经常会在子线程中做一些耗时操作(比如下载文件、后台播放音乐等),最好能让这条线程常驻内存
为当前线程添加一个RunLoop
向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环
启动该RunLoop