最近在整理项目逻辑的时候,发现一个问题:就是打点统计,经常和代码业务逻辑混在了一起,耦合性很强,并且经常容易出错。于是就在思考怎样对这一块进行优化。
其实,对这方面的讨论一直也比较多,比如继承基类,但是这样很容易使代码变得臃肿。另一个比较好的办法就是利用method swizzling, hook住需要打点的方法,将打点统计从业务逻辑中分离出来,而且额外工作量不大。最后就想从这方面去尝试,当然并没有自己造轮子,而是借用了github上的一个开源库,Aspects。这个库的代码量比较小,总共就一个类文件,使用起来也比较方便,比如你想统计某个controller的viewwillappear的调用次数,你只需要引入Aspect.h头文件,然后在合适的地方初始化如下代码即可。
#pragma mark - addKvLogAspect
- (void)addKvLogAspect {
//想法tab打开
[self aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
//统计打点
NSLog(@"");
}error:NULL];
}
看到这段代码大家应该有所感觉了,没错,它基本上就是基于method swizzling实现的。本篇文章暂时并不打算对aspects的代码进行解析(以后,可能会写一篇这样的文字),在这里就简单的记录一下我个人对于method swizzling的理解。
ios开发人员都知道,oc是一门动态语言。这个动态性怎么理解呢,知乎上有网友这么总结过:
2. Class在objc中是动态创建的, selector, method, imp, protocol等都是随后绑定上去的(即所谓的运行时绑定).
3. 通过runtime能够查出当前运行时环境中所有的类, 每个类中的方法, 每个类消息的绑定, 每个类的实现的协议, 每个协议的定义, 每个类当前的消息缓存等一切你想知道的东西.
4. 类的方法(消息)调用是间接的.
typedef struct objc_class *Class;
它其实是一个指向objc_class的指针,结构体如下:
truct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
这里也就是我们说的id对象,oc里面所有的对象都能用id表示。
SEL sel = @selector(method);
- (void)setDimension:(NSInteger)dimension {
}
- (void)setDimension:(float)dimension {
}
会提示Duplicate declaration错误,因为尽管它们有不同的参数类型,但是由于方法名完全相同会导致sel相同,违背了sel唯一性的原则,这也是oc语法和其他语法的不同。
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
//get sel
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(swizzling_viewWillAppear:);
//get method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
/**
* 这里其实是在加了一个保护,如果class_addMethod返回no,说明originalSelector已经有存在的实现了,这个时候,我们将
originalMethod,swizzledMethod直接替换掉就号了,如果还没有对应的实现,那么直接添加进去,并更改原来swizzledSelector对应的实现
*/
//exchange imp
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)swizzling_viewWillAppear:(BOOL)animated {
[self swizzling_viewWillAppear:animated];
NSString *classStr = [NSString stringWithFormat:@"%@", self.class];
NSLog(@"viewWillAppear: %@", classStr);
}
@end