变更记录
序号 | 录入时间 | 录入人 | 备注 |
---|---|---|---|
1 | 2016-09-09 | Alfred Jiang | - |
方案名称
Runtime - 使用 Aspects 实现 Method Swizzling 和 AOP 实践
关键字
Runtime \ Method Swizzling \ Aspect Oriented Programming \ 面向切面编程
需求场景
- 利用 Method Swizzling 对系统方法进行替换和追加行为
参考链接
详细内容
在 Objective-C 中, Aspect Oriented Programming (面向切面编程) 就是利用 Runtime 特性给指定的方法添加自定义代码。有很多方式可以实现 AOP ,Method Swizzling 就是其中之一。而且幸运的是,目前已经有一些第三方库可以让你不需要了解 Runtime ,就能直接开始使用 AOP 。
Aspects 就是一个不错的 AOP 库,封装了 Runtime , Method Swizzling 这些黑色技巧,只提供两个简单的API:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
示例 : 用户看到某个 View 的时候,就把这个事件记下来
1. 通过 Runtime 提供的 Method Swizzling 方法实现
@implementation UIViewController (Logging)
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated
{
// call original implementation
[self swizzled_viewDidAppear:animated];
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
@end
要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉
2. 通过 Runtime 提供的替换 IMP 方法直接用新的 IMP 取代原 IMP 来实现
void (*gOriginalViewDidAppear) (id,SEL,BOOL);
@implementation UIViewController (Logging)
void newViewDidAppear(UIViewController *self, SEL _cmd, BOOL animated)
{
// call original implementation
gOriginalViewDidAppear(self, _cmd, animated);
// Logging
[Logging logWithEventName:NSStringFromClass([self class])];
}
+ (void)load
{
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
gOriginalViewDidAppear = (void *)method_getImplementation(originalMethod);
if(!class_addMethod(self, @selector(viewDidAppear:), (IMP) newViewDidAppear, method_getTypeEncoding(originalMethod))) {
method_setImplementation(originalMethod, (IMP) newViewDidAppear);
}
}
@end
2. 通过 Aspects 提供接口实现
@implementation UIViewController (Logging)
+ (void)load
{
[UIViewController aspect_hookSelector:@selector(viewDidAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
[Logging logWithEventName:className];
}
error:NULL];
}
@end
效果图
(无)
备注
(无)