Aspects
Aspects这个第三方相信大家都有所运用,简单讲Aspects是利用切面编程的思想去hook的实例方法,然后返回一个可操作的block;下面我将针对Aspects的一些重要的源码进行解释;
基于NSObject的分类
Aspects是NSObject的分类。基于NSObject当然是基于万物皆为对象,分类的话可以在不破坏源代码的基础上实现功能。
@interface NSObject (Aspects)
aspect_add()
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
//空值判断
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
//__block 修饰,用于blockl内部可以操作该对象
__block AspectIdentifier *identifier = nil;
//aspect_performLocked 内部是一把os_unfair_lock,用于保证block内部的线程安全
aspect_performLocked(^{
//内部判断对象,方法,options,是否满足条件
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
//使用关联对象给当前self添加一个AspectsContainer对象,内部是保存3不同操作时机(AspectPositionAfter,AspectPositionInstead,AspectPositionBefore)的AspectIdentifier对象的集合的容器
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
//封装selector 对象(self)options block error的一个对象,方便加入aspectContainer容器中
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
//加入到aspectContainer容器
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
//内部使用class_replaceMethod()去 hook原来的实例方法;
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
AspectIdentifier
- 封装 selector 对象(self) options block
-
@property (nonatomic, weak) id object;
是一个弱引用,防止循环引用
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
NSCParameterAssert(block);
NSCParameterAssert(selector);
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
return nil;
}
AspectIdentifier *identifier = nil;
if (blockSignature) {
identifier = [AspectIdentifier new];
identifier.selector = selector;
identifier.block = block;
identifier.blockSignature = blockSignature;
identifier.options = options;
identifier.object = object; // weak
}
return identifier;
}
aspect_blockMethodSignature获取block方法签名
由block(block_layout).desc内存偏移得到signature
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
if (!(layout->flags & AspectBlockFlagsHasSignature)) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
if (!desc) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
只对实例方法进行hook
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
NSCParameterAssert(blockSignature);
NSCParameterAssert(object);
NSCParameterAssert(selector);
BOOL signaturesMatch = YES;
NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
...
...
...
return YES;
}
根据options的类型放入集合
根据options的类型放入三个不同的数组中
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
NSParameterAssert(aspect);
NSUInteger position = options&AspectPositionFilter;
switch (position) {
case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
}
}
切面编程
像Aspects这种,对原生代码不会去破坏,有很容易进行统一的修改,并且业务代码被统一放在了一个block中,这种编程就是切面编程AOP;
切面编程的运用
- 参数校验:网络请求前的参数校验,返回数据格式的校验等
- 无痕埋点:统一处理埋点,降低代码耦合度
- 页面统计:帮助统计页面的访问量
- 事物处理:拦截指定事件,添加出发事件
- 异常处理:发生异常时,可以捕获异常进行处理
- 热修复:AOP可以在在方法的前后直接替换另一行代码,根据这个思想我们可以实现bug修复
- 剥离非业务逻辑代码:一些非业务逻辑的代码可以不放在原生代码中
埋点
针对特定用户或时间进行捕获、处理和发送的相关技术及实行过程即为埋点。比如用户某个button的点击次数,观看某个视频的时长等等。而在代码层,埋点的实质即监听软件运行过程中的事件,对需要关注的事件进行判断和捕获。
埋点种类
- 代码埋点:用预先写好的代码埋在事件交互的代码中发送数据。
- 可视化埋点:用可视化的方法来代替代码埋点把代码和业务逻辑分开。举个例子,像游戏公司现在开发会把资源文件和配置信息和代码分开,用户更新游戏只要下载资源文件和配置就可以了。代表:MixPanel
- 无侵入埋点:和可视化埋点类似,二者的区别是可视化埋点通过界面配置来决定要统计的事件来源,而无埋点是经可能把所有能收集的数据全部收集一遍,再通过后台配置要留下哪些统计分析。代表:sensorsdata
MixPanel
原理:
- 通过手势请求web创建连接(匹配设备)
- 连接成功后将信息发送给web
- 手机将截图发送给服务器用于投屏
- 生成websocket长连接,用于持续投屏
- 将web上的操作返回给手机,打印日志
crash 捕获
-
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
使用系统接口去捕获崩溃信息
#import "LGUncaughtExceptionHandle.h"
#import <SCLAlertView.h>
#import <UIKit/UIKit.h>
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#include <stdatomic.h>
NSString * const LGUncaughtExceptionHandlerSignalExceptionName = @"LGUncaughtExceptionHandlerSignalExceptionName";
NSString * const LGUncaughtExceptionHandlerSignalExceptionReason = @"LGUncaughtExceptionHandlerSignalExceptionReason";
NSString * const LGUncaughtExceptionHandlerSignalKey = @"LGUncaughtExceptionHandlerSignalKey";
NSString * const LGUncaughtExceptionHandlerAddressesKey = @"LGUncaughtExceptionHandlerAddressesKey";
NSString * const LGUncaughtExceptionHandlerFileKey = @"LGUncaughtExceptionHandlerFileKey";
NSString * const LGUncaughtExceptionHandlerCallStackSymbolsKey = @"LGUncaughtExceptionHandlerCallStackSymbolsKey";
atomic_int LGUncaughtExceptionCount = 0;
const int32_t LGUncaughtExceptionMaximum = 8;
const NSInteger LGUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger LGUncaughtExceptionHandlerReportAddressCount = 5;
@implementation LGUncaughtExceptionHandle
/// Exception
void LGExceptionHandlers(NSException *exception) {
NSLog(@"%s",__func__);
// 收集 - 上传
int32_t exceptionCount = atomic_fetch_add_explicit(&LGUncaughtExceptionCount,1,memory_order_relaxed);
if (exceptionCount > LGUncaughtExceptionMaximum) {
return;
}
// 获取堆栈信息 - model 编程思想
NSArray *callStack = [LGUncaughtExceptionHandle lg_backtrace];
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:[exception userInfo]];
[userInfo setObject:exception.name forKey:LGUncaughtExceptionHandlerSignalExceptionName];
[userInfo setObject:exception.reason forKey:LGUncaughtExceptionHandlerSignalExceptionReason];
[userInfo setObject:callStack forKey:LGUncaughtExceptionHandlerAddressesKey];
[userInfo setObject:exception.callStackSymbols forKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];
[userInfo setObject:@"LGException" forKey:LGUncaughtExceptionHandlerFileKey];
[[[LGUncaughtExceptionHandle alloc] init]
performSelectorOnMainThread:@selector(lg_handleException:)
withObject: [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo] waitUntilDone:YES];
}
+ (void)installUncaughtSignalExceptionHandler{
NSSetUncaughtExceptionHandler(&LGExceptionHandlers);
}
- (void)lg_handleException:(NSException *)exception{
NSDictionary *dict = [exception userInfo];
[self saveCrash:exception file:[dict objectForKey:LGUncaughtExceptionHandlerFileKey]];
// 网络上传 - flush
// 用户奔溃
// runloop 起死回生
CFRunLoopRef runloop = CFRunLoopGetCurrent();
// 跑圈依赖 - mode
CFArrayRef allmodes = CFRunLoopCopyAllModes(runloop);
SCLAlertView *alert = [[SCLAlertView alloc] initWithNewWindowWidth:300.0f];
[alert addButton:@"请你奔溃" actionBlock:^{
self.dismissed = YES;
}];
[alert showSuccess:exception.name subTitle:exception.reason closeButtonTitle:nil duration:0.0f];
// 起死回生
while (!self.dismissed) {
for (NSString *mode in (__bridge NSArray *)allmodes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.0001, false);
}
}
CFRelease(runloop);
}
/// 保存奔溃信息或者上传
- (void)saveCrash:(NSException *)exception file:(NSString *)file{
NSArray *stackArray = [[exception userInfo] objectForKey:LGUncaughtExceptionHandlerCallStackSymbolsKey];// 异常的堆栈信息
NSString *reason = [exception reason];// 出现异常的原因
NSString *name = [exception name];// 异常名称
// 或者直接用代码,输入这个崩溃信息,以便在console中进一步分析错误原因
// NSLog(@"crash: %@", exception);
NSString * _libPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:file];
if (![[NSFileManager defaultManager] fileExistsAtPath:_libPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:_libPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSDate *dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970];
NSString *timeString = [NSString stringWithFormat:@"%f", a];
NSString * savePath = [_libPath stringByAppendingFormat:@"/error%@.log",timeString];
NSString *exceptionInfo = [NSString stringWithFormat:@"Exception reason:%@\nException name:%@\nException stack:%@",name, reason, stackArray];
BOOL sucess = [exceptionInfo writeToFile:savePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSLog(@"保存崩溃日志 sucess:%d,%@",sucess,savePath);
}
/// 获取函数堆栈信息
+ (NSArray *)lg_backtrace{
void* callstack[128];
int frames = backtrace(callstack, 128);//用于获取当前线程的函数调用堆栈,返回实际获取的指针个数
char **strs = backtrace_symbols(callstack, frames);//从backtrace函数获取的信息转化为一个字符串数组
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = LGUncaughtExceptionHandlerSkipAddressCount;
i < LGUncaughtExceptionHandlerSkipAddressCount+LGUncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
@end
网友评论