都傳聞說 OC 的運行時很是NB,今天就來看看很是有名的Aspects
,源碼在這git
https://github.com/steipete/A...github
裏面的內容很是簡單,其實就2個文件,Aspect.h
和Aspect.m
,它使用Category
爲NSObject
提供了兩個額外的方法,API以下:objective-c
/// Adds a block of code before/instead/after the current `selector` for a specific class. /// /// @param block Aspects replicates the type signature of the method being hooked. /// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method. /// These parameters are optional and will be filled to match the block signature. /// You can even use an empty block, or one that simple gets `id<AspectInfo>`. /// /// @note Hooking static methods is not supported. /// @return A token which allows to later deregister the aspect. + (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; /// Adds a block of code before/instead/after the current `selector` for a specific instance. - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; /// Deregister an aspect. /// @return YES if deregistration is successful, otherwise NO. id<AspectToken> aspect = ...; [aspect remove];
它提供的解決方案就是爲一個消息提供一個 before 和 after 的 block 調用,也就是爲 OC 提供了 AOP 的能力。
咱們知道在 Java 中,實現 AOP 採用的是動態代理的方式,那麼在 OC 中的實現,其實就是經過 Swizzle Method 的方式進行啦。ide
看下 Aspects 究竟是如何實現這個功能的函數
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add((id)self, selector, options, block, error); } /// @return A token which allows to later deregister the aspect. - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add(self, selector, options, block, error); }
事實上,無論是靜態仍是動態方式添加,都是使用aspect_add
這個方法,代理
static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { NSCParameterAssert(self); NSCParameterAssert(selector); NSCParameterAssert(block); __block AspectIdentifier *identifier = nil; // ... // 省略加鎖的 block 和 權限檢查 // 看 aspect_getContainerForObject 源碼可知,使用 lazy load 的方式,爲 self 生成一個 AspectsContainer 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_prepareClassAndHookSelector
指針
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); // 下面一行代碼,是動態生成了一個子類,而後覆蓋了原先的 forwardInvocation 消息,這是這裏最 magic 的地方,如下會講到 Class klass = aspect_hookClass(self, error); Method targetMethod = class_getInstanceMethod(klass, selector); IMP targetMethodIMP = method_getImplementation(targetMethod); // 檢查若是目標方法的實現還不是 _objc_msgForward 或者 _objc_msgForward_stret 的話,就進行 hook if (!aspect_isMsgForwardIMP(targetMethodIMP)) { // Make a method alias for the existing method implementation, it not already copied. const char *typeEncoding = method_getTypeEncoding(targetMethod); // 給咱們須要被取代的 selector 取一個別名 SEL aliasSelector = aspect_aliasForSelector(selector); if (![klass instancesRespondToSelector:aliasSelector]) { // 爲類增長一個名字爲 aliasSelector, 實現爲 selector 的消息 __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); } // 把原先的 selector 方法的實現指向 _objc_msgForward 或 _objc_msgForward_stret。 // 在先前調用了 aspect_hookClass 裏面,hook 了 forwardInvocation,後文會說明 // We use forwardInvocation to hook in. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } }
好了,通過這麼多步驟後,咱們理清一下思路,若是咱們要對@selector(viewDidLoad:)
進行 hookcode
先建立 subclass, hook @selector(forwardInvocation:),並把當前的對象設置爲該類的對象,這樣在不污染原類的狀況下,實現了forwardInvocation
的hookorm
爲咱們的 obj 的目標消息建立一個別名,這裏若是是viewDidLoad:
的話,那別名就是aspects_viewDidLoad:
對象
取代目標消息的實現,使用aspect_getMsgForwardIMP
來選擇是 _objc_msgForward 或是 _objc_msgForward_stret
這樣就完成了前置工做,接下來咱們簡單來說什麼是 _objc_msgForward
咱們這篇,主要是講 Aspects 提供的解決方案,因此不會展開闡述 objc runtime 的一些內容,因此先提供參考資料:
http://blog.ibireme.com/2013/...
objc 中發送消息的方式是主要是在 C 層面調用 obj_msgSend 方法,若是找不到消息的實現,它會嘗試進行轉發,原理是把函數的實現改成 _objc_msgForward,它是一個函數指針
在轉發過程當中,objc 會把方法簽名包裝成 Invocation 傳入到 forwardInvocation:
裏,如下是對博文的引用:
Test NSObject initialize
Test NSObject new
Test NSObject alloc
Test NSObject allocWithZone:
Test NSObject init
Test NSObject performSelector:
Test NSObject resolveInstanceMethod:
Test NSObject forwardingTargetForSelector:
Test NSObject methodSignatureForSelector:
Test NSObject class
Test NSObject doesNotRecognizeSelector:
結合NSObject文檔能夠知道,_objc_msgForward 消息轉發作了以下幾件事:
調用resolveInstanceMethod:方法,容許用戶在此時爲該Class動態添加實現。若是有實現了,則調用並返回。若是仍沒實現,繼續下面的動做。
調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。若是獲取到,則直接轉發給它。若是返回了nil,繼續下面的動做。
調用methodSignatureForSelector:方法,嘗試得到一個方法簽名。若是獲取不到,則直接調用doesNotRecognizeSelector拋出異常。
調用forwardInvocation:方法,將地3步獲取到的方法簽名包裝成Invocation傳入,如何處理就在這裏面了。
上面這4個方法均是模板方法,開發者能夠override,由runtime來調用。最多見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其餘對象都是沒問題的。
通過以上,咱們知道了,若是方法的實現是_objc_msgForward
的話,那咱們的消息就會被包裝成Invocation
發送到forwardInvocation
裏去,那麼在前面,咱們進行subclass
的時候,就會forwardInvocation
進行了hook
,這時候就用到了!
看看 Aspects 是如何作的吧。
具體函數在aspect_swizzleForwardInvocation
中實現
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:"; 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)); }
咱們看到,Aspects
把forwardInvocation
的實現換成了__ASPECTS_ARE_BEING_CALLED__
這個函數,而原始的forwardInvocation
實現的名字就變成了__aspects_forwardInvocation
看看__ASPECTS_ARE_BEING_CALLED__
這裏幹了什麼
// This is the swizzled forwardInvocation: method. static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { NSCParameterAssert(self); NSCParameterAssert(invocation); // 獲得原始 selector SEL originalSelector = invocation.selector; // 獲得原始 selector 的別名(以前被咱們添加到類裏了),這纔是真正的實現 SEL aliasSelector = aspect_aliasForSelector(invocation.selector); // 替換 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); // 以上就是執行 hook 的具體內容了 // If no hooks are installed, call original implementation (usually to throw an exception) if (!respondsToAlias) { // 若是沒有執行的話。。那麼只好執行默認的 forwardInvocation 了 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
hook objc 進行 AOP 全過程,雖然只有短短一千行不到的代碼,卻提供了很方便的方式進行 AOP,實現也很巧妙。看完後對 objc runtime 羨慕不已,實在強大!