init project

添加初始项目
This commit is contained in:
DKJone
2019-01-23 11:38:18 +08:00
parent ae931fb755
commit b2ddc1ad24
40 changed files with 8411 additions and 7 deletions
@@ -0,0 +1,85 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDConfigManager.h
// MonkeyDev
//
// Created by AloneMonkey on 2018/4/24.
// Copyright © 2018年 AloneMonkey. All rights reserved.
//
#import <Foundation/Foundation.h>
#define MDCONFIG_CYCRIPT_KEY @"Cycript"
#define MDCONFIG_LOADATLAUNCH_KEY @"LoadAtLaunch"
#define MDCONFIG_TRACE_KEY @"MethodTrace"
#define MDCONFIG_LOG_LEVEL_KEY @"LogLevel"
#define MDCONFIG_LOG_WHEN_KEY @"LogWhen"
#define MDCONFIG_LOG_REGEX_STRING_KEY @"LogRegexString" // 仅LogWhen=MDTraceLogWhenRegexString有效
#define MDCONFIG_TRACE_FLAG_KEY @"TraceFlag"
#define MDCONFIG_TRACE_OBJECT_KEY @"TraceObject"
#define MDCONFIG_CLASS_REGEX_STRING_KEY @"ClassRegexString" // 仅TraceObject=MDTraceObjectRegexClass有效
#define MDCONFIG_CORE_CLASS_LIST @"CORE_CLASS_LIST"
#define MDCONFIG_USER_CLASS_LIST @"USER_CLASS_LIST"
#define MDCONFIG_TRACE_MODE_KEY @"TraceMode"
#define MDCONFIG_METHOD_WHITE_LIST_KEY @"MethodWhiteList"
#define MDCONFIG_METHOD_BLACK_LIST_KEY @"MethodBlackList"
// Trace日志级别
typedef NS_ENUM(NSUInteger, MDTraceLogLevel) {
MDTraceLogLeveError = 0, // 错误
MDTraceLogLeveDebug = 1, // 调试
MDTraceLogLeveMax
};
// Trace日志输出时机
typedef NS_ENUM(NSUInteger, MDTraceLogWhen) {
MDTraceLogWhenStartup = 0, // 启动即输出日志
MDTraceLogWhenVolume = 1, // 根据音量键控制输出日志(增加音量:输出日志;降低音量:关闭日志;默认时关闭日志)
MDTraceLogWhenRegexString = 2, // 日志包含指定正则字符串才输出日志
MDTraceLogWhenMax
};
// Trace控制位(尽量在该处扩展)
typedef NS_OPTIONS(NSUInteger, MDTraceFlag) {
MDTraceFlagDoesNotUseDescription = 1 << 0, // 跳过调用对象description方法,避免不正规的description实现导致递归
MDTraceFlagDumpClassListInfo = 1 << 1, // 打印类列表信息,便于调试
MDTraceFlagDumpClassMethod = 1 << 2, // 打印某个类的方法(不包括父类方法),便于调试
MDTraceFlagDumpSuperClassMethod = 1 << 3, // 打印某个类的父类方法(包括递归父类的方法),便于调试
MDTraceFlagMask = 0xF,
MDTraceFlagDefault = 0,
};
// Trace对象
typedef NS_ENUM(NSUInteger, MDTraceObject) {
// 屏蔽trace所有类
MDTraceObjectNone = 0,
// trace引擎指定类的方法(仅测试验证使用),仅需要考虑CORE_CLASS_LIST
MDTraceObjectCoreClass = 1,
// trace用户指定类的方法,需要考虑USER_CLASS_LIST + "USER_CLASS_LIST和CORE_CLASS_LIST交集"
MDTraceObjectUserClass = 2,
// trace用户指定类 + 正则匹配类的方法,需要考虑USER_CLASS_LIST + "USER_CLASS_LIST和CORE_CLASS_LIST交集" +
// "匹配ClassRegexString的CLASS_LIST和CORE_CLASS_LIST交集"
MDTraceObjectRegexClass = 3,
MDTraceObjectMax
};
// Trace模式
typedef NS_ENUM(NSUInteger, MDTraceMode) {
MDTraceModeOff = 0, // 屏蔽trace方法
MDTraceModeAll = 1, // trace所有方法
MDTraceModeIncludeWhiteList = 2, // trace包含"白名单方法列表"的方法
MDTraceModeExcludeBlackList = 3, // trace排除"黑名单方法列表"的方法
MDTraceModeMax
};
@interface MDConfigManager : NSObject
+ (instancetype)sharedInstance;
- (NSDictionary*)readConfigByKey:(NSString*) key;
@end
@@ -0,0 +1,48 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDConfigManager.m
// MonkeyDev
//
// Created by AloneMonkey on 2018/4/24.
// Copyright © 2018年 AloneMonkey. All rights reserved.
//
#define CONFIG_FILE_NAME @"MDConfig"
#import "MDConfigManager.h"
@implementation MDConfigManager{
NSString* _filepath;
}
+ (instancetype)sharedInstance{
static MDConfigManager *sharedInstance = nil;
if (!sharedInstance){
sharedInstance = [[MDConfigManager alloc] init];
}
return sharedInstance;
}
- (BOOL)isActive{
_filepath = [[NSBundle mainBundle] pathForResource:CONFIG_FILE_NAME ofType:@"plist"];
if(_filepath == nil){
return NO;
}
return YES;
}
- (NSDictionary*) readConfigByKey:(NSString*) key{
if([self isActive]){
NSDictionary* contentDict = [NSDictionary dictionaryWithContentsOfFile:_filepath];
if([contentDict.allKeys containsObject:key]){
return contentDict[key];
}else{
return nil;
}
}else{
return nil;
}
}
@end
@@ -0,0 +1,40 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDCycriptManager.h
// MonkeyDev
//
// Created by AloneMonkey on 2018/3/8.
// Copyright © 2018年 AloneMonkey. All rights reserved.
//
#ifndef __OPTIMIZE__
#import <Foundation/Foundation.h>
#define PORT 6666
@interface MDCycriptManager : NSObject
+ (instancetype)sharedInstance;
/**
Download script by config.plist
@param update Force update of all scripts
*/
-(void)loadCycript:(BOOL) update;
/**
eval javascript by cycript
@param cycript javascript string
@param error error happened
@return eval result
*/
-(NSString*)evaluateCycript:(NSString*) cycript error:(NSError **) error;
@end
#endif
@@ -0,0 +1,306 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDCycriptManager.m
// MonkeyDev
//
// Created by AloneMonkey on 2018/3/8.
// Copyright © 2018年 AloneMonkey. All rights reserved.
//
#ifndef __OPTIMIZE__
#import "MDCycriptManager.h"
#import "MDConfigManager.h"
#import <ifaddrs.h>
#import <arpa/inet.h>
#import <net/if.h>
#import <pthread.h>
#import <JavaScriptCore/JavaScriptCore.h>
#define IOS_CELLULAR @"pdp_ip0"
#define IOS_WIFI @"en0"
#define IP_ADDR_IPv4 @"ipv4"
#define IP_ADDR_IPv6 @"ipv6"
#define MDLog(fmt, ...) NSLog((@"[Cycript] " fmt), ##__VA_ARGS__)
extern JSGlobalContextRef CYGetJSContext(void);
extern void CydgetMemoryParse(const uint16_t **data, size_t *size);
NSString * const CYErrorLineKey = @"CYErrorLineKey";
NSString * const CYErrorNameKey = @"CYErrorNameKey";
NSString * const CYErrorMessageKey = @"CYErrorMessageKey";
@interface MDSettingObject : NSObject
@property (nonatomic, assign) NSInteger priority;
@property (nonatomic, copy) NSString* url;
@property (nonatomic, copy) NSString* content;
@property (nonatomic, assign) BOOL loadAtLaunch;
-(instancetype)initWithDicationary:(NSDictionary*) dictionary;
@end
@implementation MDSettingObject
-(instancetype)initWithDicationary:(NSDictionary *)dictionary{
self = [super init];
if(self){
self.priority = [dictionary[@"priority"] integerValue];
self.url = dictionary[@"url"];
self.content = dictionary[@"content"];
self.loadAtLaunch = [dictionary objectForKey:MDCONFIG_LOADATLAUNCH_KEY] && [dictionary[MDCONFIG_LOADATLAUNCH_KEY] boolValue];
}
return self;
}
@end
@interface MDCycriptManager()
@property (nonatomic, strong) NSDictionary* configItem;
@property (nonatomic, copy) NSString* cycriptDirectory;
@property (nonatomic, strong) NSMutableArray* downloading;
@property (nonatomic, strong) NSMutableDictionary* loadAtLaunchModules;
@end
@implementation MDCycriptManager
+ (instancetype)sharedInstance{
static MDCycriptManager *sharedInstance = nil;
if (!sharedInstance){
sharedInstance = [[MDCycriptManager alloc] init];
}
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
_loadAtLaunchModules = [NSMutableDictionary dictionary];
_downloading = [NSMutableArray array];
[self check];
[self readConfigFile];
}
return self;
}
-(void)check{
NSString* ip = [self getIPAddress];
if(ip != nil){
printf("\nDownload cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r %s:%d\n\n", [ip UTF8String], PORT);
}else{
printf("\nPlease connect wifi before using cycript!\n\n");
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
_cycriptDirectory = [documentsPath stringByAppendingPathComponent:@"cycript"];
[fileManager createDirectoryAtPath:_cycriptDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
-(NSArray*)sortedArray:(NSDictionary*) dictionary{
NSMutableArray* result = [NSMutableArray arrayWithCapacity:10];
NSArray* sortedArray = [dictionary.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSNumber* _Nonnull number1, NSNumber* _Nonnull number2) {
if ([number1 integerValue] > [number2 integerValue])
return NSOrderedDescending;
return NSOrderedAscending;
}];
for (NSNumber* item in sortedArray) {
[result addObject:dictionary[item]];
}
return [result copy];
}
-(void)readConfigFile{
MDConfigManager * configManager = [MDConfigManager sharedInstance];
_configItem = [configManager readConfigByKey:MDCONFIG_CYCRIPT_KEY];
}
-(void)loadCycript:(BOOL) update{
NSFileManager *fileManager = [NSFileManager defaultManager];
if(_configItem && _configItem.count > 0){
BOOL download = NO;
for (NSString* moduleName in _configItem.allKeys) {
MDSettingObject * item = [[MDSettingObject alloc] initWithDicationary:_configItem[moduleName]];
NSString *fullPath = [[_cycriptDirectory stringByAppendingPathComponent:moduleName] stringByAppendingPathExtension:@"cy"];
if(item.url){
if(![fileManager fileExistsAtPath:fullPath] || update){
download = YES;
[self.downloading addObject:moduleName];
[self downLoadUrl:item.url saveName:moduleName];
}
}
if(item.content){
if(![fileManager fileExistsAtPath:fullPath] || update){
NSString* writeContent = [NSString stringWithFormat:@"(function(exports) { %@ })(exports);", item.content];
[writeContent writeToFile:fullPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
}
if(item.loadAtLaunch){
[_loadAtLaunchModules setObject:fullPath forKey:@(item.priority)];
}
}
if(!download){
[self finishDownload];
}
}
}
-(void)finishDownload{
MDLog(@"Finish download all script!");
NSArray* sortedArray = [self sortedArray:_loadAtLaunchModules];
for (NSString* fullPath in sortedArray) {
NSError* error;
[self evaluateCycript:[NSString stringWithFormat:@"require('%@');",fullPath] error:&error];
if(error.code != 0){
MDLog(@"%@", error.localizedDescription);
}
}
}
-(void)downLoadUrl:(NSString*) urlString saveName:(NSString*) filename{
__weak typeof(self) weakSelf = self;
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error){
MDLog(@"Failed download script [%@]: %@", filename, error.localizedDescription);
}else{
NSString *fullPath = [[weakSelf.cycriptDirectory stringByAppendingPathComponent:filename] stringByAppendingPathExtension:@"cy"];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
MDLog(@"Successful download script [%@]", filename);
}
[weakSelf.downloading removeObject:filename];
if(!weakSelf.downloading.count){
[weakSelf finishDownload];
}
}];
[downloadTask resume];
}
-(NSString *)evaluateCycript:(NSString *)cycript error:(NSError *__autoreleasing *)error{
NSString *resultString = nil;
static pthread_mutex_t cycript_metex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&cycript_metex); {
JSGlobalContextRef context = CYGetJSContext();
size_t length = cycript.length;
unichar *buffer = malloc(length * sizeof(unichar));
[cycript getCharacters:buffer range:NSMakeRange(0, length)];
const uint16_t *characters = buffer;
CydgetMemoryParse(&characters, &length);
JSStringRef expression = JSStringCreateWithCharacters(characters, length);
// Evaluate the Javascript
JSValueRef exception = NULL;
JSValueRef result = JSEvaluateScript(context, expression, NULL, NULL, 0, &exception);
JSStringRelease(expression);
// If a result was returned, convert it into an NSString
if (result) {
JSStringRef string = JSValueToStringCopy(context, result, &exception);
if (string) {
resultString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
JSStringRelease(string);
}
}
// If an exception was thrown, convert it into an NSError
if (exception && error) {
JSObjectRef exceptionObject = JSValueToObject(context, exception, NULL);
NSInteger line = (NSInteger)JSValueToNumber(context, JSObjectGetProperty(context, exceptionObject, JSStringCreateWithUTF8CString("line"), NULL), NULL);
JSStringRef string = JSValueToStringCopy(context, JSObjectGetProperty(context, exceptionObject, JSStringCreateWithUTF8CString("name"), NULL), NULL);
NSString *name = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
JSStringRelease(string);
string = JSValueToStringCopy(context, JSObjectGetProperty(context, exceptionObject, JSStringCreateWithUTF8CString("message"), NULL), NULL);
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
JSStringRelease(string);
string = JSValueToStringCopy(context, exception, NULL);
NSString *description = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
JSStringRelease(string);
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:@(line) forKey:CYErrorLineKey];
[userInfo setValue:name forKey:CYErrorNameKey];
[userInfo setValue:message forKey:CYErrorMessageKey];
[userInfo setValue:description forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:@"CYContextDomain" code:0 userInfo:userInfo];
}
}pthread_mutex_unlock(&cycript_metex);
return resultString;
}
- (NSString *)getIPAddress{
NSDictionary *addresses = [self getIPAddresses];
if([addresses.allKeys containsObject:IOS_WIFI @"/" IP_ADDR_IPv4]){
return addresses[IOS_WIFI @"/" IP_ADDR_IPv4];
}
return nil;
}
- (NSDictionary *)getIPAddresses{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
@end
#endif
@@ -0,0 +1,30 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDMethodTrace.h
// MonkeyDev
//
// Created by AloneMonkey on 2017/9/7.
// Copyright © 2017年 AloneMonkey. All rights reserved.
//
#ifndef MethodTrace_h
#define MethodTrace_h
#define TRACE_README @"\n📚--------------------OCMethodTrace(Usage)-------------------📚\nhttps://github.com/omxcodec/OCMethodTrace/blob/master/README.md\n📚--------------------OCMethodTrace(Usage)-------------------📚"
#import <UIKit/UIKit.h>
@interface MDMethodTrace : NSObject
+ (instancetype)sharedInstance;
- (void)addClassTrace:(NSString *) className;
- (void)addClassTrace:(NSString *)className methodName:(NSString *)methodName;
- (void)addClassTrace:(NSString *)className methodList:(NSArray *)methodList;
@end
#endif /* MethodTrace_h */
@@ -0,0 +1,923 @@
// weibo: http://weibo.com/xiaoqing28
// blog: http://www.alonemonkey.com
//
// MDMethodTrace.m
// MonkeyDev
//
// Created by AloneMonkey on 2017/9/6.
// Copyright © 2017年 AloneMonkey. All rights reserved.
//
#import "MDMethodTrace.h"
#import <objc/runtime.h>
#import <AVFoundation/AVFoundation.h>
#import "MDConfigManager.h"
#import "OCMethodTrace.h"
//#define USE_DDLOG // 使用外部日志系统,日志多的时候特别有用
#ifdef USE_DDLOG
#import "CocoaLumberjack.h"
static const int ddLogLevel = DDLogLevelVerbose;
#define MDLog(fmt, ...) DDLogDebug((@"[MethodTrace] " fmt), ##__VA_ARGS__)
#else
//#define MDLog(fmt, ...) NSLog((@"[MethodTrace] " fmt), ##__VA_ARGS__)
#define MDLog(fmt, ...) printf("[MethodTrace] %s\n",[[NSString stringWithFormat:fmt, ##__VA_ARGS__] UTF8String]);
#endif
#define SAFE_CHECK(_object, _type) (_type *)[[self class] safeCheck:_object class:[_type class]]
#define MDFatal(fmt, ...) [NSException raise:@"MDMethodTrace" format:fmt, ##__VA_ARGS__]
// 指定ClassInfo源
typedef NS_ENUM(NSUInteger, MDTraceSource) {
MDTraceSourceCore = 0, // 引擎指定(引擎指OCMethodTrace内部实现框架)
MDTraceSourceUser = 1, // 用户指定
MDTraceSourceMerge = 2, // 引擎和用户合并
MDTraceSourceMax
};
////////////////////////////////////////////////////////////////////////////////
#pragma mark - Class Define
@interface MDTraceClassInfo : NSObject
@property (nonatomic, strong) NSString *name; // 类名
@property (nonatomic, assign) MDTraceSource source; // 指定源
@property (nonatomic, assign) MDTraceMode mode; // trace模式
@property (nonatomic, assign) MDTraceFlag flag; // flag
@property (nonatomic, strong) NSMutableArray *methodList; // 黑名单或者白名单,根据mode决定
// 解析类配置,无key则加key,设置默认值,并校验值类型
+ (instancetype)infoWithName:(NSString *)name source:(MDTraceSource)source config:(NSDictionary *)config;
// 根据类型返回引擎指定的默认ClassInfo
+ (instancetype)defaultCoreClassInfo:(NSString *)name;
// 根据类型返回用户指定的默认ClassInfo
+ (instancetype)defaultUserClassInfo:(NSString *)name;
// 安全检查
+ (id)safeCheck:(id)content class:(Class)cls;
// 数组相减: arrayA - arrayB
+ (NSArray *)minusWithArrayA:(NSArray *)arrayA arrayB:(NSArray *)arrayB;
// 数组相加: arrayA + arrayB,可去重,相同元素只保留一个
+ (NSArray *)unionWithArrayA:(NSArray *)arrayA arrayB:(NSArray *)arrayB;
// 合并引擎和用户ClassInfo,合并时core的优先级比user大,具体见函数内部实现
+ (MDTraceClassInfo *)mergeInfoWithCoreInfo:(MDTraceClassInfo *)coreInfo userInfo:(MDTraceClassInfo *)userInfo;
// 合并引擎和用户config
+ (MDTraceClassInfo *)mergeInfoWithName:(NSString *)name coreConfig:(NSDictionary *)coreConfig userConfig:(NSDictionary *)userConfig;
@end
@interface MDMethodTrace () <OCMethodTraceDelegate>
@property (nonatomic, strong) NSMutableDictionary *config;
@property (nonatomic, assign) MDTraceLogLevel logLevel; // 日志级别
@property (nonatomic, assign) MDTraceLogWhen logWhen; // 日志输出时机
@property (nonatomic, strong) NSString *logRegexString; // 日志正则匹配字符串,仅当logWhen=MDTraceLogWhenRegexString有效
@property (nonatomic, assign) NSInteger numberOfPendingLog; // logRegexString匹配后,待输出afer日志个数
@property (nonatomic, assign) CGFloat lastSystemVolume; // 上一次系统音量
@property (nonatomic, assign) MDTraceFlag traceFlag; // 控制trace行为的一些特殊flag
@property (nonatomic, assign) MDTraceObject traceObject; // trace对象
@property (nonatomic, strong) NSString *classRegexString; // 类正则匹配字符串
@property (nonatomic, strong) NSMutableArray *coreClassInfoList; // 引擎类信息列表
@property (nonatomic, strong) NSMutableArray *userClassInfoList; // 用户类信息列表
@property (nonatomic, strong) NSMutableArray *regexClassInfoList; // 正则类信息列表
// 解析配置文件
- (void)parseConfig:(NSDictionary *)config;
@end
////////////////////////////////////////////////////////////////////////////////
#pragma mark - MDTraceClassInfo
@implementation MDTraceClassInfo
- (instancetype)init
{
self = [super init];
if (self) {
self.name = @"NoName";
self.source = MDTraceSourceUser;
self.mode = MDTraceModeAll;
self.flag = MDTraceFlagDefault;
self.methodList = [NSMutableArray array];
}
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p name: %@ source: %tu mode: %tu flag: %tu methodList: %@>",
NSStringFromClass([self class]), self, _name, _source, _mode, _flag, _methodList];
}
+ (instancetype)infoWithName:(NSString *)name source:(MDTraceSource)source config:(NSDictionary *)config
{
// 无对应字典说明用户没有指定类,不处理
if (nil == config) {
return nil;
}
MDTraceClassInfo *info = [[MDTraceClassInfo alloc] init];
info.name = name;
info.source = source;
// 1 mode
info.mode = info.source == MDTraceSourceCore ? MDTraceModeOff: MDTraceModeAll;
if (![config valueForKey:MDCONFIG_TRACE_MODE_KEY]) {
// MDLog(@"Cannot find class %@ %@, set it to default %d", info.name, MDCONFIG_TRACE_MODE_KEY, (int)info.mode);
} else {
info.mode = [SAFE_CHECK(config[MDCONFIG_TRACE_MODE_KEY], NSNumber) integerValue];
}
// 2 flag
info.flag = MDTraceFlagDefault;
if (![config valueForKey:MDCONFIG_TRACE_FLAG_KEY]) {
// MDLog(@"Cannot find class %@ %@, set it to default %d", info.name, MDCONFIG_TRACE_FLAG_KEY, (int)info.flag);
} else {
info.flag = [SAFE_CHECK(config[MDCONFIG_TRACE_FLAG_KEY], NSNumber) integerValue];
}
// 3 whiteList
if ([config valueForKey:MDCONFIG_METHOD_WHITE_LIST_KEY]) {
id methodList = config[MDCONFIG_METHOD_WHITE_LIST_KEY];
if (![methodList isKindOfClass:[NSArray class]]) {
MDFatal(@"Class %@ %@ should be array", info.name, MDCONFIG_METHOD_WHITE_LIST_KEY);
} else if (info.mode == MDTraceModeIncludeWhiteList) {
info.methodList = methodList;
}
}
// 4 blackList
if ([config valueForKey:MDCONFIG_METHOD_BLACK_LIST_KEY]) {
id methodList = config[MDCONFIG_METHOD_BLACK_LIST_KEY];
if (![methodList isKindOfClass:[NSArray class]]) {
MDFatal(@"Class %@ %@ should be array", info.name, MDCONFIG_METHOD_BLACK_LIST_KEY);
} else if (info.mode == MDTraceModeExcludeBlackList) {
info.methodList = methodList;
}
}
return info;
}
+ (instancetype)defaultCoreClassInfo:(NSString *)name
{
MDTraceClassInfo *info = [[MDTraceClassInfo alloc] init];
info.name = name;
info.source = MDTraceSourceCore;
info.mode = MDTraceModeOff;
return info;
}
+ (instancetype)defaultUserClassInfo:(NSString *)name
{
MDTraceClassInfo *info = [[MDTraceClassInfo alloc] init];
info.name = name;
info.source = MDTraceSourceUser;
info.mode = MDTraceModeAll;
return info;
}
+ (id)safeCheck:(id)content class:(Class)cls
{
if ([content isKindOfClass:cls]) {
return content;
}
MDFatal(@"safeCheck failed, content(%@) should be class(%@) type", content, NSStringFromClass(cls));
return nil;
}
+ (NSArray *)minusWithArrayA:(NSArray *)arrayA arrayB:(NSArray *)arrayB
{
NSMutableOrderedSet *setA = [NSMutableOrderedSet orderedSetWithArray:arrayA];
NSMutableOrderedSet *setB = [NSMutableOrderedSet orderedSetWithArray:arrayB];
[setA minusOrderedSet:setB];
return [setA array];
}
+ (NSArray *)unionWithArrayA:(NSArray *)arrayA arrayB:(NSArray *)arrayB
{
NSMutableOrderedSet *setA = [NSMutableOrderedSet orderedSetWithArray:arrayA];
NSMutableOrderedSet *setB = [NSMutableOrderedSet orderedSetWithArray:arrayB];
[setA unionOrderedSet:setB];
return [setA array];
}
- (void)setSource:(MDTraceSource)source
{
NSAssert(source >= MDTraceSourceCore && source < MDTraceSourceMax, @"invalid TraceSource");
_source = source;
}
- (void)setMode:(MDTraceMode)mode
{
NSAssert(mode >= MDTraceModeOff && mode < MDTraceModeMax, @"invalid TraceMode");
_mode = mode;
}
- (void)setFlag:(MDTraceFlag)flag
{
NSAssert(flag >= MDTraceModeOff && flag <= MDTraceFlagMask, @"invalid TraceFlag");
_flag = flag;
}
- (id)copyWithZone:(NSZone *)zone
{
MDTraceClassInfo *info = [[self.class allocWithZone:zone] init];
info.name = self.name;
info.source = self.source;
info.mode = self.mode;
info.flag = self.flag;
info.methodList = [NSMutableArray arrayWithArray:self.methodList];
return info;
}
+ (MDTraceClassInfo *)mergeInfoWithCoreInfo:(MDTraceClassInfo *)coreInfo userInfo:(MDTraceClassInfo *)userInfo
{
if (nil == coreInfo && nil == userInfo) {
NSAssert(0, @"invalid info");
return nil;
}
MDTraceClassInfo *mergeInfo = nil;
if (nil != coreInfo && nil == userInfo) {
NSAssert(coreInfo.source == MDTraceSourceCore, @"invalid core source");
mergeInfo = [coreInfo copy];
} else if (nil == coreInfo && nil != userInfo) {
NSAssert(userInfo.source == MDTraceSourceUser, @"invalid user source");
mergeInfo = [userInfo copy];
} else {
NSAssert([coreInfo.name isEqualToString:userInfo.name], @"invalid name");
NSAssert(coreInfo.source == MDTraceSourceCore, @"invalid core source");
NSAssert(coreInfo.mode == MDTraceModeOff || coreInfo.mode == MDTraceModeExcludeBlackList, @"invalid core mode");
NSAssert(userInfo.source == MDTraceSourceUser, @"invalid user source");
mergeInfo = [[MDTraceClassInfo alloc] init];
mergeInfo.name = coreInfo.name;
mergeInfo.source = MDTraceSourceMerge;
mergeInfo.flag = coreInfo.flag | userInfo.flag;
// core优先级比user优先级高
if (coreInfo.mode == MDTraceModeOff) {
// core关闭则无需关心user配置
mergeInfo.mode = MDTraceModeOff;
} else if (coreInfo.mode == MDTraceModeExcludeBlackList) {
switch (userInfo.mode) {
case MDTraceModeOff:
// user关闭就不trace这个类
mergeInfo.mode = MDTraceModeOff;
break;
case MDTraceModeAll:
// 排除core指定的黑名单
mergeInfo.mode = MDTraceModeExcludeBlackList;
[mergeInfo.methodList addObjectsFromArray:coreInfo.methodList];
break;
case MDTraceModeIncludeWhiteList:
// user白名单排除掉core黑名单
mergeInfo.mode = MDTraceModeIncludeWhiteList;
[mergeInfo.methodList addObjectsFromArray:[[self class] minusWithArrayA:userInfo.methodList arrayB:coreInfo.methodList]];
break;
case MDTraceModeExcludeBlackList:
// user黑名单加上core黑名单
mergeInfo.mode = MDTraceModeExcludeBlackList;
[mergeInfo.methodList addObjectsFromArray:[[self class] unionWithArrayA:userInfo.methodList arrayB:coreInfo.methodList]];
break;
default:
break;
}
}
}
return mergeInfo;
}
+ (MDTraceClassInfo *)mergeInfoWithName:(NSString *)name coreConfig:(NSDictionary *)coreConfig userConfig:(NSDictionary *)userConfig;
{
MDTraceClassInfo *coreInfo = [MDTraceClassInfo infoWithName:name source:MDTraceSourceCore config:coreConfig];
MDTraceClassInfo *userInfo = [MDTraceClassInfo infoWithName:name source:MDTraceSourceUser config:userConfig];
return [MDTraceClassInfo mergeInfoWithCoreInfo:coreInfo userInfo:userInfo];
}
@end
////////////////////////////////////////////////////////////////////////////////
#pragma mark - MDMethodTrace
@implementation MDMethodTrace
+ (instancetype)sharedInstance {
static MDMethodTrace *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[MDMethodTrace alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.logLevel = MDTraceLogLeveDebug;
self.logWhen = MDTraceLogWhenStartup;
self.traceFlag = MDTraceFlagDefault;
self.traceObject = MDTraceObjectNone;
self.coreClassInfoList = [[NSMutableArray alloc] init];
self.userClassInfoList = [[NSMutableArray alloc] init];
self.regexClassInfoList = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc
{
#if !TARGET_OS_SIMULATOR
[self removeVolumeObserver];
#endif
}
#pragma mark - Trace utils
+ (NSString *)docPath
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
}
// 安全检查
+ (id)safeCheck:(id)content class:(Class)cls
{
if ([content isKindOfClass:cls]) {
return content;
}
MDFatal(@"safeCheck failed, content(%@) should be class(%@) type", content, NSStringFromClass(cls));
return nil;
}
// 正则匹配 optionss是否需要更多参数???
// FIXME -[NSString rangeOfString:options:NSRegularExpressionSearch]在实际运行中会死循环,不太明白原因
+ (BOOL)isMatchRegexString:(NSString *)regexString inputString:(NSString *)inputString
{
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:NSRegularExpressionDotMatchesLineSeparators | NSRegularExpressionAnchorsMatchLines error:&error];
NSTextCheckingResult *result = [regex firstMatchInString:inputString options:0 range:NSMakeRange(0, [inputString length])];
return nil != result;
}
// 获取object名称
+ (NSString *)NSStringFromTraceObject:(MDTraceObject)traceObject
{
NSString *name;
switch (traceObject) {
case MDTraceObjectNone:
name = @"未指定类";
break;
case MDTraceObjectCoreClass:
name = @"引擎类";
break;
case MDTraceObjectUserClass:
name = @"用户类";
break;
case MDTraceObjectRegexClass:
name = @"正则类";
break;
default:
NSAssert1(0, @"Unkown trace object: %tu", traceObject);
break;
}
return name;
}
// 判断object是否属于测试模式
+ (BOOL)isTestTraceObject:(MDTraceObject)traceObject
{
return (traceObject == MDTraceObjectCoreClass);
}
// 获取当前classInfoList
- (NSArray *)classInfoList
{
if (self.traceObject == MDTraceObjectCoreClass) {
return self.coreClassInfoList;
} else if (self.traceObject == MDTraceObjectUserClass) {
return self.userClassInfoList;
} else if (self.traceObject == MDTraceObjectRegexClass) {
return self.regexClassInfoList;
}
return nil;
}
// 查找某个类是否存在classInfoList中
- (MDTraceClassInfo *)infoInClassInfoList:(NSString *)className
{
for (MDTraceClassInfo *info in [self classInfoList]) {
if ([info.name isEqualToString:className]) {
return info;
}
}
return nil;
}
// dump输出ClassListInfo
- (void)dumpClassListInfo
{
if (!(self.traceFlag & MDTraceFlagDumpClassListInfo)) {
return;
}
MDLog(@"Dump %@ class list info: ", [[self class] NSStringFromTraceObject:self.traceObject]);
MDLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
NSArray *classInfoList = [self classInfoList];
for (int i = 0; i < classInfoList.count; i++) {
MDLog(@"ClassList[%05d]: %@", i, classInfoList[i]);
}
MDLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
// dump类所有方法信息
- (void)dumpClassMethodInfo:(MDTraceClassInfo *)classInfo
{
if (!(self.traceFlag & MDTraceFlagDumpClassMethod || classInfo.flag & MDTraceFlagDumpClassMethod)) {
return;
}
MDLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Class cls = NSClassFromString(classInfo.name);
while (nil != cls) {
unsigned int numMethods = 0;
Method *methodList = class_copyMethodList(cls, &numMethods);
if (NULL != methodList) {
for (unsigned int i = 0; i < numMethods; i ++) {
MDLog(@"-Class[%@] method[%03d]: %@", cls, i, [[self class] methodInfo:methodList[i]]);
}
}
Class metaClass = object_getClass(cls);
methodList = class_copyMethodList(metaClass, &numMethods);
if (NULL != methodList) {
for (unsigned int i = 0; i < numMethods; i ++) {
MDLog(@"+Class[%@] method[%03d]: %@", cls, i, [[self class] methodInfo:methodList[i]]);
}
}
if (!(self.traceFlag & MDTraceFlagDumpSuperClassMethod || classInfo.flag & MDTraceFlagDumpSuperClassMethod)) {
break;
}
cls = [cls superclass];
}
MDLog(@"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
// 某方法信息
+ (NSString *)methodInfo:(Method)method
{
return [NSString stringWithFormat:@"name[%s] IMP[%p]", sel_getName(method_getName(method)), method_getImplementation(method)];
}
#pragma mark - Trace log
// 初始化日志系统
- (void)initLogger
{
#ifdef USE_DDLOG
// 外部Lumberjack logger, 日志存放于目录"~/Library/Caches/Logs"
[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode 控制台
// [[DDTTYLogger sharedInstance] setColorsEnabled:YES];
// [DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System Logs 苹果系统日志
DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // 本地文件日志
fileLogger.doNotReuseLogFiles = YES;
fileLogger.logFileManager.maximumNumberOfLogFiles = 1; // 永远创建一个文件,便于观察日志
fileLogger.maximumFileSize = 0;
fileLogger.rollingFrequency = 0;
[DDLog addLogger:fileLogger];
#endif
// 内部logger
[[OCMethodTrace sharedInstance] setLogLevel:self.logLevel == MDTraceLogLeveError ? OMTLogLevelError : OMTLogLevelDebug];
[[OCMethodTrace sharedInstance] setDelegate:self];
}
// trace开关
- (void)enableTrace:(BOOL)enable
{
if (!enable) {
[OCMethodTrace sharedInstance].disableTrace = YES;
} else {
if (self.logWhen == MDTraceLogWhenVolume) {
#if TARGET_OS_SIMULATOR
[OCMethodTrace sharedInstance].disableTrace = NO;
self.logWhen = MDTraceLogWhenStartup;
MDLog(@"Volume control log is not supported when simulator, reset logWhen to %tu", self.logWhen);
#else
self.lastSystemVolume = [[AVAudioSession sharedInstance] outputVolume];
// 需要异步注册通知,否则无法工作
dispatch_async(dispatch_get_main_queue(), ^{
[self addVolumeObserver];
});
#endif
} else {
[OCMethodTrace sharedInstance].disableTrace = NO;
}
}
}
#if !TARGET_OS_SIMULATOR
- (void)addVolumeObserver
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onSystemVolumeChanged:) name:@"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)removeVolumeObserver
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:@"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
- (void)onSystemVolumeChanged:(NSNotification *)notification
{
NSString *category = notification.userInfo[@"AVSystemController_AudioCategoryNotificationParameter"];
NSString *changeReason = notification.userInfo[@"AVSystemController_AudioVolumeChangeReasonNotificationParameter"];
if (![category isEqualToString:@"Audio/Video"] || ![changeReason isEqualToString:@"ExplicitVolumeChange"]) {
return;
}
CGFloat volume = [[notification userInfo][@"AVSystemController_AudioVolumeNotificationParameter"] floatValue];
[OCMethodTrace sharedInstance].disableTrace = volume < self.lastSystemVolume; // 音量升高触发日志输出
self.lastSystemVolume = volume;
}
#endif
#pragma mark - Trace parse config
- (void)parseConfig:(NSDictionary *)config
{
if (nil == config) {
return;
}
MDLog(TRACE_README);
self.config = [NSMutableDictionary dictionaryWithDictionary:config];
self.logLevel = [SAFE_CHECK(self.config[MDCONFIG_LOG_LEVEL_KEY], NSNumber) unsignedIntegerValue];
self.logWhen = [SAFE_CHECK(self.config[MDCONFIG_LOG_WHEN_KEY], NSNumber) unsignedIntegerValue];
self.traceFlag = [SAFE_CHECK(self.config[MDCONFIG_TRACE_FLAG_KEY], NSNumber) unsignedIntegerValue];
self.traceObject = [SAFE_CHECK(self.config[MDCONFIG_TRACE_OBJECT_KEY], NSNumber) unsignedIntegerValue];
MDLog(@"logLevel: %tu: logWhen: %tu traceFlag: %tu traceObject: %tu(%@%@)",
self.logLevel, self.logWhen, self.traceFlag, self.traceObject,
[[self class] NSStringFromTraceObject:self.traceObject],
[[self class] isTestTraceObject:self.traceObject] ? @"(仅测试验证使用,不建议开启)" : @"");
// 检查配置有效性
[self checkConfig];
// 初始化日志
[self initLogger];
// 屏蔽trace输出。避免hook准备过程中导致的递归调用
[self enableTrace:NO];
// 实际hook类的各种方法
[self traceClassObject];
// 恢复trace输出
[self enableTrace:YES];
}
- (void)checkConfig
{
NSAssert(self.logLevel >= MDTraceLogLeveError && self.logLevel < MDTraceLogLeveMax, @"invalid loglevel");
NSAssert(self.logWhen >= MDTraceLogWhenStartup && self.logWhen < MDTraceLogWhenMax, @"invalid logWhen");
NSAssert(self.traceFlag >= 0 && self.traceFlag <= MDTraceFlagMask, @"invalid traceFlag");
NSAssert(self.traceObject >= MDTraceObjectNone && self.traceObject < MDTraceObjectMax, @"invalid traceObject");
if (self.logWhen == MDTraceLogWhenRegexString) {
self.logRegexString = SAFE_CHECK(self.config[MDCONFIG_LOG_REGEX_STRING_KEY], NSString);
if (self.logRegexString.length == 0) {
MDFatal(@"LogRegexString is nil");
}
MDLog(@"LogRegexString: %@", self.logRegexString);
}
if (self.traceObject == MDTraceObjectRegexClass) {
self.classRegexString = SAFE_CHECK(self.config[MDCONFIG_CLASS_REGEX_STRING_KEY], NSString);
if (self.classRegexString.length == 0) {
MDFatal(@"ClassRegexString is nil");
}
MDLog(@"ClassRegexString: %@", self.classRegexString);
}
}
- (void)traceClassObject
{
if (self.traceObject == MDTraceObjectNone) {
MDLog(@"Method Trace is disabled");
return;
}
[self parseClassListInfo];
[self dumpClassListInfo];
[self traceClassListInfo];
}
- (void)parseClassListInfo
{
id object = nil;
NSDictionary *coreClassListDict = nil;
NSDictionary *userClassListDict = nil;
NSMutableArray *classList = [[NSMutableArray alloc] init];
NSMutableArray *regexClassList = [[NSMutableArray alloc] init];
object = [self.config valueForKey:MDCONFIG_CORE_CLASS_LIST];
if (nil != object) {
coreClassListDict = SAFE_CHECK(object, NSDictionary);
}
object = [self.config valueForKey:MDCONFIG_USER_CLASS_LIST];
if (nil != object) {
userClassListDict = SAFE_CHECK(object, NSDictionary);
}
// 1 获取真实存在的类
int numberOfClasses = objc_getClassList(NULL, 0);
Class *tmpClassList = (Class *)malloc(sizeof(Class) * numberOfClasses);
if (NULL != tmpClassList) {
objc_getClassList(tmpClassList, numberOfClasses);
for (int i = 0; i < numberOfClasses; i++) {
NSString *className = NSStringFromClass(tmpClassList[i]);
[classList addObject:className];
if (self.traceObject == MDTraceObjectRegexClass && [[self class] isMatchRegexString:self.classRegexString inputString:className]) {
// 多次匹配可能是重复类,需要去重
if (![regexClassList containsObject:className]) {
[regexClassList addObject:className];
}
}
}
free(tmpClassList);
}
// 2 获取classInfo
if (self.traceObject == MDTraceObjectCoreClass) {
// 2.1 core
for (NSString *className in coreClassListDict.allKeys) {
if (![classList containsObject:className]) {
MDLog(@"Cannot find class %@", className);
continue;
}
NSDictionary *coreConfig = coreClassListDict[className];
MDTraceClassInfo *coreInfo = [MDTraceClassInfo infoWithName:className source:MDTraceSourceCore config:coreConfig];
[self.coreClassInfoList addObject:coreInfo];
}
} else if (self.traceObject == MDTraceObjectUserClass) {
// 2.2 user,注意,user需要跟core合并,优先级:core > user
for (NSString *className in userClassListDict.allKeys) {
if (![classList containsObject:className]) {
MDLog(@"Cannot find class %@", className);
continue;
}
NSDictionary *coreConfig = coreClassListDict[className];
NSDictionary *userConfig = userClassListDict[className];
MDTraceClassInfo *mergeinfo = [MDTraceClassInfo mergeInfoWithName:className coreConfig:coreConfig userConfig:userConfig];
[self.userClassInfoList addObject:mergeinfo];
}
} else if (self.traceObject == MDTraceObjectRegexClass) {
// 2.3 regex, 优先级:core > user > regex
// 2.3.1 user需要跟core合并,优先级:core > user
for (NSString *className in userClassListDict.allKeys) {
if (![classList containsObject:className]) {
MDLog(@"Cannot find class %@", className);
continue;
}
NSDictionary *coreConfig = coreClassListDict[className];
NSDictionary *userConfig = userClassListDict[className];
MDTraceClassInfo *mergeinfo = [MDTraceClassInfo mergeInfoWithName:className coreConfig:coreConfig userConfig:userConfig];
[self.regexClassInfoList addObject:mergeinfo];
}
// 2.3.2 regex需要跟core合并,优先级:core > regex。regex匹配的类可理解成更低优先级的user类
NSDictionary *defaultUserConfig = [[NSDictionary alloc] init];
for (NSString *className in regexClassList) {
if (nil == [self infoInClassInfoList:className]) {
NSDictionary *coreConfig = coreClassListDict[className];
MDTraceClassInfo *mergeinfo = [MDTraceClassInfo mergeInfoWithName:className coreConfig:coreConfig userConfig:defaultUserConfig];
[self.regexClassInfoList addObject:mergeinfo];
}
}
}
}
- (void)traceClassListInfo
{
MDLog(@" ");
MDLog(@"////////////////////////////////////////////////////////////////////////////////");
MDLog(@" ");
NSArray *classInfoList = [self classInfoList];
for (int i = 0; i < classInfoList.count; i++) {
MDTraceClassInfo *info = classInfoList[i];
if (nil != objc_getClass([info.name UTF8String])) {
MDLog(@"ClassList[%05d]: %@", i, info.name);
[self traceClass:info];
} else {
MDLog(@"Cannot find class %@", info.name);
}
}
MDLog(@" ");
MDLog(@"////////////////////////////////////////////////////////////////////////////////");
MDLog(@" ");
}
- (void)traceClass:(MDTraceClassInfo *)classInfo
{
if (nil == classInfo) {
return;
}
[self dumpClassMethodInfo:classInfo];
if (classInfo.mode == MDTraceModeOff) {
} else if (classInfo.mode == MDTraceModeAll) {
[self addClassTrace:classInfo.name];
} else if (classInfo.mode == MDTraceModeIncludeWhiteList) {
[self addClassTrace:classInfo.name methodList:classInfo.methodList white:YES];
} else if (classInfo.mode == MDTraceModeExcludeBlackList) {
[self addClassTrace:classInfo.name methodList:classInfo.methodList white:NO];
}
[self dumpClassMethodInfo:classInfo];
}
#pragma mark - Trace method
- (void)addClassTrace:(NSString *)className{
[self addClassTrace:className methodList:nil];
}
- (void)addClassTrace:(NSString *)className methodName:(NSString *)methodName {
[self addClassTrace:className methodList:@[methodName]];
}
- (void)addClassTrace:(NSString *)className methodList:(NSArray*)methodList {
[self addClassTrace:className methodList:methodList white:YES];
}
- (void)addClassTrace:(NSString *)className methodList:(NSArray *)methodList white:(BOOL)white {
Class targetClass = objc_getClass([className UTF8String]);
if (targetClass != nil) {
[[OCMethodTrace sharedInstance] traceMethodWithClass:NSClassFromString(className) condition:^BOOL(SEL sel) {
if (methodList == nil || methodList.count == 0) {
return YES;
}
for (id object in methodList) {
NSString *methodName = SAFE_CHECK(object, NSString);
// 方法可以是正则表达式
if ([[self class] isMatchRegexString:methodName inputString:NSStringFromSelector(sel)]) {
return white;
}
}
return !white;
} before:^(id target, Class cls, SEL sel, NSArray *args, int deep) {
NSString *selector = NSStringFromSelector(sel);
NSMutableString *selectorString = [NSMutableString new];
if ([selector containsString:@":"]) {
NSArray *selectorArrary = [selector componentsSeparatedByString:@":"];
selectorArrary = [selectorArrary filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
for (int i = 0; i < selectorArrary.count; i++) {
[selectorString appendFormat:@"%@:%@ ", selectorArrary[i], args[i]];
}
} else {
[selectorString appendString:selector];
}
NSMutableString *deepString = [NSMutableString new];
for (int i = 0; i < deep; i++) {
[deepString appendString:@"-"];
}
// [obj class]则分两种情况:
// 1 当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;
// 2 当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。
NSString *prefix = target == [target class] ? @"+" : @"-";
// target不是强引用,如果打印接口异步,可能未实际调用description就被释放了,所以提前获取desc,保证线程安全
NSString *description = [self descriptionWithTarget:target class:cls selector:sel targetPosition:OMTTargetPositionBeforeSelf];
NSString *logString = nil;
if ([target class] != [cls class]) {
// 如果是子类调用基类方法,则()内打印基类名
logString = [NSString stringWithFormat:@"%@%@[%@(%@) %@]", deepString, prefix, description, NSStringFromClass(cls), selectorString];
} else {
logString = [NSString stringWithFormat:@"%@%@[%@ %@]", deepString, prefix, description, selectorString];
}
if (self.logWhen == MDTraceLogWhenStartup ||
self.logWhen == MDTraceLogWhenVolume ||
(self.logWhen == MDTraceLogWhenRegexString && [[self class] isMatchRegexString:self.logRegexString inputString:logString])) {
self.numberOfPendingLog++;
MDLog(@"%@", logString);
}
} after:^(id target, Class cls, SEL sel, id ret, int deep, NSTimeInterval interval) {
// 因为多线程并发,numberOfPendingLog变量维护的输出状态有可能并不是那么准,但是打印调试可以容忍
if (self.numberOfPendingLog > 0) {
self.numberOfPendingLog--;
NSMutableString *deepString = [NSMutableString new];
for (int i = 0; i < deep; i++) {
[deepString appendString:@"-"];
}
NSString *prefix = target == [target class] ? @"+" : @"-";
MDLog(@"%@%@ret:%@", deepString, prefix, ret);
}
}];
} else {
MDLog(@"Canot find class %@", className);
}
}
#pragma mark - OCMethodTraceDelegate
- (NSString *)descriptionWithTarget:(id)target class:(Class)cls selector:(SEL)sel targetPosition:(OMTTargetPosition)targetPosition
{
if (nil == target) {
return @"nil";
}
NSString *targetClassName = NSStringFromClass([target class]);
// 全局跳过对象description方法
if (self.traceFlag & MDTraceFlagDoesNotUseDescription) {
return [NSString stringWithFormat:@"<%@: %p>", targetClassName, target];
}
// 类跳过对象description方法,粒度更小一点
MDTraceClassInfo *info = [self infoInClassInfoList:targetClassName];
BOOL doesNotUseDescription = (nil != info && info.flag & MDTraceFlagDoesNotUseDescription);
if (!doesNotUseDescription) {
// 构造初始化函数特殊处理, 系统类初始化比较喜欢"_init"这样的方式
NSString *selectorName = NSStringFromSelector(sel);
BOOL isAllocFunc = [selectorName hasPrefix:@"new"] || [selectorName hasPrefix:@"alloc"];
BOOL maybeInitFunc = [selectorName hasPrefix:@"init"] || [selectorName hasPrefix:@"_init"];
BOOL isDeallocFunc = [selectorName isEqualToString:@"dealloc"];
if (isAllocFunc || maybeInitFunc) {
switch (targetPosition) {
case OMTTargetPositionBeforeSelf:
// 调用构造函数时,此时实例还没初始化完全,不能调用description
doesNotUseDescription = YES;
break;
case OMTTargetPositionBeforeArgument:
break;
case OMTTargetPositionAfterSelf:
case OMTTargetPositionAfterReturnValue:
if (isAllocFunc) {
doesNotUseDescription = YES;
} else if (maybeInitFunc) {
// 调用构造函数时,可能是调用父类的构造函数,此时实例还没初始化完全,不能调用description
if (![targetClassName isEqualToString:NSStringFromClass(cls)]) {
doesNotUseDescription = YES;
}
}
break;
default:
break;
}
} else if (isDeallocFunc) {
// 析构函数所有只打印指针,因为dealloc after时,对象已被释放
doesNotUseDescription = YES;
}
}
return doesNotUseDescription ? [NSString stringWithFormat:@"<%@: %p>", targetClassName, target] : [target description];
}
- (void)log:(OMTLogLevel)level format:(NSString *)format, ...
{
va_list args;
if (format) {
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
MDLog(@"%@", message);
}
}
@end
////////////////////////////////////////////////////////////////////////////////
#pragma mark - Entry
static __attribute__((constructor)) void entry()
{
NSDictionary *config = [[MDConfigManager sharedInstance] readConfigByKey:MDCONFIG_TRACE_KEY];
[[MDMethodTrace sharedInstance] parseConfig:config];
}