天天看點

Aspects 源代碼解析<一>Aspects 是什麼,解決了什麼問題?Aspects的思路及實作

Aspects 是什麼,解決了什麼問題?

Aspects是AOP(面向切面程式設計)思想在iOS下OC的實作。Aspects可以用于hook函數,讓函數執行一些副操作(列印調試資訊、記錄日志等)。切面可以簡單了解為嵌入不同函數中的功能相同的操作(列印調試資訊等),每類功能相同的操作可以抽取出一個切面。下面簡要介紹OOP(面向對象程式設計)和AOP的概念和差別:

  • OOP(面向對象程式設計)針對業務處理過程的實體及其屬性和行為進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
  • AOP則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。

OOP實際上是對對象的屬性和行為的封裝,而AOP對于這點就無從談起,但是AOP是處理某個步驟和階段的,從中進行切面的提取,也就是說,如果幾個或更多個邏輯過程中,有重複的操作行為,AOP就可以提取出來,運用動态代理,實作程式功能的統一維護,這麼說來可能太含蓄,如果說到權限判斷,日志記錄等,可能就明白了。如果我們單純使用OOP,那麼權限判斷怎麼辦?在每個操作前都加入權限判斷?日志記錄怎麼辦?在每個方法裡的開始、結束、異常的地方手動添加日志?所有,如果使用AOP就可以借助代理完成這些重複的操作,就能夠在邏輯過程中,降低各部分之間的耦合了。二者揚長補短,互相結合最好。

Aspects的思路及實作

思路

Aspects對外暴露了兩個接口,用于hook selector:

#pragma mark - Public Aspects API

+ (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);
}
           

參數說明:

selector options block error
要被hook的selector 切面(block)執行的時機,在selector執行前、後執行,還是替換被hook的selector 切面 hook過程中的錯誤

方法說明:

  1. 第一個方法為類方法,receiver為要被hook的類(

    [receiver message]

    )。直接在本類上hook類的執行個體方法(被KVO過的類,直接在KVO過程中生成的子類上進行hook執行個體方法),進行method swizzling,對類的methodLists

    struct objc_method_list **methodLists

    進行修改,修改的方法有兩個:
    1. forwardInvocation:

      forwardInvocation: 的IMP(方法實作)被替換為:

      __ASPECTS_ARE_BEING_CALLED__

      ,該函數内部具體執行被hook的selector和切入的操作,forwardInvocation: 的本來的IMP被儲存在

      __aspects_forwardInvocation:

      中。forwardInvocation:的method swizzling操作是在

      static Class aspect_hookClass(NSObject *self, NSError **error)

      中進行的;
    2. 被hook的selector。被hook的selector的IMP被替換為:

      _objc_msgForward

      /

      _objc_msgForward_stret

      ,該方法用于觸發消息轉發,被hook的selector的本來的IMP被儲存在

      aliasSelector

      中。被hook的selector 的method swizzling 是在

      static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error)

      中進行的。
  2. 第二個方法為執行個體方法,receiver為要被hook的類的執行個體。用該方法hook執行個體方法較複雜,步驟簡述如下:
    1. 新生成一個被hook類的子類,
    2. 利用isa swizzle将該執行個體的isa

      Class isa

      指向新生成的被hook類的子類,
    3. 對被hook類的子類進行method swizzling,method swizzling的思路與上面類方法的method swizzling相同。

這裡直接拿世健同學的例子來講解吧:

@interface Target : NSObject
- (void)targetSelector;
@end

- (void)targetSelector
{
    NSLog(@"%@'s original selector: %@", self, NSStringFromSelector(_cmd));
}
           

我們要對Target類的targetSelector進行hook,以方法二(執行個體方法)為例進行講解,假定aTarget為Target類的一個執行個體方法。

Target *aTarget = [[Target alloc] init];
    [aTarget aspect_hookSelector:NSSelectorFromString(@"targetSelector")
                     withOptions:AspectPositionBefore
                      usingBlock:^(id<AspectInfo> aspectInfo) {
        NSLog(@"Hook targetSelector");
    }
                           error:NULL];
    [aTarget targetSelector];
           

hook之前aTarget及targetSelector的指向:

Aspects 源代碼解析&lt;一&gt;Aspects 是什麼,解決了什麼問題?Aspects的思路及實作

hook的過程:

hook Class(“isa swizzling”):

  1. 通過

    statedClass = self.class

    擷取self本來的class(class方法被重寫了,用來擷取self被hook之前的Class(Target)。下文交代class方法是如何被重寫的);
  2. 通過

    Class baseClass = object_getClass(self)

    擷取self的isa指針實際指向的class(self在運作時實際的class,表面上看這是一隻皮鞋(statedClass),實際上這是一隻刮胡刀(basedClass))。
  3. 如果baseClass(實際指向的class)已經是被hook過的子類,則傳回baseClass。
  4. 如果baseClass是MetaClass或者被KVO過的Class,則不必再生成subClass,直接在其自身上進行method swizzling。
  5. 如果不是上述3. 、4. 所述情況,預設情況下需要對被hook的Class進行”isa swizzling”:
    1. 通過

      subclass = objc_allocateClassPair(baseClass, subclassName, 0)

      動态建立一個被hook類(

      Target

      )的子類(

      Target_Aspects_

      );
    2. 然後對子類的

      forwardInvocation:

      進行method swizzling,替換為

      __ASPECTS_ARE_BEING_CALLED__

      ,進行消息轉發時,實際執行的是

      __ASPECTS_ARE_BEING_CALLED__

      中的方法;
    3. 重寫子類的擷取類名的方法

      class

      ,使其傳回被hook之前的類的類名;
    4. 将self(aTarget)的isa指針指向子類

      Target_Aspects_

      object_setClass(self, subclass)

      )。

class被hook後的情況:

Aspects 源代碼解析&lt;一&gt;Aspects 是什麼,解決了什麼問題?Aspects的思路及實作
#pragma mark - Hook Class

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, );
        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;
}
           

hook selector:

  1. 用新生成的aspects__targetSelector指向selector的IMP;
  2. 将selector指向_objc_msgForward(或者_objc_msgForward_stret,與前者的差別為傳回值的不同,該實作傳回結構體?)。

selector 被hook後:

Aspects 源代碼解析&lt;一&gt;Aspects 是什麼,解決了什麼問題?Aspects的思路及實作

經過上面hook class和hook selector,Target的targetSelector就被hook進“切面”了。當調用

[aTarget targetSelector]

時,程式流程如下:

Aspects 源代碼解析&lt;一&gt;Aspects 是什麼,解決了什麼問題?Aspects的思路及實作

參考:

http://www.cnblogs.com/jyh317/p/3834271.html 簡單了解AOP(面向切面程式設計)

http://blog.ibireme.com/2013/11/26/objective-c-messaging/ Objective-C 中的消息與消息轉發

http://blog.cnbang.net/tech/2855/ JSPatch實作原理詳解<二>