任意方法的Swizzle的應(yīng)用之一AOP

在上篇博客曾聊過(guò)對(duì)任意方法Swizzle有多種應(yīng)用,其中之一就是對(duì)多個(gè)方法的開始或者結(jié)束添加統(tǒng)一的切面調(diào)用。很有名的Aspect庫(kù)利用類似于KVO實(shí)現(xiàn)原理來(lái)實(shí)現(xiàn),在運(yùn)行時(shí)給需要Swizzle的類動(dòng)態(tài)添加子類,同時(shí)該對(duì)象isa指針指向創(chuàng)建的子類,然后hook子類的forwardInvocation關(guān)聯(lián)到函數(shù)__ASPECTS_ARE_BEING_CALLED__。之后再將要hook的method的實(shí)現(xiàn)替換為_objc_msgForward,這樣對(duì)該method調(diào)用會(huì)就轉(zhuǎn)發(fā)到__ASPECTS_ARE_BEING_CALLED__,然后利用NSInvocation轉(zhuǎn)發(fā)該調(diào)用即可。而有了對(duì)任意方法Swizzle的方案后就可以另辟蹊徑來(lái)解決這個(gè)問(wèn)題。

上篇博客給了個(gè)簡(jiǎn)單的demo實(shí)現(xiàn),不過(guò)功能較弱,這次花了一天多時(shí)間,寫了一個(gè)較為成熟的實(shí)現(xiàn),總共三百行左右,其中一半是匯編。

先上代碼,先嚇走一票人再說(shuō)。

#import <objc/runtime.h>

typedef NS_OPTIONS(NSInteger, ZWInvocationOption) {
    ZWInvocationOptionReplace = 1,
    ZWInvocationOptionBefore = 1 << 1,
    ZWInvocationOptionAfter = 1 << 2,
    ZWInvocationOptionOnly = 1 << 3,
};

#if defined(__arm64__)

static NSMutableDictionary  *_ZWBeforeIMP;
static NSMutableDictionary  *_ZWOriginIMP;
static NSMutableDictionary  *_ZWAfterIMP;
static NSLock  *_ZWLock;

OS_ALWAYS_INLINE void ZWStoreParams(void) {
    asm volatile("str    d7, [x11, #0x88]\n\
                 str    d6, [x11, #0x80]\n\
                 str    d5, [x11, #0x78]\n\
                 str    d4, [x11, #0x70]\n\
                 str    d3, [x11, #0x68]\n\
                 str    d2, [x11, #0x60]\n\
                 str    d1, [x11, #0x58]\n\
                 str    d0, [x11, #0x50]\n\
                 str    x8, [x11, #0x40]\n\
                 str    x7, [x11, #0x38]\n\
                 str    x6, [x11, #0x30]\n\
                 str    x5, [x11, #0x28]\n\
                 str    x4, [x11, #0x20]\n\
                 str    x3, [x11, #0x18]\n\
                 str    x2, [x11, #0x10]\n\
                 str    x1, [x11, #0x8]\n\
                 str    x0, [x11]\n\
                 ");
}
OS_ALWAYS_INLINE void ZWLoadParams(void) {
    asm volatile("ldr    d7, [x11, #0x88]\n\
                 ldr    d6, [x11, #0x80]\n\
                 ldr    d5, [x11, #0x78]\n\
                 ldr    d4, [x11, #0x70]\n\
                 ldr    d3, [x11, #0x68]\n\
                 ldr    d2, [x11, #0x60]\n\
                 ldr    d1, [x11, #0x58]\n\
                 ldr    d0, [x11, #0x50]\n\
                 ldr    x8, [x11, #0x40]\n\
                 ldr    x7, [x11, #0x38]\n\
                 ldr    x6, [x11, #0x30]\n\
                 ldr    x5, [x11, #0x28]\n\
                 ldr    x4, [x11, #0x20]\n\
                 ldr    x3, [x11, #0x18]\n\
                 ldr    x2, [x11, #0x10]\n\
                 ldr    x1, [x11, #0x8]\n\
                 ldr    x0, [x11]\n\
                 ");
}

OS_ALWAYS_INLINE void ZWGlobalOCSwizzle(void) {
    asm volatile("stp    x29, x30, [sp, #-0x10]!");
    
    asm volatile("mov    x29, sp\n\
                 sub    sp, sp, #0xb0");
    
    asm volatile("mov    x11, sp");
    asm volatile("bl    _ZWStoreParams");

    asm volatile("mov    x0, sp");
    asm volatile("bl    _ZWBeforeInvocation");
    
    asm volatile("mov    x0, sp");
    asm volatile("bl    _ZWInvocation");
    
    asm volatile("str    x0, [sp, #0xa0]");
    asm volatile("str    d0, [sp, #0xa8]");

    asm volatile("mov    x0, sp");
    asm volatile("bl    _ZWAfterInvocation");
    
    asm volatile("ldr    x0, [sp, #0xa0]");
    asm volatile("ldr    d0, [sp, #0xa8]");
    
    asm volatile("mov    sp, x29");
    asm volatile("ldp    x29, x30, [sp], #0x10");
}

OS_ALWAYS_INLINE NSString *ZWGetMetaSelName(SEL sel) {
    return [@"__META_" stringByAppendingString:NSStringFromSelector(sel)];
}

OS_ALWAYS_INLINE id ZWGetInvocation(NSDictionary *dict, id obj, SEL sel) {
    Class class = object_getClass(obj);
    NSString *className = NSStringFromClass(class);
    NSString *selName = class_isMetaClass(class) ? ZWGetMetaSelName(sel) : NSStringFromSelector(sel);
    [_ZWLock lock];
    id Invocation = dict[className][selName];
    [_ZWLock unlock];
    return Invocation;
}

OS_ALWAYS_INLINE NSUInteger ZWGetInvocationCount(NSDictionary *dict, id obj, SEL sel) {
    id ret = ZWGetInvocation(dict, obj, sel);
    if ([ret isKindOfClass:[NSArray class]]) {
        return [ret count];
    } else if ([ret isKindOfClass:NSClassFromString(@"NSBlock")]) {
        return 1;
    }
    return 0;
}

IMP ZWGetOriginImp(id obj, SEL sel) {
    id Invocation = ZWGetInvocation(_ZWOriginIMP, obj, sel);
    if ([Invocation isKindOfClass:[NSValue class]]) {
        return [Invocation pointerValue];
    }
    return NULL;
}

IMP ZWGetCurrentImp(id obj, SEL sel) {
    id Invocation = ZWGetInvocation(_ZWOriginIMP, obj, sel);
    if ([Invocation isKindOfClass:NSClassFromString(@"NSBlock")]) {
        if (!Invocation) return NULL;
        uint64_t *p = (__bridge void *)(Invocation);
        return (IMP)*(p + 2);
    }
    return NULL;
}

IMP ZWGetAopImp(NSDictionary *Invocation, id obj, SEL sel, NSUInteger index) {
    if (!obj || !sel) return NULL;
    id block = ZWGetInvocation(Invocation, obj, sel);
    if ([block isKindOfClass:[NSArray class]]) {
        block = block[index];
    }
    if (!block) return NULL;
    uint64_t *p = (__bridge void *)(block);
    return (IMP)*(p + 2);
}


void ZWAopInvocation(void **sp, NSDictionary *Invocation, ZWInvocationOption option) {
    id obj = (__bridge id)(*sp);
    SEL sel = *(sp + 1);
    if (!obj || !sel) return;
    NSInteger count = ZWGetInvocationCount(Invocation, obj, sel);
    __autoreleasing NSArray *arr = @[obj, [NSValue valueWithPointer:sel], @(option)];
    for (int i = 0; i < count; ++i) {
        ZWGetAopImp(Invocation, obj, sel, i);
        asm volatile("cbz    x0, LBB_20181107");
        asm volatile("mov    x17, x0");
        asm volatile("ldr    x11, %0": "=m"(sp));
        asm volatile("ldr    x12, %0": "=m"(arr));
        asm volatile("bl    _ZWLoadParams");
        asm volatile("mov    x1, x12");
        asm volatile("blr    x17");
        asm volatile("LBB_20181107:");
    }
}

void ZWAfterInvocation(void **sp) {
    ZWAopInvocation(sp, _ZWAfterIMP, ZWInvocationOptionAfter);
}
void ZWInvocation(void **sp) {
    __autoreleasing id obj;
    SEL sel;
    void *obj_p = &obj;
    void *sel_p = &sel;
    
    asm volatile("ldr    x11, %0": "=m"(sp));
    asm volatile("ldr    x10, %0": "=m"(obj_p));
    asm volatile("ldr    x0, [x11]");
    asm volatile("str    x0, [x10]");
    asm volatile("ldr    x10, %0": "=m"(sel_p));
    asm volatile("ldr    x0, [x11, #0x8]");
    asm volatile("str    x0, [x10]");
    
    asm volatile("ldr    x11, %0": "=m"(sp));
    asm volatile("ldr    x0, [x11]");
    asm volatile("ldr    x1, [x11, #0x8]");
    asm volatile("bl    _ZWGetOriginImp");
    asm volatile("cbnz    x0, LZW_20181105");
    
    __autoreleasing NSArray *arr = @[obj, [NSValue valueWithPointer:sel], @(ZWInvocationOptionReplace)];

    asm volatile("ldr    x11, %0": "=m"(sp));
    asm volatile("ldr    x0, [x11]");
    asm volatile("ldr    x1, [x11, #0x8]");
    asm volatile("bl    _ZWGetCurrentImp");
    asm volatile("cbz    x0, LZW_20181106");
    asm volatile("mov    x17, x0");
    asm volatile("ldr    x12, %0": "=m"(arr));
    asm volatile("ldr    x11, %0": "=m"(sp));
    asm volatile("bl    _ZWLoadParams");
    asm volatile("mov    x1, x12");
    asm volatile("blr    x17");
    asm volatile("b LZW_20181106");

    asm volatile("LZW_20181105:");
    asm volatile("mov    x17, x0");
    asm volatile("ldr    x11, %0": "=m"(sp));
    asm volatile("bl    _ZWLoadParams");
    asm volatile("blr    x17");
    asm volatile("LZW_20181106:");
}
void ZWBeforeInvocation(void **sp) {
    ZWAopInvocation(sp, _ZWBeforeIMP, ZWInvocationOptionBefore);
}

OS_ALWAYS_INLINE Method ZWGetMethod(Class cls, SEL sel) {
    unsigned int count = 0;
    Method retMethod = NULL;
    Method *list = class_copyMethodList(cls, &count);
    for (int i = 0; i < count; ++i) {
        Method m = list[i];
        SEL s = method_getName(m);
        if (sel_isEqual(s, sel)) {
            retMethod = m;
        }
    }
    free(list);
    return retMethod;
}

OS_ALWAYS_INLINE void ZWAddInvocation(NSDictionary *dict, NSString *className, NSString *selName, id block,  ZWInvocationOption options) {
    NSArray *tmp = dict[className][selName];
    if (options & ZWInvocationOptionOnly) {
        dict[className][selName] = block;
    } else {
        if ([tmp isKindOfClass:[NSArray class]]) {
            dict[className][selName] = [tmp arrayByAddingObject:block];
        } else if (!tmp) {
            dict[className][selName] = @[block];
        }
    }
}

void ZWAddAop(id obj, SEL sel, ZWInvocationOption options, id block) {
    if (options == ZWInvocationOptionOnly || !obj || !sel || !block) return;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _ZWOriginIMP = [NSMutableDictionary dictionary];
        _ZWBeforeIMP = [NSMutableDictionary dictionary];
        _ZWAfterIMP = [NSMutableDictionary dictionary];
        _ZWLock = [[NSLock alloc] init];
    });
    
    Class class = object_getClass(obj);
    Method method = NULL;
    NSString *className = NSStringFromClass(class);
    ZWGetMethod(class, sel, &method);//class_getInstanceMethod(class, sel)會(huì)獲取父類的方法
    NSString *selName = class_isMetaClass(class) ? ZWGetMetaSelName(sel) : NSStringFromSelector(sel);
    
    [_ZWLock lock];
    if (!_ZWOriginIMP[className]) {
        _ZWOriginIMP[className] = [NSMutableDictionary dictionary];
        _ZWBeforeIMP[className] = [NSMutableDictionary dictionary];
        _ZWAfterIMP[className] = [NSMutableDictionary dictionary];
    }

    if (options & ZWInvocationOptionReplace) {
        _ZWOriginIMP[className][selName] = block;
    } else {
        IMP originImp = method_getImplementation(method);
        if (originImp != ZWGlobalOCSwizzle) {
            _ZWOriginIMP[className][selName] = [NSValue valueWithPointer:originImp];
        }
    }
    
    if (options & ZWInvocationOptionBefore) {
        ZWAddInvocation(_ZWBeforeIMP, className, selName, block, options);
    }
    if (options & ZWInvocationOptionAfter) {
        ZWAddInvocation(_ZWAfterIMP, className, selName, block, options);
    }
    method_setImplementation(method, ZWGlobalOCSwizzle);
    [_ZWLock unlock];
}
#else
void ZWAddAop(id obj, SEL sel, ZWInvocationOption options, id block) {
}
#endif

這里只寫了arm64下的實(shí)現(xiàn),arm32機(jī)器已經(jīng)比較少,x86_64匯編比較麻煩,就不實(shí)現(xiàn)了。

入口函數(shù)ZWAddAop,前倆參數(shù)就不多說(shuō)了,第三個(gè)參數(shù)是一個(gè)block,這個(gè)block在定義的時(shí)候,一定要注意參數(shù)類型要對(duì)應(yīng),Method的前兩個(gè)參數(shù)簽名為固定的“@:”,而block第一個(gè)參數(shù)簽名是固定的“@?”,就是block本身,所以這里本來(lái)是需要一個(gè)占位的參數(shù)SEL和原始方法對(duì)應(yīng),后續(xù)的參數(shù)一一對(duì)應(yīng)即可,如果不需要使用某些參數(shù)可以省略聲明。但需要注意的是這里的占位參數(shù)被我改變成了NSArray,其會(huì)將當(dāng)前調(diào)用的對(duì)象和方法,以及調(diào)用位置封裝成數(shù)組傳遞,方便使用者。(簡(jiǎn)易實(shí)現(xiàn),所以直接用了公共容器)。另外需要說(shuō)明的是這里我并沒(méi)有校驗(yàn)block和原始方法簽名是否對(duì)應(yīng),需要的可以自行添加。

- (int )aMethod3:(NSString *)str;
對(duì)應(yīng)
^(NSArray *info, NSString *str) {};
如果不使用str, ^(NSArray *info){};不使用info,也可以不聲明

第四個(gè)參數(shù)ZWInvocationOption,表示需要在哪些位置插入該block調(diào)用。其中ZWInvocationOptionOnly不能單獨(dú)使用,其他情況都可以單獨(dú)使用或者復(fù)合使用。Only和Before,After復(fù)合使用時(shí)表示當(dāng)前添加的block是當(dāng)前位置唯一的調(diào)用,其他調(diào)用會(huì)被替換掉,否則在同一個(gè)方法的同一個(gè)位置可以添加多個(gè)block調(diào)用。Only和Replace復(fù)合沒(méi)有意義,因?yàn)槲矣X(jué)得沒(méi)有必要替換多次,因?yàn)閺墓δ苌现vBefore就可以完成這項(xiàng)需求,如果有需求的話可以自行修改。

ZWAddAop函數(shù)實(shí)現(xiàn)比較容易看懂,需要說(shuō)明一下的是,這里我操作NSMutableDictionary時(shí)我加了鎖,如果有大量方法需要替換時(shí),可以采用串行調(diào)用而不是加鎖的方式,這樣速度會(huì)提高。如果是元類方法會(huì)在存儲(chǔ)的時(shí)候添加上__META_頭和實(shí)例方法區(qū)分開。另外class_getInstanceMethod會(huì)獲取父類的實(shí)現(xiàn),所以這里class_copyMethodList來(lái)搜索,因?yàn)榇嬖趦?nèi)存拷貝,所以效率不太好。

注意:優(yōu)化點(diǎn),將所有NSDictionary的selector key由NSString變成NSNumber,該地址是一個(gè)常量,而class對(duì)象是可以直接作為key的。AOP切面調(diào)用極為頻繁,這就會(huì)帶來(lái)極大的性能提升,優(yōu)化后甚至可以提高一個(gè)數(shù)量級(jí)。

以上是添加AOP調(diào)用的過(guò)程,接下來(lái)說(shuō)明一下調(diào)用過(guò)程。

所有添加AOP的方法,IMP會(huì)修改為ZWGlobalOCSwizzle方法,其聲明沒(méi)有參數(shù)沒(méi)有返回值,也不涉及任何OC和C的代碼,所以編譯器會(huì)給一個(gè)空的實(shí)現(xiàn),不會(huì)添加多余代碼,可以方便的添加匯編代碼(這種叫C樁(stub)函數(shù))。其邏輯還是很容易看懂的。

我大概說(shuō)一下,這里我抽離了參數(shù)入棧的的匯編到ZWStoreParams,這個(gè)函數(shù)會(huì)被多個(gè)地方調(diào)用,所以不能直接使用sp,我這里使用了x11,調(diào)用的時(shí)候需要設(shè)置x11的值(復(fù)雜的情況下使用寄存器,需要先暫存其值到棧上,方便之后恢復(fù)供后續(xù)使用,只不過(guò)比較麻煩,會(huì)涉及到初始??臻g的分配,這里其他地方不會(huì)使用x11存數(shù)據(jù)就簡(jiǎn)單處理了),調(diào)用ZWStoreParams,將參數(shù)存在棧上,同時(shí)將棧指針?lè)湃離0,傳遞給ZWBeforeInvocation使用。

接下來(lái)調(diào)用ZWBeforeInvocation,其接收了棧指針sp,上面有所有參數(shù),其內(nèi)部調(diào)用ZWAopInvocation。

在ZWAopInvocation中獲枚舉當(dāng)前方法當(dāng)前位置所有添加的AOP block。下面代碼會(huì)將一些信息封裝到數(shù)組中,后面寫x1也就是block調(diào)用的第二個(gè)參數(shù)(info)。

__autoreleasing NSArray *arr = @[obj, [NSValue valueWithPointer:sel], @(option)];

for循環(huán)所有調(diào)用,并依次調(diào)用ZWGetAopImp獲取所有block入口地址,ZWGetAopImp在字典中_ZWBeforeIMP或者_ZWAfterIMP獲取存入的Block,并計(jì)算出入口地址返回。

使用cbz指令, 如果x0==0就跳轉(zhuǎn)標(biāo)簽LBB_20181107,就是末尾。否則將入口地址寫入x17,接下來(lái)加載sp指針到x11,加載arr到x12,調(diào)用ZWLoadParams加載參數(shù)到寄存器,將arr寫入x1,最后跳轉(zhuǎn)到x17執(zhí)行block方法。

note:標(biāo)簽LZW_20181107似乎有一些要求,不能隨便亂寫,似乎要L打頭(L表示局部標(biāo)簽),后面就隨便了,確保唯一即可。

回到ZWGlobalOCSwizzle函數(shù),調(diào)用完ZWBeforeInvocation后,調(diào)用ZWInvocation,也就是原方法或是其替換方法。該方法也接收sp參數(shù),然后我們聲明obj,sel,及其指針obj_p,sel_p,接下來(lái)將sp和obj,sel的地址加載到寄存器,然后將ps,ps+8,也就是棧上的第一第二個(gè)參數(shù)寫入obj,sel中。這樣就把匯編的數(shù)據(jù)轉(zhuǎn)移到了C語(yǔ)言的變量里面,這也是內(nèi)聯(lián)匯編和OC/C要數(shù)據(jù)交互方法。

接下來(lái)將sp,sp+8寫入x0,x1,然后調(diào)用ZWGetOriginImp,其會(huì)嘗試獲取_ZWOriginIMP字典中的原始IMP并返回,匯編cbnz x0, LZW_20181105如果原始IMP不為NULL,則跳轉(zhuǎn)標(biāo)簽LZW_20181105。

再看標(biāo)簽LZW_20181105,其實(shí)現(xiàn)也比較簡(jiǎn)單,將原始IMP寫入x17,同時(shí)加載sp到x11,調(diào)用ZWLoadParams恢復(fù)參數(shù)到寄存器上,然后跳轉(zhuǎn)到x17執(zhí)行原始IMP后就結(jié)束了(變量聲明都是autoreleasing的,所以不會(huì)插入release也就不會(huì)破壞x0,d0)。

如果原始IMP為NULL,則表明其被替換過(guò)。則將obj,sel,調(diào)用位置ZWInvocationOptionReplace封裝成數(shù)組arr待用。加載sp,sp+8到x0,x1,然后調(diào)用ZWGetCurrentImp,獲取替換后的實(shí)現(xiàn)。其是一個(gè)block,計(jì)算出IMP并返回。cbz x0, LZW_20181106,如果返回值為NULL,則直接跳轉(zhuǎn)到標(biāo)簽LZW_20181106結(jié)束調(diào)用,否則繼續(xù)執(zhí)行,將IMP寫入x17,加載arr,sp到x12,x11,調(diào)用ZWLoadParams,恢復(fù)所有的參數(shù),這里和調(diào)用原始IMP不一樣的時(shí)候?qū)⒌诙€(gè)參數(shù)x1替換為數(shù)組arr。然后blr x17執(zhí)行IMP,之后跳轉(zhuǎn)到標(biāo)簽LZW_20181106,這里不能直接使用匯編的"ret"返回。因?yàn)樵摼渲髸?huì)調(diào)用___stack_chk_guard來(lái)檢查棧有沒(méi)有被破壞,不過(guò)幸運(yùn)的是該調(diào)用沒(méi)有破壞x0,d0寄存器。

最后再次回到ZWGlobalOCSwizzle函數(shù),調(diào)用完ZWInvocation后,將可能存儲(chǔ)返回值的寄存器x0,d0寫入sp的+0xa0,+0xa8的位置,然后加載sp為參數(shù)調(diào)用ZWAfterInvocation,其邏輯和ZWBeforeInvocation基本一致就不再多說(shuō)。

然后從sp的+0xa0,+0xa8的位置恢復(fù)數(shù)據(jù)到x0,d0,然后恢復(fù)sp,x29,x30寄存器,最后返回。

怎么使用呢?這里給一些例子

- (void)viewDidLoad {
    [super viewDidLoad];

    ZWAddAop(self, @selector(aMethod1:), ZWInvocationOptionBefore | ZWInvocationOptionOnly, ^(NSArray *info){
        NSLog(@"before1 : ");
    });
    ZWAddAop(self, @selector(aMethod1:), ZWInvocationOptionBefore | ZWInvocationOptionReplace  | ZWInvocationOptionAfter , ^(NSArray *info, int a){
        NSLog(@"before1 | replace1 | after1 : %d", a);
    });

    ZWAddAop(self, @selector(aMethod2:), ZWInvocationOptionAfter, ^(NSArray *info, NSString *str){
        NSLog(@"after2: %@", str);
    });

    ZWAddAop(self, @selector(aMethod2:), ZWInvocationOptionReplace | ZWInvocationOptionOnly, ^(NSArray *info, NSString *str){
        NSLog(@"replace2 | after2: %@ \n%@",info, str);
    });

    ZWAddAop(self, @selector(aMethod3::), ZWInvocationOptionReplace, ^int (NSArray *info, NSString *str){
        NSLog(@"replace3: %@", str);
        return 11034;
    });

    ZWAddAop(self, @selector(aMethod3::), ZWInvocationOptionAfter, ^(NSArray *info, NSString *str){
        NSLog(@"after31: %@ \n %@", info, str);
    });

    ZWAddAop(self, @selector(aMethod3::), ZWInvocationOptionAfter, ^(NSArray *info, NSString *str){
        NSLog(@"after32: %@", str);
    });
    
    ZWAddAop([self class], @selector(aMethod4:), ZWInvocationOptionReplace, ^(id info , int a){
        NSLog(@"META replace4:");
    });

    [self aMethod1:8848];
    [self aMethod2:@"test str"];
    int r = [self aMethod3:@"咋不上天呢" :@[@1,@2]];
    NSLog(@"return int: %d",r);
    [ViewController aMethod4:12358];
}

- (NSRange)aMethod1:(int)a {
    NSLog(@"method1: %d",a);
    return (NSRange){0,1};
}
- (void)aMethod2:(NSString *)str {
    NSLog(@"method2: %@", str);
}
- (int )aMethod3:(NSString *)str :(NSArray *)array{
    NSLog(@"method3: %@", str);
    return 11;
}
+ (void)aMethod4:(int )obj {
    NSLog(@"method4: %d", obj);
}

本來(lái)只是想寫一個(gè)demo供大家參考的,但發(fā)現(xiàn)很多東西還是要過(guò)手了才有說(shuō)服力,于是前前后后(主要是想簡(jiǎn)單實(shí)現(xiàn)基本功能即可,后來(lái)發(fā)現(xiàn)很多功能特性還是得添加,于是乎就不斷涂涂改改...)花了兩天時(shí)間寫了個(gè)較完善些的實(shí)現(xiàn)。如果對(duì)穩(wěn)定性要求較高,可以多測(cè)試一下,匯編和OC/C混編確實(shí)容易出bug,寫的時(shí)候稍不注意就出現(xiàn)莫名其妙的問(wèn)題,需要不斷的修改和改進(jìn),另外我測(cè)試的case也不太足。

最后說(shuō)一下這套方案的優(yōu)缺點(diǎn)吧。

優(yōu)點(diǎn):1、簡(jiǎn)潔,雖然可能匯編細(xì)節(jié)不太懂,但大致的實(shí)現(xiàn)思路還是很容易看懂。
2、效率較高,大量使用匯編,減少函數(shù)調(diào)用,當(dāng)然也有一個(gè)龜速代表class_copyMethodList拖慢了效率,當(dāng)然也僅僅只是指針拷貝,而且大部分類的方法也就幾個(gè)十幾個(gè)。主要是沒(méi)有其他類似的方法,要替換class_copyMethodList就得自己訪問(wèn)Class的MethodList,這比較麻煩,需要大量的代碼才能實(shí)現(xiàn),不過(guò)class_copyMethodList調(diào)用不多。每個(gè)hook函數(shù)調(diào)用過(guò)程中AOP的開銷才是重點(diǎn)關(guān)注的地方,目前主要是內(nèi)存分配(OC對(duì)象創(chuàng)建)。

缺點(diǎn),改進(jìn)點(diǎn):
1、追求簡(jiǎn)潔,拋棄了一些細(xì)節(jié)問(wèn)題。比如傳入的block和原始方法的簽名沒(méi)有校驗(yàn)是否匹配,再比如:block的傳回的第一個(gè)參數(shù)使用NSArray來(lái)存儲(chǔ)obj,sel和調(diào)用位置,其實(shí)最好可以使用model對(duì)象,同時(shí)也可以傳回更多的信息。
2、整型參數(shù)大于6個(gè)或者浮點(diǎn)參數(shù)大于8個(gè)則無(wú)法處理。因?yàn)槎喑鰜?lái)的參數(shù)在棧上,目前我還沒(méi)有想出除了拷貝棧以外的簡(jiǎn)潔解決方案,而拷貝棧比較麻煩(我大概思考了一下,比較麻煩的部分是確定棧上存了多少數(shù)據(jù),這個(gè)得根據(jù)簽名和參數(shù)傳遞的規(guī)則去計(jì)算)。如果需要處理這類函數(shù)就需要自己另外處理了。

Github源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JOBridge之一任意方法的Swizzle 之前的博客都偏理論,這次來(lái)玩?zhèn)€有趣的。 JSPatch作為熱修復(fù)方案...
    吸血鬼de晚餐閱讀 1,031評(píng)論 0 2
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,068評(píng)論 0 9
  • 眾所周知,block可以封裝一個(gè)匿名函數(shù)為對(duì)象,并捕獲上下文所需的數(shù)據(jù),并傳給目標(biāo)對(duì)象在適當(dāng)?shù)臅r(shí)候回調(diào)。正因?yàn)閷⒊?..
    吸血鬼de晚餐閱讀 3,011評(píng)論 0 1
  • 關(guān)于OC中的消息發(fā)送的實(shí)現(xiàn),在去年也看過(guò)一次,當(dāng)時(shí)有點(diǎn)不太理解,但是今年再看卻很容易理解。 我想這跟知識(shí)體系的構(gòu)建...
    咖啡綠茶1991閱讀 1,072評(píng)論 0 1
  • 敬人愛已,規(guī)劃行為,提升服務(wù)! 為提升志愿者服務(wù)品質(zhì),孝顏迎來(lái)了志愿者禮儀培訓(xùn)課程。此次課程由中國(guó)形象協(xié)會(huì)...
    孝顏閱讀 768評(píng)論 0 0

友情鏈接更多精彩內(nèi)容