iOS RunLoop在项目中具体应用

此篇文章我将用最短的篇幅列举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 

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 

#include 
#include 

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,不过只能有一次机会,第二次就不好使了。

本文完。

你可能感兴趣的:(iOS RunLoop在项目中具体应用)