Aspects是iOS上技巧性很強(qiáng)的一個(gè)第三方類庫(kù)偎窘,主要針對(duì)于AOP編程(面向切面編程)的思想。
“面向切面編程” 顧名思義就是帶有一定侵入性的編程方式溜在,網(wǎng)上對(duì)AOP編程比較專業(yè)的解釋就是:可以通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)陌知。比較常用于APP內(nèi)部事件埋點(diǎn)的操作。
Aspects的使用:
我們捕捉viewWillAppear的這個(gè)方法掖肋,把option設(shè)為AspectPositionAfter仆葡,block就會(huì)在viewWillAppear執(zhí)行之后再執(zhí)行:
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"viewWillAppear");
}
[self aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info){
NSLog(@"hook_viewWillAppear");
} error:nil];
執(zhí)行結(jié)果:
而且不光能對(duì)對(duì)象使用,也可以直接對(duì)類使用
[ViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info){
NSLog(@"hook_viewWillAppear");
} error:nil];
這樣所有這個(gè)類的對(duì)象在執(zhí)行viewWillAppear的時(shí)候志笼,都會(huì)觸發(fā)block沿盅。
當(dāng)然,對(duì)類的hook和對(duì)對(duì)象的hook是不一樣的纫溃,類應(yīng)該是整體起效腰涧,而對(duì)象是獨(dú)立起效。我們先看一下兩種hook之后的情況紊浩,看看大概做了什么窖铡,然后就比較容易理解。
1坊谁、類的hook:
對(duì)類進(jìn)行hook之后费彼,我們打印出方法列表,發(fā)現(xiàn)確實(shí)動(dòng)態(tài)添加了些方法口芍,
aspects__viewWillAppear:加了aspects__前綴的方法箍铲,用來(lái)保留原的方法的實(shí)現(xiàn),因?yàn)樵瓉?lái)的方法指針指向forwardInvocation了阶界。
forwardInvocation方法:即使沒(méi)實(shí)現(xiàn)虹钮,也會(huì)動(dòng)態(tài)添加這個(gè)方法,指針指向了真正實(shí)現(xiàn)功能的ASPECTS_ARE_BEING_CALLED方法膘融。
2、對(duì)象的hook:
對(duì)對(duì)象進(jìn)行hook之后祭玉,我們也打印出方法列表:
為啥并沒(méi)有新增任何方法氧映?我們猜測(cè)會(huì)不會(huì)是這個(gè)對(duì)象已經(jīng)不指向原來(lái)的類了呢,那我們就利用object_getClass(self)方法來(lái)獲取這個(gè)對(duì)象的類看看:
可以看到對(duì)象的類變化了脱货,對(duì)象的hook由于不能使其他同族的對(duì)象收到影響岛都,所以不能對(duì)原來(lái)的類直接進(jìn)行改變律姨,aspects就動(dòng)態(tài)生成了一個(gè)類(類似kvo思想),我們?cè)俅蛴〕鰟?dòng)態(tài)類的方法列表:
如我們所料臼疫,他把整體的過(guò)程移到了動(dòng)態(tài)類去實(shí)現(xiàn)择份。
Aspects的內(nèi)部實(shí)現(xiàn):
Aspects的實(shí)現(xiàn)核心還是利用iOS消息轉(zhuǎn)發(fā)的機(jī)制,利用轉(zhuǎn)發(fā)的第三部forwardInvocation來(lái)捕捉方法的調(diào)用時(shí)機(jī)烫堤,然后用利用method swizzling將函數(shù)指針指向自己的實(shí)現(xiàn)荣赶,然后在自己的實(shí)現(xiàn)里調(diào)用原來(lái)的方法,再根據(jù)option的時(shí)機(jī)調(diào)用block鸽斟。
跳轉(zhuǎn)方法拔创,可以看到統(tǒng)一的方法入口是aspect_add,先是進(jìn)行hook之前的準(zhǔn)備工作富蓄。
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
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(^{ #為了安全加了鎖
#判斷方法是否能被hook
if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
#將被hook的對(duì)象先通過(guò)AspectsContainer對(duì)象先進(jìn)行動(dòng)態(tài)綁定剩燥,以供后續(xù)使用 。
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
#生成block的方法簽名立倍,并將各個(gè)參數(shù)保存生成identifier對(duì)象
#MARK:(為什么要對(duì)block這么處理灭红,后面再說(shuō))
identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
if (identifier) {
#將identifier存入入aspectContainer
[aspectContainer addAspect:identifier withOptions:options];
// Modify the class to allow message interception.
aspect_prepareClassAndHookSelector(self, selector, error);
}
}
});
return identifier;
}
然后執(zhí)行到aspect_prepareClassAndHookSelector方法,這個(gè)方法會(huì)對(duì)對(duì)象和實(shí)例區(qū)別處理口注,然后改變函數(shù)指針的指向比伏。
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
NSCParameterAssert(selector);
#這里地方根據(jù)hook的對(duì)象是實(shí)例還是類的,進(jìn)行區(qū)別處理疆导,返回需要處理的類
Class klass = aspect_hookClass(self, error);
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
#接下來(lái)主要就是改變指針指向赁项,將原來(lái)方法的指針指向forwardInvocation方法。
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));
}
}
aspect_hookClass的實(shí)現(xiàn):
static Class aspect_hookClass(NSObject *self, NSError **error) {
NSCParameterAssert(self);
#class會(huì)返回對(duì)象的類澈段,但是如果本身是類悠菜,就會(huì)返回自身。
#object_getClass會(huì)返回ISA指針的指向败富,對(duì)象返回類悔醋,類則會(huì)返回元類。
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
# isa已經(jīng)指向動(dòng)態(tài)生成的類了
// 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.
#如果是KVO的對(duì)象的話兽叮。
#因?yàn)镵VO也會(huì)動(dòng)態(tài)生成子類芬骄,并且改變isa指針指向
}else if (statedClass != baseClass) {
return aspect_swizzleClassInPlace(baseClass);
}
#以下說(shuō)明被Hook的是對(duì)象了
// Default case. Create dynamic subclass.
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
#動(dòng)態(tài)創(chuàng)建子類
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;
}
#交換forwardInvocation指針
aspect_swizzleForwardInvocation(subclass);
#這里做了kvo一樣的事……讓class方法返回還是原來(lái)的類。
#不過(guò)我們還是可以用object_getClass看到對(duì)象的類已經(jīng)是XXX_Aspects_了 ㄟ( ▔, ▔ )ㄏ
aspect_hookedGetClass(subclass, statedClass);
aspect_hookedGetClass(object_getClass(subclass), statedClass);
objc_registerClassPair(subclass);
}
#將對(duì)象的isa指針指向動(dòng)態(tài)生成的子類鹦聪。
object_setClass(self, subclass);
return subclass;
}
我們也看一下aspect_swizzleClassInPlace這個(gè)方法的實(shí)現(xiàn)账阻,這是對(duì)類的hook的處理
static Class aspect_swizzleClassInPlace(Class klass) {
NSCParameterAssert(klass);
NSString *className = NSStringFromClass(klass);
_aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
#其實(shí)主要的也還是交換了swizzleForwardInvocation的imp指針
#加了個(gè)數(shù)組判斷,防止反復(fù)交換
if (![swizzledClasses containsObject:className]) {
aspect_swizzleForwardInvocation(klass);
[swizzledClasses addObject:className];
}
});
return klass;
}
交換了forwardInvocation的imp指針泽本,指向了ASPECTS_ARE_BEING_CALLED方法:
static void aspect_swizzleForwardInvocation(Class klass) {
NSCParameterAssert(klass);
// If there is no method, replace will act like class_addMethod.
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
if (originalImplementation) {
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
然后當(dāng)被hook的方法被調(diào)用時(shí)淘太,就會(huì)觸發(fā)以下的方法,也就是真正的執(zhí)行的方法:
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;
#根據(jù)option的情況,區(qū)分處理蒲牧,執(zhí)行block
#三種option分別由三個(gè)數(shù)組進(jìn)行保存管理
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
#選擇instead 撇贺,原來(lái)的方法就不執(zhí)行了。
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
#這里執(zhí)行原來(lái)的方法
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.
#移除option為AspectOptionAutomaticRemoval的hook信息
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
PS:關(guān)于Aspects的block簽名問(wèn)題:
Aspects里面對(duì)block進(jìn)行了簽名冰抢,然后又用NSInvocation去調(diào)用block松嘶。
有的人不理解,我們不是拿到block了嗎挎扰,為什么不直接調(diào)用block翠订,非要弄得這么麻煩?鼓鲁?蕴轨?因?yàn)锳spects要讓block能夠支持動(dòng)態(tài)參數(shù)。
block在用普通方式調(diào)用的時(shí)候骇吭,必須要明確參數(shù)橙弱,在不確定參數(shù)個(gè)數(shù)的情況下,我們就無(wú)法這么調(diào)用了燥狰,而要使用NSInvocation配置參數(shù)再進(jìn)行調(diào)用棘脐。
嘗試一下,依然以hook控制器的viewWillAppear方法為例龙致,在block后面加入被hook方法的參數(shù)(即animated)
[self aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> info,BOOL animated){
NSLog(@"hook_show animatied:%d",animated);
} error:nil];
輸出結(jié)果:確實(shí)是成功傳遞了參數(shù)蛀缝。
接下來(lái)看一下內(nèi)部的實(shí)現(xiàn):
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
#根據(jù)block簽名生成block的invocation
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
#這個(gè)是原方法的invocation
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
#檢測(cè)兩個(gè)invocation的參數(shù)必須匹配
// Be extra paranoid. We already check that on hook registration.
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
#把id<AspectInfo> info先作為第二個(gè)參數(shù),第一個(gè)參數(shù)為self目代,block的方法沒(méi)有_cmd
// The `self` of the block will be the AspectInfo. Optional.
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
#這個(gè)地方是從2開始屈梁,因?yàn)槲覀兤綍r(shí)的方法其實(shí)會(huì)有兩個(gè)隱形的參數(shù)self和_cmd,這了兩個(gè)參數(shù)占了0和1的位置
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
#把原方法的參數(shù)傳遞給block
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
#執(zhí)行block
#因?yàn)閎lock的本質(zhì)就是一個(gè)對(duì)象榛了,內(nèi)部保存著函數(shù)指針在讶,
#所以它執(zhí)行的target就是自己,不需要通過(guò)selector
找到方法名稱霜大。
[blockInvocation invokeWithTarget:self.block];
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}
我們打印出執(zhí)行之后的兩個(gè)invocation看一下:
可以看到argument 2為{B} 0, 說(shuō)明是bool類型构哺,值為0。