RunLoop

什么是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

3.app的卡顿监控,保活子线程 添加定时器 监控主线程RunLoop的状态 (封装单例工具类)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容