用 OC 開發(fā) iOS 應(yīng)用的人都知道 OC 是一門動態(tài)語言。我們能夠在運行時通過 Runtime API 獲取并替換任意方法。所以,在 AOP 和 熱更新的實現(xiàn)上都經(jīng)??梢钥吹?Runtime 的身影。這雖然看似美好,但運用不當?shù)脑捦ǔ斐芍卮?bug。Runtime 對于 OC 來說就像是魔力強大但又十分危險的黑魔法。

1 消息轉(zhuǎn)發(fā)
iOS 的方法執(zhí)行是通過 SEL 找到 對應(yīng)的 IMP,然后執(zhí)行相應(yīng)的函數(shù)。如果 SEL 找不到對應(yīng)的 IMP 就會啟動消息轉(zhuǎn)發(fā)機制。消息轉(zhuǎn)發(fā)會經(jīng)過三個階段。
-
第一階段:在類的內(nèi)部進行處理
//處理實例方法 + (BOOL)resolveInstanceMethod:(SEL)sel //處理類方法 + (BOOL)resolveClassMethod:(SEL)sel重寫該方法可以對相應(yīng)的 SEL 進行處理,返回 YES 說明處理完成,不進行下一步轉(zhuǎn)發(fā)。
-
第二階段:尋找備援接收者
- (id)forwardingTargetForSelector:(SEL)aSelector該方法會返回一個能夠處理改消息的備援接收者。如果返回 nil 則繼續(xù)下一步。
-
第三階段:全局處理
- (void)forwardInvocation:(NSInvocation *)invocation將消息包裝成一個 NSInvocation 對象,讓系統(tǒng)對其進行處理。如果這里還是沒有進行,則會拋出相應(yīng)的異常。
整個流程如下圖所示:

2 Aspects
Aspects 通過 swizzling method 和 swizzling isa 實現(xiàn)對原方法的 hook ,以此來幫助使用者完成 AOP 編程。
2.1 Aspect 主要主要類介紹:
@interface AspectInfo : NSObject <AspectInfo>
- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
AspectInfo:aspect 的信息,被 hook 方法的 IMP 信息存放在 NSInvocation 中。
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
AspectIdentifier:一個 aspect 的具體信息,相應(yīng)的 sel,options,block
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
AspectsContainer:本類所有的 aspect 的容器
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, readonly) NSString *trackedClassName;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
@end
AspectTracker:用來追蹤 aspect 的相關(guān)信息
2.2 Aspects API調(diào)用之后發(fā)生的事情
下面是 aspect 相關(guān)方法調(diào)用之后,主要的執(zhí)行邏輯
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
NSCParameterAssert(self);
NSCParameterAssert(selector);
NSCParameterAssert(block);
__block AspectIdentifier *identifier = nil;
aspect_performLocked(^{
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
aspect 方法入口,先是斷言判斷傳入的參數(shù)。然后 aspect_performLocked 加鎖同步操作,通過 aspect_isSelectorAllowedAndTrack 判斷該方法是否允許 hook。判斷邏輯如下:
- 實例方法的"retain", "release", "autorelease", "forwardInvocation:" 不允許 hook , "dealloc" 方法只能是 AspectPositionBefore 的方式操作
- 類方法要判斷父類,子類中沒有 hook 過該方法。
如果可以 hook,則根據(jù)參數(shù)生成一個 AspectIdentifier 對象,之后放在相應(yīng)的 AspectsContainer 容器中,然后調(diào)用 aspect_prepareClassAndHookSelector 進行 class swizzling 和 method swizzling。
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
Class klass = aspect_hookClass(self, error);
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
// Make a method alias for the existing method implementation, it not already copied.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
if (![klass instancesRespondToSelector:aliasSelector]) {
__unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
}
// We use forwardInvocation to hook in.
class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
}
調(diào)用 aspect_hookClass 進行相應(yīng)的 class swizzling 操作。
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// Already subclassed
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// We swizzle a class object, not a single object.
}else if (class_isMetaClass(baseClass)) {
return aspect_swizzleClassInPlace((Class)self);
// Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) {
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
aspect_swizzleForwardInvocation(subclass);
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
class swizzling 的主要邏輯如下:
- 若 class 是元類,swizzling 相應(yīng)的 ForwardInvocation 方法
- 若 class 是普通的類, 先判斷是否是 KVO 對象,是的話 swizzling 相應(yīng)的 ForwardInvocation 方法
- 若不是 KVO 對象的話,先創(chuàng)建一個子類,并 swizzling 子類的 ForwardInvocation,同時將本類的 isa 指針,指向創(chuàng)建的子類。
hook 過的 class 都保存在 swizzledClasses 對象中。接下去進行 method swizzling, 調(diào)用 aspect_isMsgForwardIMP 確認實現(xiàn)函數(shù)不是消息轉(zhuǎn)發(fā)相關(guān)的。然后讓 aliasSelector 指向 targetMethodIMP,本來的 OriginalSel 指向 msgForwardIMP。至此設(shè)置階段就完成了。
2.3 被 Hook 的方法被調(diào)用之后發(fā)生的事情
在調(diào)用 aspect 的 api 進行相關(guān)設(shè)置之后,程序執(zhí)行到剛被 hook 過的方法就會進入 aspect 的執(zhí)行邏輯。
由于上面的設(shè)置,我們知道調(diào)用實例方法的類的 isa 會指向了一個子類,然后子類的 OriginalSel 指向了 msgForwardIMP, 而子類相應(yīng)的 ForwardInvocation 也被替換了,指向了 __ASPECTS_ARE_BEING_CALLED__,所以,一旦調(diào)用相應(yīng)的方法就會進入 __ASPECTS_ARE_BEING_CALLED__ 里面。
如果是類方法,則是 OriginalSel 指向了 msgForwardIMP ,本身的 ForwardInvocation 被替換指向了__ASPECTS_ARE_BEING_CALLED__ ,所以一樣進入到 __ASPECTS_ARE_BEING_CALLED__ 方法里。
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
__ASPECTS_ARE_BEING_CALLED__ 內(nèi)部的邏輯是:根據(jù) aliasSelector 獲取之前存儲的類或者元類的 AspectsContainer 對象,之后根據(jù) AspectOptions 在適當?shù)臅r機獲取 AspectsContainer 中對應(yīng)的所有 AspectIdentifier 并執(zhí)行相應(yīng)的 block 。
同時處理被 hook 方法原來的實現(xiàn)( IMP ) , 被封裝在 originalInvocation 中(若有 AspectIdentifier 的 AspectOptions 是 insteadAspects ,則 originalInvocation 將不會被執(zhí)行)。若 AspectIdentifier 的 AspectOptions 是 AspectOptionAutomaticRemoval ,則該 AspectIdentifier 會被存入 aspectsToRemove 中,執(zhí)行結(jié)束之后會被移除。
3 總結(jié)
以上就是本人對于 Aspects 這個強大的 AOP 庫的一個大致理解。同時這里面還有很多細節(jié)很值得學習。