切面编程 Aspects 就是利用 Method Swizzling 与 _objc_msgForward
的典型实例。下面就对 Aspects 做一个深度的解读。
Aspects 的 hook 过程
首先我们来看 Aspects.h
文件。他是一个 NSObject
的类别,这样所有 OC 对象就都可以调用他提供的方法了。他提供了两个入口方法,方法名和参数都相同,区别在于他们一个是类方法,一个是实例方法。他们的返回值都是实现了 AspectToken
协议的 id
类型对象。
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector |
在 Aspects.m
我们可以看到,里面的实现都是通过静态方法实现。hook 的入口方法
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { |
这个方法做了很多工作,可以用流程图来加深理解。
在分析之前,我们需要理解几个类以及他们的作用。
AspectTracker
记录被 hook 的类对象。记录该类被 hook 的方法和类层级关系的 topmost,防止被重复 hook。保存在全局字典 swizzledClassesDict
中,key 是当前类对象。
AspectIdentifier
记录单个被 hook 方法的信息。其中会对传入的 block 进行签名:
- block 参数个数必须小于等于方法的参数个数;
- block 的参数大于 1 个时,第 1 个参数类型必须是 SEL 或
id<AspectInfo>
类型;第 0 个参数类型是 self/block - block 的参数大于 2 时,block 参数类型必须与 方法参数类型一致
AspectsContainer
记录被 Hook 方法的方式。被关联到 self 上,并以别名作为被关联的 key。
AspectsContainer 有三个数组,分别记录不同 AspectOptions
下的 AspectIdentifier。
hook 的4个条件
在 hook 之前,会先判断这个方法是否被允许 hook。判断的条件有4个
不在
retain
、release
、autorelease
、forwardInvocation
之列如果是
dealloc
,只支持AspectPositionBefore
方式,即 hook 的方法要在原方法之前执行。方法必须有实现。
如果传入是类对象,该方法在类与类的继承链上只能被 hook 一次。同时会被 [AspectTracker] 跟踪。tracker 被加到
swizzledClassesDict
全局字典里,key 是 类名。实现还是挺妙的。// Search for the current class and the class hierarchy IF we are modifying a class object
if (class_isMetaClass(object_getClass(self))) {
Class klass = [self class];
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
Class currentClass = [self class];
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if ([tracker.selectorNames containsObject:selectorName]) {
// Find the topmost class for the log.
if (tracker.parentEntry) {
AspectTracker *topmostEntry = tracker.parentEntry;
while (topmostEntry.parentEntry) {
topmostEntry = topmostEntry.parentEntry;
}
NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
return NO;
}else if (klass == currentClass) {
// Already modified and topmost!
return YES;
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
}如果传入是实例对象,始终被允许。
hook 类
- baseClass 如果包含
_Aspects_
后缀,说明已经被 hook - baseClass 如果不包含
_Aspects_
后缀,并且是元类,则去 hook 类 - baseClass 如果不包含
_Aspects_
后缀,并且不是元类,并且 statedClass 与 baseClass 不相等(被 KVO 的对象/swizzle 类/swizzle 元类),则去 hook - 以上条件都不满足(如传入对象是实例对象),则拼接
_Aspects_
后缀生成子类 hook
static Class aspect_hookClass(NSObject *self, NSError **error) { |
object_getClass: 当传入对象是个实例对象时,返回类对象;当传入对象是类对象时,返回元类
+[self class] 与 -[self class]: 返回类对象
hook 类时,先判断该类是否已经 hook 过,hook 过的类的类名添加到全局集合 swizzledClasses
中。未 hook 过的类,是将 - forwardInvocation:
的实现替换为 __ASPECTS_ARE_BEING_CALLED__
。并添加方法__aspects_forwardInvocation
,IMP 指向 originIMP。
static Class aspect_swizzleClassInPlace(Class klass) { |
hook 方法
- 首先获取 selector 的实现,判断是否是
_objc_msgForward
- 生成方法别名
aspects_selectorName
- 判断类是否实现方法
aspects_selectorName
, 未实现时将aspects_selectorName
的实现指向 selector 的实现 - 将
selector
的实现指向_objc_msgForward
或者_objc_msgForward_stret
,后面在调用 selector 方法就会直接走到消息转发的流程
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { |
那么在调用之前的 hook 过程就完成了。
Aspected 的调用过程
从上面的 Hook 过程我们可以知道,Aspects 在调用 selector 的时候,会直接到 _objc_msgForward
走消息转发的流程,最后会调用到 - forwardInvocation:
方法。就是我们 swizzled 后的 __ASPECTS_ARE_BEING_CALLED__
方法。
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { |
aspect_invoke 是一个宏定义,执行两个动作
- 执行 - invokeWithInfo:
- 把
AspectOptionAutomaticRemoval
类型的 aspect 加入到数组中
Aspected 的移除过程
- 首先获取到 aspectContainer ,移除对应数组的 aspects
- aspect_cleanupHookedClassAndSelector 恢复被 hook 的类和方法
static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) { |
aspect_cleanupHookedClassAndSelector
做了三件事情:
- 恢复 selector 的 IMP 指向
_objc_msgFoward
,则将 IMP 指针指向 originIMP - 在全局
swizzledClassesDict
字典获取对应类对象 tracker,移除对应被 hook 的 selector,当该类没有被 hook 的方法时,移除该 tracker - 检查 container 中是否还有 aspect,当没有的时候,移除该 container 的关联属性;当该类含有
_Aspects_
后缀时,获取 originClass(去除后缀),将 self 的 isa 指针指向 originClass;如果没有后缀但是是元类时,将该类的- forwardInvocation:
的 IMP 指向 originIMP,并在全局集合swizzledClasses
中移除该类名
static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { |