什麼是面向切面程式設計
Aspect Oriented Programming(AOP),面向切面程式設計,是一個比較熱門的話題。AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。
主要用來處理一些具有橫切性質的系統性服務,如日誌記錄、許可權管理、快取、物件池管理等,AOP 已經成為一種非常常用的解決方案。
比如說我們在實現許可權驗證的時候,需要在每個業務的執行前對許可權進行相應的判斷,從而導致了大量的重複程式碼,不利於模組的複用。AOP則通過將每個業務的公共行為進行抽離,封裝成一個可複用的模組,這個模組就叫做『切面』。
什麼是Aspects
Aspects是一個輕量級的面向切面程式設計的庫。它主要提供了三個切入點:before(在原始的方法前執行)/instead(替換原始的方法執行)/after(在原始的方法後執行,預設),通過Runtime訊息轉發實現Hook,同時這也會帶來一定的負擔,所以它不適合迴圈多次呼叫的方法。
Aspects擴充套件了NSObject類,對外提供如下兩個方法:
1 2 3 4 5 6 7 8 9 10 11 |
///為指定的類的當前方法的before/instead/after切入點新增block程式碼 + (id)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; ///為指定例項的當前方法的before/instead/after切入點新增block程式碼 - (id)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; |
Aspects初始化工作核心部分的解析
aspect_add方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { NSCParameterAssert(self); NSCParameterAssert(selector); NSCParameterAssert(block); //宣告AspectIdentifier例項 __block AspectIdentifier *identifier = nil; aspect_performLocked(^{ //判斷當前XX方法是否允許被Hook,1."retain"、"release"、"autorelease"、"forwardInvocation"這幾個方法是不被允許的,所謂的黑名單。2.如果方法是dealloc,則他的切入點必須是Before。3.判斷當前例項物件和類物件是否能響應方法4.是否是類物件,如果是則判斷繼承體系中方法是否已經被Hook,而例項則不用。 if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { //獲得當前aspects__XX方法的AspectsContainer容器 AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); //初始化AspectIdentifier變數,方法內部通過toll-free bridged獲取Block方法簽名,並判斷其相容性 identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { //通過options選項分別新增到容器中的beforeAspects,insteadAspects,afterAspects這三個陣列 [aspectContainer addAspect:identifier withOptions:options]; //HookSelector的過程和HookClass的過程 aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier; } |
HookClass過程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
static Class aspect_hookClass(NSObject *self, NSError **error) { NSCParameterAssert(self); Class statedClass = self.class; Class baseClass = object_getClass(self); NSString *className = NSStringFromClass(baseClass); // 如果類名有_Aspects_字首,說明Class已被Hook if ([className hasSuffix:AspectsSubclassSuffix]) { return baseClass; // 判斷是否為類物件,如果是,則直接在當前類中進行swizzle }else if (class_isMetaClass(baseClass)) { return aspect_swizzleClassInPlace((Class)self); // 判斷是否為KVO過的物件,因為KVO的物件ISA指標會指向一箇中間類,則直接在這個中間類中進行swizzle }else if (statedClass != baseClass) { return aspect_swizzleClassInPlace(baseClass); } // 預設則會動態建立一個子類 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; } //實現替換當前類forwardInvocation方法的實現為__ASPECTS_ARE_BEING_CALLED__ aspect_swizzleForwardInvocation(subclass); //實現當前類的isa指標指向原生的類 aspect_hookedGetClass(subclass, statedClass); //實現當前類的元類的isa指標指向原生的類 aspect_hookedGetClass(object_getClass(subclass), statedClass); //註冊當前類 objc_registerClassPair(subclass); } //將當前物件的isa指標指向剛生成的類 object_setClass(self, subclass); return subclass; } |
HookSelector過程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); //HookClass過程 Class klass = aspect_hookClass(self, error); //此時的klass類為剛建立的具有_Aspects_字尾的子類,在建立的時候指定類他的父類,所以我們可以獲取到selector這個方法 Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); //判斷是否為訊息轉發 if (!aspect_isMsgForwardIMP(targetMethodIMP)) { //獲得原生方法的型別編碼 const char *typeEncoding = method_getTypeEncoding(targetMethod); SEL aliasSelector = aspect_aliasForSelector(selector); if (![klass instancesRespondToSelector:aliasSelector]) { //為klass新增aspects__XX方法,方法的實現為原生方法的實現。 __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); } // 將原生方法實現替換為_objc_msgForward或_objc_msgForward_stret,用來實現訊息轉發 class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } } |
Aspects執行工作核心部分的解析
當我們正式向某個接受者傳送訊息的時候,會進行訊息轉發,而之前HookClass的過程當中我們已經對forwardInvocation的實現替換為了__ASPECTS_ARE_BEING_CALLED__
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { NSCParameterAssert(self); NSCParameterAssert(invocation); //獲取原始方法XX SEL originalSelector = invocation.selector; //獲取含有字首的方法aspects_XX SEL aliasSelector = aspect_aliasForSelector(invocation.selector); //替換Sel invocation.selector = aliasSelector; //獲得例項物件容器 AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); //獲得類物件容器 AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); //初始化AspectInfo,傳入self、invocation引數 AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; NSArray *aspectsToRemove = nil; // 執行before切入點的呼叫 aspect_invoke(classContainer.beforeAspects, info); aspect_invoke(objectContainer.beforeAspects, info); // 執行Instead切入點的呼叫,判斷當前insteadAspects是否有資料,如果沒有資料則判斷當前繼承鏈是否能響應aspects_XX方法,如果能,則直接呼叫,此時的aspects_XX則為原生的實現。 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切入點的呼叫 aspect_invoke(classContainer.afterAspects, info); aspect_invoke(objectContainer.afterAspects, info); // 若Hook未被正確執行,則呼叫原生訊息轉發。 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方法 [aspectsToRemove makeObjectsPerformSelector:@selector(remove)]; } |
執行block的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (BOOL)invokeWithInfo:(id)info { //根據blockSignature獲取Invocation NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; //獲取原生方法的Invocation NSInvocation *originalInvocation = info.originalInvocation; //獲取blockInvocation引數個數 NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; // 判斷blockInvocation引數個數是否大於originalInvocation引數個數 if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { AspectLogError(@"Block has too many arguments. Not calling %@", info); return NO; } // blockInvocation給索引為1的引數賦值 if (numberOfArguments > 1) { [blockInvocation setArgument:&info atIndex:1]; } //當所以大於1的時候進行遍歷,把原生的引數值賦值給相應的blockInvocation中的引數 void *argBuf = NULL; for (NSUInteger idx = 2; idx |
流程圖:
流程圖
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式