mirror of
https://github.com/DKJone/DKWechatHelper.git
synced 2026-07-29 06:12:07 +08:00
init project
添加初始项目
This commit is contained in:
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user