Objective-C:RunLoop(应用)
对RunLoop的知行合一;--RunLoop
线程常驻
说明
线程的五大状态分别是:新建状态、就绪状态、运行状态、阻塞状态、死亡状态。
我们通常创建的线程即使通过全局变量持有,当任务执行完毕之后,尽管内存中还存在这个线程,但实际上这个线程已经处于死亡状态了,如果有高频操作,就需要重复频繁开启新的线程。
线程的开启和销毁是一个损耗CPU性能的操作,在特定的需求中,如音频录制,视频录制的合成,语音对讲等,频繁的开关线程对应用的性能损耗很大,这时,我们可以借用RunLoop实现一个常驻的后台服务线程,监听这些操作,有任务则唤醒执行,无任务则休眠节省资源。
AFN常驻线程的实现
AFN中AFURLConnectionOperation是基于NSURLConnection构建的,其希望能够在后台线程来接收Delegate的回调。为此AFN创建了一个线程,然后在里面开启了一个RunLoop,然后添加item:
~~~objective-c
+ (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;
}
~~~
这里的port仅仅是为了让RunLoop不退出而添加的item,如果需要使用这个port,调用者则要持有这个port,在外部线程通过port发送消息到这个Loop内部。
~~~objective-c
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
~~~
在需要这个线程执行任务的时候,通过[NSObject performSelector:onThread:..]将任务扔到这个线程的RunLoop中来执行。
接口
仿照AFN的服务线程,我们提供常驻线程的一个单例接口:
~~~objective-c
@interface SeanThread : NSThread
/**
* videoRecord线程获取方法
*/
+ (instancetype)videoRecordThread;
@end
~~~
实现
在入口方法中开启RunLoop,添加Source避免RunLoop没有item退出:
~~~objective-c
@implementation SeanThread
/**
* videoRecord线程获取方法
*/
+ (instancetype)videoRecordThread {
static SeanThread * _instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] initWithTarget:self selector:@selector(videoRecordThreadEntryPoint:) object:nil];
[_instance setName:@"VideoRecordThread"];
[_instance start];
});
return _instance;
}
/**
* videoRecord线程入口方法
*/
+ (void)videoRecordThreadEntryPoint:(id)__unused object {
@autoreleasepool {
NSLog(@"子线程进入");
NSLog(@"current thread : %@", [NSThread currentThread]);
NSRunLoop * currentRunloop = [NSRunLoop currentRunLoop];
[currentRunloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];// runloop没有mode将会退出
[currentRunloop run];
NSLog(@"runloop run after");// runlopp开启后这里将永不执行
}
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
~~~
使用
使用这个常驻线程执行任务:
~~~objective-c
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self performSelector:@selector(doSomethingInBackgroundThread) onThread:[SeanThread videoRecordThread] withObject:nil waitUntilDone:false];
}
- (void)doSomethingInBackgroundThread {
@autoreleasepool {
NSLog(@"do something begin");
NSLog(@"do something doing");
NSLog(@"do something end");
}
}
~~~
线程依赖
给两个线程添加依赖性有多种方式,使用RunLoop是其中的一种。
使用GCD方式
~~~objective-c
@interface ThreadViewController ()
@property (nonatomic, assign) BOOL runLoopThreadDidFinishFlag;
@property (nonatomic, strong) NSThread * myThread;
@end
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/// 线程依赖
[self runLoopAddDependance];
}
#pragma mark - 线程依赖
- (void)runLoopAddDependance {
self.runLoopThreadDidFinishFlag = NO;
NSLog(@"Start a New Run Loop Thread");
NSThread *runLoopThread = [[NSThread alloc] initWithTarget:self selector:@selector(handleRunLoopThreadTask) object:nil];
[runLoopThread start];
NSLog(@"Exit handleRunLoopThreadButtonTouchUpInside");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (!_runLoopThreadDidFinishFlag) {
self.myThread = [NSThread currentThread];
NSLog(@"%@", self.myThread);
NSLog(@"Begin RunLoop");
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSPort *myPort = [NSPort port];
[runLoop addPort:myPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"End RunLoop");
[self.myThread cancel];
self.myThread = nil;
}
});
}
- (void)handleRunLoopThreadTask {
NSLog(@"Enter Run Loop Thread");
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"In Run Loop Thread, count = %ld", i);
sleep(1);
}
_runLoopThreadDidFinishFlag = YES;
NSLog(@"Exit Normal Thread");
[self performSelector:@selector(tryOnMyThread) onThread:self.myThread withObject:nil waitUntilDone:NO];
NSLog(@"Exit Run Loop Thread");
}
- (void)tryOnMyThread {
NSLog(@"tryOnMyThread");
}
~~~
使用NSThread方式
~~~objective-c
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_runLoopThreadDidFinishFlag = NO;
NSThread *runLoopThread = [[NSThread alloc] initWithTarget:self selector:@selector(handleRunLoopThreadTask) object:nil];
[runLoopThread start];
self.myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadEntryPoint) object:nil];
[self.myThread start];
}
- (void)myThreadEntryPoint {
@autoreleasepool {
if (!_runLoopThreadDidFinishFlag) {
NSLog(@"Begin RunLoop");
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSPort *myPort = [NSPort port];
[runLoop addPort:myPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@"End RunLoop");
[self.myThread cancel];
self.myThread = nil;
}
}
}
- (void)handleRunLoopThreadTask {
NSLog(@"Enter Run Loop Thread");
for (NSInteger i = 0; i < 5; i ++) {
NSLog(@"In Run Loop Thread, count = %ld", i);
sleep(1);
}
_runLoopThreadDidFinishFlag = YES;
NSLog(@"Exit Normal Thread");
[self performSelector:@selector(tryOnMyThread) onThread:self.myThread withObject:nil waitUntilDone:NO];
}
- (void)tryOnMyThread {
NSLog(@"tryOnMyThread");
}
~~~
流畅度优化
场景:UITableView,Cell数量1000,每个Cell含有3个imageView,每个imageView均本地加载高清大图(主线程中存在不可避免的耗时操作)。
分析:卡顿原因产生的原因,在于一个Runloop周期需要绘制显示到屏幕上的所有的点(或者有变化的点),此场景下为一个周期绘制多张高清图。
优化:
- 为imageView设置image,是在UITrackingRunLoopMode中进行的,在滑动的Mode下更新UI操作是第一个问题点,这里可以通过更改设置image的Mode避免与手势滑动抢时间:[imageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
- 将每张高清图片的绘制(一个操作单元)派发给一个Runloop周期,每个Runloop周期绘制一张高清图片(执行一个操作单元)。由于Runloop循环非常迅速,所以图片的额加载速度不受影响。
普通加载
~~~objective-c
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
NSString *path1 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image1 = [UIImage imageWithContentsOfFile:path1];
cell.image1 = image1;
cell.title1 = [@(indexPath.row).description stringByAppendingString:@"First"];
NSString *path2 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image2 = [UIImage imageWithContentsOfFile:path2];
cell.image2 = image2;
cell.title2 = [@(indexPath.row).description stringByAppendingString:@"Second"];
NSString *path3 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image3 = [UIImage imageWithContentsOfFile:path3];
cell.image3 = image3;
cell.title3 = [@(indexPath.row).description stringByAppendingString:@"Third"];
return cell;
}
~~~
使用RunLoop拆分任务
先看匿名类别:
~~~objective-c
/// 任务的Block定义
typedef BOOL(^TaskBlock)(void);
@interface ViewController ()
<UITableViewDelegate,
UITableViewDataSource>
@property (nonatomic, strong) UITableView * tableView;
/// 用于唤醒RunLoop的timer
@property (nonatomic, strong) NSTimer * timer;
/// 任务存储
@property (nonatomic, strong) NSMutableArray * tasks;
/// 最大任务数
@property (nonatomic, assign) NSInteger maxTaskCount;
@end
~~~
属性懒加载:
~~~objective-c
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [UITableView new];
_tableView.backgroundColor = [UIColor whiteColor];
_tableView.frame = (CGRect){0, 0, KSWIDTH, KSHEIGHT};
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass:[TableViewCell class] forCellReuseIdentifier:@"TableViewCell"];
_tableView.rowHeight = 100.f;
[self.view addSubview:_tableView];
}
return _tableView;
}
~~~
添加RunLoop观察者:
~~~objective-c
#pragma mark - RunLoopObserver
/**
* NSTimer保证runloop高效监听
*/
- (void)keepRunloopAwake {
}
/**
* 添加RunloopObserver
*/
- (void)addRunloopObserver {
/// 获取当前Runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
/// 定义一个上下文
CFRunLoopObserverContext observerContext = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
/// 创建一个观察者
static CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, true, NSIntegerMax - 999, &RunloopObserverCallback, &observerContext);
/// 添加观察者到当前的Runloop
CFRunLoopAddObserver(runloop, observer, kCFRunLoopDefaultMode);
/// Release
CFRelease(observer);
}
/**
* RunloopObserver监听回调
*/
static void RunloopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
ViewController * vc = (__bridge ViewController *)info;
/// 回调频繁调用,添加条件判断拦截不必要的执行,以提高性能
if (vc.tasks.count <= 0) {
return;
}
BOOL isCurrentIndexPath = false;
while (!isCurrentIndexPath && vc.tasks.count > 0) {
TaskBlock task = vc.tasks.firstObject;
isCurrentIndexPath = task();
[vc.tasks removeObjectAtIndex:0];
}
}
/**
* 添加要分发执行的任务到数组
*/
- (void)addTask:(TaskBlock)taskBlock {
[self.tasks addObject:taskBlock];
/// 移除不需要执行的任务(只绘制展示到屏幕上的最新的30张图片)
if (self.tasks.count > self.maxTaskCount) {
[self.tasks removeObjectAtIndex:0];
}
}
~~~
TableView代理回调:
~~~objective-c
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1000;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
cell.currentIndexPath = indexPath;
[self addTask:^BOOL{
if (![cell.currentIndexPath isEqual:indexPath]) {
return false;
}
NSString *path1 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image1 = [UIImage imageWithContentsOfFile:path1];
[cell performSelector:@selector(setImage1:) withObject:image1 afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
//cell.image1 = image1;
cell.title1 = [@(indexPath.row).description stringByAppendingString:@"First"];
return true;
}];
[self addTask:^BOOL{
if (![cell.currentIndexPath isEqual:indexPath]) {
return false;
}
NSString *path2 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image2 = [UIImage imageWithContentsOfFile:path2];
[cell performSelector:@selector(setImage2:) withObject:image2 afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
//cell.image2 = image2;
cell.title2 = [@(indexPath.row).description stringByAppendingString:@"Second"];
return true;
}];
[self addTask:^BOOL{
if (![cell.currentIndexPath isEqual:indexPath]) {
return false;
}
NSString *path3 = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"png"];
UIImage *image3 = [UIImage imageWithContentsOfFile:path3];
[cell performSelector:@selector(setImage3:) withObject:image3 afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
//cell.image3 = image3;
cell.title3 = [@(indexPath.row).description stringByAppendingString:@"Third"];
return true;
}];
return cell;
}
~~~
视图加载运行:
~~~objective-c
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(keepRunloopAwake) userInfo:nil repeats:true];
self.maxTaskCount = 30;
self.tasks = [NSMutableArray array];
[self addRunloopObserver];
[self.tableView reloadData];
}
~~~
Cell内部内存优化
~~~objective-c
- (void)prepareForReuse {
[super prepareForReuse];
self.imageView1.image = nil;
self.imageView2.image = nil;
self.imageView3.image = nil;
self.image1 = nil;
self.image2 = nil;
self.image3 = nil;
self.titleLabel1.text = nil;
self.titleLabel2.text = nil;
self.titleLabel3.text = nil;
self.title1 = nil;
self.title2 = nil;
self.title3 = nil;
}
~~~
主线程卡顿监测
测试只能反馈一部分问题,一款优秀的应用是经得起在真实的用户环境中的考验的,一些对用户体验要求较为严苛的应用,当投向市场,要随时获得应用的使用情况并据此进行优化。
参照微信团队的卡顿检测方案,我们实现一下线上卡顿监测并报告给服务器的功能。
贴上主线程卡顿监测的类LagMonitor的实现。
接口
~~~objective-c
#import <Foundation/Foundation.h>
@interface LagMonitor : NSObject
+ (instancetype)sharedInstance;
/**
* Start With Standard
*/
- (void)start;
/**
* Start With Define Available
*/
- (void)startWithTimerInterval:(NSTimeInterval)timerTimeInterval TimeoutInterval:(NSTimeInterval)timeOutInterval;
/**
* Stop
*/
- (void)stop;
@end
~~~
类别
~~~objective-c
#import "LagMonitor.h"
@interface LagMonitor ()
{
/// Observer in Main RunLoop
CFRunLoopObserverRef _observer;
/// Timer for time judge in Monitor Thread
CFRunLoopTimerRef _timer;
/// Interval of timer
NSTimeInterval _timerTimeInterval;
}
/// 监控线程
@property (nonatomic, strong) NSThread * monitorThread;
/// Task is executiving in Main RunLoop or not
@property (nonatomic, assign) BOOL executiving;
/// Task start date in Main RunLoop
@property (nonatomic, strong) NSDate * taskStartDate;
/// Interval which task time out in Main RunLoop
@property (nonatomic, assign) NSTimeInterval timeOutInterval;
@end
~~~
实现
~~~objective-c
@implementation LagMonitor
+ (instancetype)sharedInstance {
static LagMonitor * _instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
_instance.monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitorThreadEntryPoint:) object:nil];
[_instance.monitorThread setName:@"LagMonitorThread"];
[_instance.monitorThread start];
});
return _instance;
}
+ (void)monitorThreadEntryPoint:(id)__unused object {
@autoreleasepool {
NSRunLoop * runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[runloop run];
}
}
#pragma mark - Public Functions
/**
* Start With Standard
*/
- (void)start {
[self startWithTimerInterval:1 TimeoutInterval:1.5];
}
/**
* Start With Define Available
*/
- (void)startWithTimerInterval:(NSTimeInterval)timerTimeInterval TimeoutInterval:(NSTimeInterval)timeOutInterval {
_timerTimeInterval = timerTimeInterval;
_timeOutInterval = timeOutInterval;
/// 创建Observer
[self configObserver];
/// 创建Timer
[self performSelector:@selector(configTimer) onThread:self.monitorThread withObject:nil waitUntilDone:false modes:@[NSRunLoopCommonModes]];
}
/**
* Stop
*/
- (void)stop {
/// 移除Observer
[self removeObserver];
/// 移除Timer
[self performSelector:@selector(removeTimer) onThread:self.monitorThread withObject:nil waitUntilDone:false modes:@[NSRunLoopCommonModes]];
}
#pragma mark - Observer Callback
/**
* 创建Observer
*/
- (void)configObserver {
if (_observer) {
return;
}
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
true,
0,
&RunLoopObserverCallback,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
}
/**
* 移除Observer
*/
- (void)removeObserver {
if (!_observer) {
return;
}
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
CFRelease(_observer);
_observer = NULL;
}
/**
* Observer回调
*/
static void RunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
LagMonitor * monitor = (__bridge LagMonitor *)info;
switch (activity) {
case kCFRunLoopEntry:
case kCFRunLoopBeforeTimers:
{
break;
}
case kCFRunLoopBeforeSources:
{
monitor.executiving = true;
monitor.taskStartDate = [NSDate date];
break;
}
case kCFRunLoopBeforeWaiting:
{
monitor.executiving = false;
break;
}
case kCFRunLoopAfterWaiting:
case kCFRunLoopExit:
default:
{
break;
}
}
}
#pragma mark - Timer Callback
/**
* 创建Timer
*/
- (void)configTimer {
if (_timer) {
return;
}
CFRunLoopTimerContext context = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
_timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
0.1,
_timerTimeInterval,
0,
0,
&MonitorTimerCallback,
&context);
CFRunLoopAddTimer(CFRunLoopGetCurrent(), _timer, kCFRunLoopCommonModes);
}
/**
* 移除Timer
*/
- (void)removeTimer {
if (!_timer) {
return;
}
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), _timer, kCFRunLoopCommonModes);
CFRelease(_timer);
_timer = NULL;
}
/**
* Timer回调
*/
static void MonitorTimerCallback(CFRunLoopTimerRef timer, void *info) {
LagMonitor * monitor = (__bridge LagMonitor *)info;
if (!monitor.executiving) {
return;
}
/// MainRunLoop正在执行任务,且本次loop此时尚未执行完
NSTimeInterval executeTime = [[NSDate date] timeIntervalSinceDate:monitor.taskStartDate];
/// 主线程一次RunLoop延迟超过阈值,获取当前调用栈,上传服务器
if (executeTime >= monitor.timeOutInterval) {
NSLog(@"主线程本次RunLoop卡顿时间:%.2f", executeTime);
}
}
#pragma mark - StackInfo Handle
@end
~~~
使用
~~~objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ViewController * rootVC = [ViewController new];
UINavigationController * navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
self.window.rootViewController = navVC;
[self.window makeKeyAndVisible];
/// LagMonitor
[[LagMonitor sharedInstance] startWithTimerInterval:1 TimeoutInterval:1];
/// CrashHandler
[CrashHandler sharedInstance];
return YES;
}
~~~
让闪退的应用起死回生并获取闪退信息
既然要处理闪退信息,那肯定需要一个全局的类,我们定为CrashHandler。
接口
~~~objective-c
#import <Foundation/Foundation.h>
@interface CrashHandler : NSObject
{
BOOL ignore;
}
+ (instancetype)sharedInstance;
@end
~~~
类内部常变量定义
~~~objective-c
#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);
static NSString * _exceptionInfo;
static NSString * _signalInfo;
~~~
实现
~~~objective-c
@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)handleException:(NSException *)exception
{
/// info
NSString *message = [NSString stringWithFormat:@"异常捕获崩溃,堆栈信息:\n%@\n%@",
[exception reason],
[[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
NSLog(@"%@",message);
_exceptionInfo = message;
/// alert
UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"反馈提示" message:@"应用程序崩溃了,为了准确定位闪退原因,我们需要您将当前程序信息反馈给我们,是否反馈?" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * sureAction = [UIAlertAction actionWithTitle:@"反馈" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"问题报告:\n %@\n%@", _exceptionInfo, _signalInfo);
}];
UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
ignore = YES;
}];
[alertVC addAction:sureAction];
[alertVC addAction:cancelAction];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertVC animated:true completion:nil];
/// runloop
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);
_signalInfo = [NSString stringWithFormat:@"信号捕获崩溃,堆栈信息:%@",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];
}
~~~
使用
~~~objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ViewController * rootVC = [ViewController new];
UINavigationController * navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
self.window.rootViewController = navVC;
[self.window makeKeyAndVisible];
/// LagMonitor
[[LagMonitor sharedInstance] startWithTimerInterval:1 TimeoutInterval:1];
/// CrashHandler
[CrashHandler sharedInstance];
return YES;
}
~~~
找个控制器测试一下:
~~~objective-c
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSArray * array = [NSArray array];
NSLog(@"%@", array[0]);
}
~~~
可以看到预期的结果。
UITableView滑动性能影响因素
人眼能识别的帧率是60左右,这也是为什么电脑屏幕的最佳刷新帧率是60HZ。
屏幕1秒钟会刷新60次(重新渲染60次),每次刷新的处理时间就是1/60(秒)。
所以,所有会导致计算、渲染耗时的操作都会影响UITableVIew的流畅。
在主线程中执行耗时操作
这种情况不做赘述。
动态计算Cell高度运算量过大
在iOS7之前,每一个Cell的高度,只会计算一次,后面再次滑到这个Cell这里,都会读取缓存的高度,也即高度计算的代理方法不会再执行。
但是到了iOS8,不会再缓存Cell的高度了,也就是说每次滑到某个Cell,代理方法都会执行一次,重新计算这个Cell的高度(iOS 9以后没测试过)。
所以,如果计算Cell高度的这个过程过于复杂,或者某个计算使用的算法耗时很长,可能会导致计算时间大于1/60,那么必然导致界面的卡顿,或不流畅。
一种解决方法是:在Cell中定义一个public方法,用来计算Cell高度,然后计算完高度后,将高度存储在Cell对应的Model中(Model里定义一个属性来存高度),然后在渲染Cell时,我们依然需要动态计算各个子视图的高度。
更优的方案是:再定义一个ModelFrame对象,在子线程请求服务器接口返回后,转换为对象的同时,也把各个子视图的frame计算好,存在ModelFrame中,ModelFrame 和 Model 合并成一个Model存储到数组中。这样在为Cell各个子控件赋值时,仅仅是取值、赋值,在计算Cell高度时,也仅仅是加法运算。
界面汇总透明背景色视图过多
屏幕上显示的内容,是通过一个个像素点呈现出来的,每一个像素点都是通过三原色的组合才呈现出不同的颜色,最终才是我们看到的内容。
在 iPhone5 的液晶显示器上有1,136×640=727,040个像素,因此有2,181,120个颜色单元。在15寸视网膜屏的 MacBook Pro 上,这一数字达到15.5百万以上。所有的图形堆栈一起工作以确保每次正确的显示。当你滚动整个屏幕的时候,数以百万计的颜色单元必须以每秒60次的速度刷新,这是一个很大的工作量。
每一个像素点的颜色计算是这样的:
R = S + D (1 - Sa)
结果的颜色 是子视图这个像素点的颜色 + 父视图这个像素点的颜色 (1 - 子视图的透明度)
当然,如果有两个兄弟视图叠加,那么上面的中文解释可能并不贴切,只是为了更容易理解。
如果两个兄弟视图重合,计算的是重合区域的像素点:结果的颜色 是 上面的视图这个像素点的颜色 + 下面这个视图该像素点的颜色 * (1 - 上面视图的透明度)
只有当透明度为1时,上面的公式变为R = S,就简单的多了。否则的话,就非常复杂了。
每一个像素点是由三原色组成,例如父视图的颜色和透明度是(Pr,Pg,Pb,Pa),子视图的颜色颜色和透明度是(Sr,Sg,Sb,Sa),那么我们计算这个重合区域某像素点的颜色,需要先分别计算出红、绿、蓝。
Rr = Sr + Pr (1 - Sa),
Rg = Sg + Pg (1 - Sa),
Rb = Sb + Pb * (1 - Sa)。
如果父视图的透明度,即Pa = 1,那么这个像素的颜色就是(Rr,Rg,Rb)。
但是,如果父视图的透明Pa 不等 1,那么我们需要将这个结果颜色当做一个整体作为子视图的颜色,再去与父视图组合计算颜色,如此递推。
所以设置不透明时,可以为GPU节省大量的工作,减少大量的消耗。
主线程RunLoop切换到UITrackingRunLoopMode时,视图有过多的修改
这也就是上面介绍的RunLoop的使用,避免在主线程RunLoop切换到UITrackingRunLoopMode时,修改视图。
结语
纯理论往往是枯燥无味的,较为深入的更是晦涩难懂,最佳的学习方式就是所见即所得,理论指导实践,实践检验理论,逆向学习,逆向分析往往会更加轻松。 RunLoop是iOS/MacOS中基础的知识点,但却是整个系统中较为核心的部分,涉及到操作系统、端口、内核、进程(线程)通信,比较深入且复杂。
在实际开发中很少用到这些底层的知识点,我们只需要对RunLoop的理论进行一些掌握,然后可以应用RunLoop的知识进行分析、优化、实现,就足够了。