iOS 開源庫系列 Aspects核心源碼分析---面向切面編程之瘋狂的 Aspects


Aspects的源碼學習,我學到的有幾下幾點

  1. Objective-C Runtime
  2. 理解OC的消息分發機制
  3. KVO中的指針交換技術
  4. Block 在內存中的數據結構
  5. const 的修飾區別
  6. block 中常量在特定情況下的三種處理方法
  7. 斷言語句,
  8. 自旋鎖 使用注意
  9. _objc_msgForward_stret 和 _objc_msgForward 前者存在的必要

  10. Type Encoding

https://www.cnblogs.com/DafaRan/p/8192069.html

簡介

Aspects是一個面向切面編程的庫。
如果想深入了解iOS Runtime中的消息發送機制,Aspects的源碼是值得分析的。 

項目主頁
Aspects

整體分析

閱讀Aspects的源碼需要以下知識作為基礎

  1. Objective-C Runtime
  2. 理解OC的消息分發機制
  3. KVO中的指針交換技術

閱讀本文之前,建議應該先斷點調試下Aspects的Demo,了解大致的過程。

核心實現

Aspects的核心實現就是利用Runtime中的消息分發機制如圖:

Aspects通過把selector的方法替換為msg_forward方法轉發 轉而調用 forwardInvocation(forwardInvocation的實現被Aspects替換,將原來的方法實現與添加的實現組合在了一起)

核心源碼分析

這是Aspects 面向切面編程的入口方法

- (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error { return aspect_add(self, selector, options, block, error); }復制代碼

這段代碼可以分三部分來看

  1. aspect_isSelectorAllowedAndTrack 這個方法 對父子類同時hook一個方法進行了一些限制
  2. aspect_getContainerForObject 通過Runtime添加關聯值的方式 管理hook的方法
  3. aspect_prepareClassAndHookSelector 這是核心的實現,涉及到動態生成子類,改變isa指針的指向,改變方法的實現 一系列操作
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)) { //一個實例 只有一個container //這是區分實例對象和類對象的關鍵 //實例對象可以有很多個,但是同一個類的類對象只能有一個 AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); //原來的selector block identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; if (identifier) { //container 里 存有 identifier (selector,block) [aspectContainer addAspect:identifier withOptions:options]; // Modify the class to allow message interception. aspect_prepareClassAndHookSelector(self, selector, error); } } }); return identifier; }復制代碼

核心方法

aspect_prepareClassAndHookSelector這是核心的實現,涉及到動態生成子類,改變isa指針,改變方法的實現 一系列操作

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { NSCParameterAssert(selector); //動態創建子類,改變forwardInvocation方法的實現 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]) { //子類的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 // We use forwardInvocation to hook in. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); } }復制代碼

動態生成子類,改變isa指針

#pragma mark - Hook Class static Class aspect_hookClass(NSObject *self, NSError **error) { NSCParameterAssert(self); //這里可以思考一下 class 方法 和 isa 的區別 //[self class] KVO可能改變了isa指針的指向 Class statedClass = self.class; // object_getClass 能准確的找到isa指針 Class baseClass = object_getClass(self); NSString *className = NSStringFromClass(baseClass); // Already subclassed //如果已經子類化了 就返回 if ([className hasSuffix:AspectsSubclassSuffix]) { return baseClass; //如果是類 就改掉類的forwardInvocation 而不是一個子類對象 // We swizzle a class object, not a single object. }else if (class_isMetaClass(baseClass)) { return aspect_swizzleClassInPlace((Class)self); //考慮到KVO,KVO的底層實現,交換了isa指針 // 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; } // forwardInvocation 替換成 (IMP)_ASPECTS_ARE_BEING_CALLED__ aspect_swizzleForwardInvocation(subclass); //子類的class方法返回當前被hook的對象的class aspect_hookedGetClass(subclass, statedClass); aspect_hookedGetClass(object_getClass(subclass), statedClass); objc_registerClassPair(subclass); } //將當前self設置為子類,這里其實只是更改了self的isa指針而已, 這里hook了子類的forwardInvocation方法,再次使用當前類時,其實是使用了子類的forwardInvocation方法。 object_setClass(self, subclass); return subclass; }復制代碼

 


作者:Junyiii
鏈接:https://juejin.im/post/58f6d1675c497d006ca638fe
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
 

Aspects初始化工作核心部分的解析

aspect_add方法:

HookClass過程:

HookSelector過程

 

Aspects執行工作核心部分的解析

當我們正式向某個接受者發送消息的時候,會進行消息轉發,而之前HookClass的過程當中我們已經對forwardInvocation的實現替換為了__ASPECTS_ARE_BEING_CALLED__

執行block的代碼

流程圖:

 

http://ios.jobbole.com/84522/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM