导语
在经历了用NSLog来记录日志的写法后,我意识到一个问题,如果当你在项目里想要用NSLog输出一个当时你想要的计算结果,而这个计算结果又没有必要输出的时候它却无可避免的写在了你的日志里。久而久之,你所记录的日志内容有时候变的完全分不清楚。
这里是个过渡
我们来记录项目中某个页面(需记录页面的中文名)出现的时间以及消失的时间。因为考虑到在每个页面的-(void)viewWillAppear:方法和-(void)viewWillDisappear:方法中重复书写记录时间的代码很繁琐,所以就想到了继承。
1--如何使用继承
我们现在的XXXViewController都是直接继承自UIViewController,本来直接在基类里面重写-(void)viewWillAppear:和-(void)viewWillDisappear:两个方法即可,但因UIViewController这个类不是开源的,所以我们需要重新写一个类:DBViewController,使它继承自UIViewController,在DBViewController中重写-(void)viewWillAppear:和-(void)viewWillDisappear:两个方法来记录时间,并使得之后的每个控制器都继承自DBViewController即可。
这么写的结局就是:在某个午后你看见我打开了项目中的每个ViewController开始改它继承自DBViewController。。
2--runtime登场
在我不厌其烦的嫌弃自己之前的记录想法的情况下,我慢慢的发现好像运行时不仅好玩,还能简单的实现我的需求,那么搞起来!
简单的利用方法交换就可以实现,首先抽出这样一个方法
void LXMethodSwizzling(Class cls, SEL originalSel, SEL swizzledSel)
{
//两个方法的Method
Method originalMethod = class_getInstanceMethod(cls, originalSel);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);
//方法体
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *swizzledTypes = method_getTypeEncoding(swizzledMethod);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL didAddMethod = class_addMethod(cls, originalSel, swizzledIMP, swizzledTypes);
if (didAddMethod) {
//如果成功,说明类中不存在这个方法的实现
method_setImplementation(swizzledMethod, originalIMP);
} else {
//否则,交换两个方法的实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
抽出方法的好处在于,当需要交换多个方法的时候可以直接调用。
通过了解,了解到将这个实现写成UIViewController的一个分类是最好的选择,并且在分类的+(void)load方法中做交换最为合适,因为这个类方法会在调用每一个类的时候都走一遍,对于我们的需求正合适。
于是,就是这样:
+ (void)load
{
map = [NSMutableDictionary dictionaryWithCapacity:4];
// static dispatch_once_t onceToken;
// dispatch_once(&onceToken, ^{
//获得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
LXMethodSwizzling([self class], systemSel, swizzSel);
//获得viewController的生命周期方法的selector
SEL systemSel1 = @selector(viewWillDisappear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel1 = @selector(swiz_viewWillDisappear:);
LXMethodSwizzling([self class], systemSel1, swizzSel1);
}
当然在这个分类中需要实现- (void)swiz_viewWillAppear:和- (void)swiz_viewWillDisappear:两个用于对应交换的方法。
在这里需要注意的一点是,虽然时间的记录很好实现,但是想要将ViewController的名字和对应的描述对应上就必须有个配置文件与其匹配,当然可以将匹配这一步放在服务器对应。我这里采用的是将这个配置文件放在plist文件,像这样:
就可以在对应的方法中去遍历这个plist文件去对应描述,像这样:
- (void)swiz_viewWillAppear:(BOOL)animate
{
NSLog(@"%@这是哪个的类%@",[self class], @(__FUNCTION__));
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"VCPlist" ofType:@"plist"];
NSMutableDictionary *VCData = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
for (NSString *VCClassStr in VCData)
{
if ([VCClassStr isEqualToString:NSStringFromClass([self class])])
{
[map setObject:NSStringFromClass([self class]) forKey:@"VCName"];
[map setObject:VCData[VCClassStr] forKey:@"description"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *dateStr = [dateFormatter stringFromDate:[NSDate date]];
[map setObject:dateStr forKey:@"startTime"];
}
}
// 调用原始实现 viewWillAppear:
[self swiz_viewWillAppear:animate];
}
这样的写法保证了在执行当前的实现方法的同时,也执行了原始的viewWillAppear:方法。在- (void)swiz_viewWillDisappear:的方法中也是类似的写法,只是多了一步将所记录的信息写入文件。在“iOS日志相关PART1”中已经记录过,这里将不进行赘述。
值得提及的一点
在“iOS日志相关PART1”中记录的写入日志没有仔细的摸索写入文件的好的办法,在实现的过程中发现这次写入会将上次记录的文件覆盖,对于这个问题我找到了苹果提供的这样一个类NSFileHandle
NSFileHandle : NSObject <NSSecureCoding>
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
[fileHandle seekToEndOfFile]; //将节点跳到文件的末尾
NSData *mapData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
[fileHandle writeData:mapData];
[fileHandle closeFile];
使用这个很好的解决了这个问题,每次记录的时候都会打开文件,并将节点跳转至文件的末尾开始记录。
终于完事了,简直要写吐了。。