OC的消息機(jī)制深入理解

前言

這篇文章緊接上一篇, runtime理論我想大家都知道的, 上一篇是站在個人理解的角度, 探討了相關(guān)的一些動態(tài)原理,和基本實(shí)現(xiàn)思路,最后拓展到runtime框架, 而且也簡單寫了動態(tài)添加屬性和KVO簡單實(shí)現(xiàn)的demo,如果有興趣可以去看深入理解OC的運(yùn)行時(Runtime)

編碼相關(guān)

具體可通過編譯指令 @encode(type) typeof()

@encode(type) 必須傳入類型 不能是具體的變量
typeof() 傳入的可以是變量, 可以是類型, 通過typeof獲取到類型然后傳給@encode,最后會返回對應(yīng)的 const char*, 這個就是類型的編碼, 同樣可以獲取到block函數(shù)的(相當(dāng)于沒有獲取到), 這里舉出一些例子:

** 先定義一個Log輸出的宏(來自RAC宏的運(yùn)用改編,有時間會專門寫一篇宏相關(guān)的東西) ,這里設(shè)置的最多10個參數(shù), 如果不夠,可以自己看著往后加 **


static inline NSString* _LOG_GET_FORMAT(const char* typeCode,size_t size){
    NSString* result;
    ///無符號
    if (strcmp(typeCode, "I") == 0) result = @"%u";

    else if (strcmp(typeCode, "Q") == 0) result = @"%lu";

    else if (strcmp(typeCode, "C") == 0) result = @"%d";

    else if (strcmp(typeCode, "S") == 0) result = @"%d";

    else if (strcmp(typeCode, "i") == 0) {
        /// 可能是 'a' 也可能是 10
        if (size == sizeof(char)) {
            result = @"%c";

        }else result = @"%d";

    }else if(strcmp(typeCode, "q") == 0){
        result = @"%ld";

    }else if(strcmp(typeCode, "c") == 0){
        result = @"%c";

    }else if(strcmp(typeCode, "s") == 0){
        result = @"%d";

    }else if(strcmp(typeCode, "*") == 0){
        /// char*
        result = @"%s";

    }else if(strcmp(typeCode, "f") == 0){
        result = @"%f";

    }else if(strcmp(typeCode, "d") == 0){
        result = @"%f";

    }else{
        NSString* tmp = [NSString stringWithFormat:@"%s",typeCode];
        if ([tmp hasSuffix:@"c]"] || [tmp hasSuffix:@"C]"]) {
            result = @"%s";

        }else if([tmp hasPrefix:@"^"]){ ///指針
            result = @"%p";

        }else if([tmp isEqualToString:@"@"]) {
            result = @"%@";
        }else {
            result = [NSString stringWithFormat:@"%%%s",typeCode];
        }
    }
    return result;
}

#if __OBJC__

#define getTypeStr(_V) {\
NSString* _LogFormatStr = _LOG_GET_FORMAT(@encode(__typeof(_V)),sizeof(_V));\
NSLog(@"after: %@",_LogFormatStr);\
if([_LogFormatStr isEqualToString:@"%@"]) {\
NSLog(@"對象的打印 %@",_LogFormatStr);\
NSLog(_LogFormatStr,_V);\
}\
else {\
printf(_LogFormatStr.UTF8String,_V);\
printf("\n");\
}\
}
#define LogMaxArg 10
#define LogArgList0 10,9,8,7,6,5,4,3,2,1

#define Log(...) Log_args(LogMaxArg,__VA_ARGS__,LogArgList0)(__VA_ARGS__)
#define Log_args(N,...) Log_Find_index(__VA_ARGS__,LogArgList0)
#define Log_Find_index(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,...) LogFun(Log_,__VA_ARGS__)
#define LogFun(Fun,F,...) Fun##F

#define Log_1(_V)                   getTypeStr(_V)

#define Log_2(_V,...)               getTypeStr(_V);Log_1(__VA_ARGS__)

#define Log_3(_V,...)               getTypeStr(_V);Log_2(__VA_ARGS__)

#define Log_4(_V,...)               getTypeStr(_V);Log_3(__VA_ARGS__)

#define Log_5(_V,...)               getTypeStr(_V);Log_4(__VA_ARGS__)

#define Log_6(_V,...)               getTypeStr(_V);Log_5(__VA_ARGS__)

#define Log_7(_V,...)               getTypeStr(_V);Log_6(__VA_ARGS__)

#define Log_8(_V,...)               getTypeStr(_V);Log_7(__VA_ARGS__)

#define Log_9(_V,...)               getTypeStr(_V);Log_8(__VA_ARGS__)

#define Log_10(_V,...)              getTypeStr(_V);Log_9(__VA_ARGS__)
#endif

使用

////打印整型浮點(diǎn),基本指針
int a = 100;
float b = 1.23432e3;
char ch = 'A';
Log(a,b, 'a', ch, 919123L);

//// 打印結(jié)果
100
1234.319946
97
A
919123

////打印指針
float* fp = &b;
Log(fp, *fp, &b);

///打印結(jié)果
0x7ffee56ce8e8
1234.319946
0x7ffee56ce8e8

///打印c字符串
char *s = "ssssss";
char array[] = "array";
Log("aaa","bbb","ccc", s, array, 123, 890);

///打印結(jié)果:
aaa
bbb
ccc
ssssss
array
123
890


///打印oc對象 如果直接在宏的參數(shù)里填寫字典,數(shù)組, 加上括號,整體括起來
NSArray* tmp = @[@"xxxx",@"bbbb",@{@"array":@123}];
    Log(@"123",
        (@{@"1233":@123}),
        tmp,
        "這個是c的字符串",
        324.12331f
        );

///打印結(jié)果:
{
    1233 = 123;
}
(
    xxxx,
    bbbb,
        {
        array = 123;
    }
)
這個是c的字符串
324.123322

各種數(shù)字(整型,浮點(diǎn))編碼

char: c
short: s
int: i
long: q
NSInteger: q
long long: q
float: f
double: d
unsigned char: C
unsigned short: S
unsigned int: I
unsigned long: Q
NSUInteger: Q
unsigned long long: Q

各種指針的編碼 就是在對應(yīng)的類型前面加上^

char* : *
short*: ^s
int*: ^i
long*: ^q
NSInteger*: ^q
long long*: ^q
float*: ^f
double*: ^d
unsigned char*: *
unsigned short*: ^S
unsigned int*: ^I
unsigned long*: ^Q
NSUInteger*: ^Q
unsigned long long*: ^Q
int**: ^^i
void* ^V
void** ^^V

c語言里數(shù)組的編碼

short[]: ^s
int[10]: [10i]
long[5]: [5q]
long long[8]: [9q]
NSInteger[7]: [7q]
unsigned short[]: ^S
....///規(guī)律自己尋找

const相關(guān)的編碼

const int a;     i
int const b;     i
int* const c;    ^i
int const* d;   r^i
const int* e;   r^i

const int a[] = {3};    [1i]
const int b[2];           [2i]
const int* c[] = {  NULL  };    [1^i]
int* const d[2] = {  NULL,NULL }; [2^i]

函數(shù)指針和block相關(guān) ?代表著匿名

   ////局部的函數(shù)指針和block
    void (*funtest)(void) = NULL;  ^?
    void (^blocktest)(void) = NULL; @?
    int (*funtest1)(int) = NULL; ^?
    int (^blocktest1)(int) = NULL;@?

結(jié)構(gòu)體,聯(lián)合體相關(guān)

///系統(tǒng)的
@encode(typeof(CGRect)) {CGRect={CGPoint=dd}{CGSize=dd}}

///自己定義的
struct tmp{
        int a;
        struct{
            int b;
            NSString* c;
        };
    };
///編碼格式結(jié)果
{tmp=i{?=i@}}


///聯(lián)合體
union{
        int a;
        int b;
    }d;
////d的編碼是 ?代表匿名 d這里是一個變量
(?=ii) 

union tmpU{
        int a;
        int b;
    }d;
////此時d的編碼是
(tmpU=ii) 

oc相關(guān)的編碼

@encode(typeof(instance))  @
@encode(typeof(NSObject)) {NSObject=#}
@encode(typeof(UIViewController)) {UIViewController=#}

@interface Person :NSObject{
  int* age;
  NSString* name;
}
@end
@encode(typeof(Person)) {UIViewController=#}


////類里方法的編碼通過runtime函數(shù)獲取到method, 然后再獲取編碼


方法簽名

?1. 在NSObject里有一個簽名的類NSMethodSignature
?2. 獲取NSMethodSignature的途徑有
??2.1 通過NSObject的接口

例子如下:


@implementation VC :UIViewController
+ (void)classMT{}
- (void)objcMT{}
@end
/**
-[NSObject methodSignatureForSelector:SEL]這個方法在頭文件里聲明的是對象方法, 但是以類方法的調(diào)用形式去調(diào)用也可以, 不知道為什么
*/

///1  class 獲取 object 方法
NSMethodSignature* obj = [ViewController methodSignatureForSelector:@selector(signatest1)];
    NSLog(@"class get -: %p",obj);

///2 object 獲取 object
    obj = nil;
    obj = [self methodSignatureForSelector:@selector(signatest1)];
    NSLog(@"object get -: %p",obj);

////3 class 獲取 class
    obj = nil;
    obj = [ViewController methodSignatureForSelector:@selector(signatest2)];
    NSLog(@"class get +: %p",obj);

////4 object 獲取 class
    obj = nil;
    obj = [self methodSignatureForSelector:@selector(signatest2)];
    NSLog(@"objcet get +: %p",obj);

////5 class 獲取 object 方式2
    obj = nil;
    obj = [ViewController instanceMethodSignatureForSelector:@selector(signatest1)];
    NSLog(@"class get -: %p",obj);

////5 class 獲取 class  方式2
obj = nil;
    obj = [ViewController instanceMethodSignatureForSelector:@selector(signatest2)];
    NSLog(@"class get +: %p",obj);

日志
0x0
0x600000228b00
0x600000228b00
0x0
0x600000228b00
0x0


總結(jié): 1.如果是獲取對象方法的簽名, 就用對象去調(diào)用methodSignatureForSelector或者用類去調(diào)用instanceMethodSignatureForSelector
???2.如果是獲取類方法的簽名, 就用類去調(diào)用methodSignatureForSelector
?
獲取簽名對象后, 函數(shù)簽名代碼如下:


- (void)signatest3:(int)a arg2:(NSString*)b{
//    return 1;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMethodSignature* obj = [self methodSignatureForSelector:@selector(signatest3:arg2:)];
    for (NSInteger i = 0; i < obj.numberOfArguments; ++i) {
        NSLog(@"%s",[obj getArgumentTypeAtIndex:i]);
    }
    NSLog(@"%s",obj.methodReturnType);
}
////@ : i @ V 
////對應(yīng) IMP的調(diào)用參數(shù)表
//// 第0個 id  @
//// 第1個SEL 對應(yīng) :
//// 第2個開始是參數(shù) 上面的是 int 所以是 i
//// 第3個 對應(yīng)上面的 NSString*  @
//// 返回值要單獨(dú)獲取, 這里是void 所以是V

??2.2 通過runtime的接口

還是上面的那個類定義

Method m = class_getInstanceMethod(ViewController.class, @selector(signatest3:arg2:));
    NSLog(@"%s",method_getTypeEncoding(m));
/// i28@0:8i16@20  這些數(shù)字代表什么含義,沒懂, 但是去掉數(shù)字就和上面對應(yīng)了,而且最開始的是從返回算的



?3. 自己簽名

創(chuàng)建簽名對象 NSMethodSignature 的時候,給的簽名類型,注意創(chuàng)建簽名的時候, 要把返回值也帶上

    NSMethodSignature* method = [NSMethodSignature signatureWithObjCTypes:"i@:i@"];
    for (NSInteger i = 0; i < method.numberOfArguments; ++i) {
        NSLog(@"%s",[method getArgumentTypeAtIndex:i]);
    }
    NSLog(@"%s",method.methodReturnType);
/// @:i@  對應(yīng)的方法  - (int)name:(int)a arg:(id)obj;
/// 第一是返回值 所以創(chuàng)建的時候要指定返回值i


消息轉(zhuǎn)發(fā)(利用系統(tǒng)提供的接口轉(zhuǎn)發(fā))

?在oc里如果對對象發(fā)送消息時, 最后沒有找到方法的實(shí)現(xiàn), 系統(tǒng)會自動進(jìn)入消息轉(zhuǎn)發(fā)流程, 而且在oc的層面提供了接口,在這些接口里我們可以轉(zhuǎn)發(fā)消息, 這里拿對象方法來說

? 1. 進(jìn)入+(bool)resolveInstanceMethod:(SEL)

這里系統(tǒng)會將之前傳入的SEL傳遞過來,我們知道, SEL是oc接收消息后去尋找方法匹配的標(biāo)識,和IMP是分開的, 這里把SEL方法的標(biāo)識傳過來, 表示當(dāng)前類沒有與SEL對應(yīng)的實(shí)現(xiàn)IMP, 所以這里就為這個SEL指定一個實(shí)現(xiàn)地址, 然后返回YES告訴系統(tǒng)處理了, 系統(tǒng)就不會往下走了

@implementation VC : UIViewController
- (void)forwardTestFun1{}

- (void)viewDidLoad{
  [super viewDidLoad];

  ////這里瞎調(diào)用了一個方法, 寫成這樣是為了消除警告
  objc_msgSend(self, sel_registerName("noMethodTest"));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
   if (sel_isEqual(sel, sel_getUid("noMethodTest"))) {
        class_addMethod(self, sel, imp_implementationWithBlock(^(id obj){
            Log("已經(jīng)被改變指向了,來到了這里執(zhí)行");
        }), "v@:");


        return 1;
    }
    return [super resolveInstanceMethod:sel];
}

@end

這一步主要的用途是在本類中動態(tài)為sel添加一個Method, 如果這里返回false, 或什么都不管, 就會去下一步. 這里要說明一點(diǎn), 就算將當(dāng)前沒有實(shí)現(xiàn)的noMethodTest的sel 改為forwardTestFun1的sel傳給父類,還是會崩潰

? 2. 消息轉(zhuǎn)發(fā)的第2步:- (id)forwardingTargetForSelector:(SEL)aSelector

這一步要求給系統(tǒng)傳一個對象, 要求這個對象有sel的實(shí)現(xiàn), 比如傳一個Person對象, 他實(shí)現(xiàn)了這sel

?3. 如果2不處理, 即返回了nil,或者沒有實(shí)現(xiàn)會進(jìn)入到這里- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector, 這個方法要求對sel提供一個簽名對象 eg:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"V@:"];
}

這里的簽名一定要和傳入的sel對應(yīng), 這里表示 - (void)funName{ }, v==void, @ == id : == sel 因?yàn)?objc_msgsend()的參數(shù)是 id, sel, 所以這里要提供@和:

?4. 如果3不實(shí)現(xiàn)或者,返回nil,崩潰, 如果返回了簽名則會執(zhí)行: - (void)forwardInvocation:(NSInvocation *)anInvocation, 這個方法會傳遞一個NSInvocation對象, 簽名已經(jīng)設(shè)置好了, 就是上面的一步我們設(shè)置的簽名, 這個方法要求提供一個對象供anInvocation調(diào)用, 而且要求對象必須實(shí)現(xiàn)了 同名的sel的實(shí)現(xiàn), 因?yàn)檫@里的anInvocationselector系統(tǒng)賦值的是傳過來的sel,所以一定會調(diào)用新對象同名的方法,如果想指定新對象別的方法,這里需要更改anInvocationselector屬性值,但是對應(yīng)實(shí)現(xiàn)的方法簽名必須和當(dāng)前參數(shù)sel的一致。代碼如下

@implementation Son :NSObject
- (void)test{
  Log("消息會不會被轉(zhuǎn)發(fā)來呢?");
}

- (void)noMethodTest{
  Log("消息一定會轉(zhuǎn)發(fā)到這里");
}
@end


VC中:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
///如果上一步提供的簽名不對, 也是崩潰的
    [anInvocation invokeWithTarget:Son.new];


///這里可以獲取到參數(shù)信息
NSMethodSignature* si = anInvocation.methodSignature;
    for (NSInteger i = 2; i < si.numberOfArguments; ++i) {
        @autoreleasepool {
            __autoreleasing NSObject* s;
            [anInvocation getArgument:&s atIndex:i];
            NSLog(@"%@",s);
        }
    }
}
///如果不實(shí)現(xiàn)noMethodTest, 崩潰, 方法找不到,如果實(shí)現(xiàn)了會調(diào)用noMethodTest


手動觸發(fā)消息轉(zhuǎn)發(fā)(方法監(jiān)聽AOP編程)

? 1. 手動觸發(fā)消息轉(zhuǎn)發(fā)還是和原理一樣的, 要實(shí)現(xiàn)即使 當(dāng)前調(diào)用的sel有對應(yīng)的IMP, 也要觸發(fā)轉(zhuǎn)發(fā)流程. 這里要說一點(diǎn)就是, 處理的操作肯定是在 轉(zhuǎn)發(fā)流程的其中一步, 因?yàn)榧热皇寝D(zhuǎn)發(fā), 一定會走到轉(zhuǎn)發(fā)流程的函數(shù),所以我們處理的地方就是在那幾步當(dāng)中(針對當(dāng)前類來說,后面拓展到通用的時候?qū)崿F(xiàn)方案又不一樣的)

場景1 假設(shè)現(xiàn)在的需求是 項目里所有Person對象用到的地方, 只要調(diào)用了- [Person runFrom: to:] 都必須在調(diào)用前打印 hello world


解決方案選擇

方案1
  • 找到工程里所有的Person實(shí)例化的對象, 然后查找哪里調(diào)用了- [Person runFrom: to:], 在每一個調(diào)用前都加上輸出語句


方案2
  • 修改Person中對應(yīng)方法的實(shí)現(xiàn), 在- [Person runFrom: to:]的實(shí)現(xiàn)里, 最開始加上輸出語句


方案3
  • 修改Person類的實(shí)現(xiàn), 在Person.m文件里加入私有的類比如 _PersonHelloWord 繼承自 Person, 修改Person的初始化操作返回 _PersonHelloWord, 然后_PersonHelloWord的實(shí)現(xiàn)里重寫- [Person runFrom: to:], 加入輸出語句, 再調(diào)用父類的方法


方案4
  • 不修改Person類, 也不創(chuàng)建子類, 利用runtime的方案, 這里可以為Person添加一個分類, 在分類里寫一個新的方法, 然后利用方法交換的技術(shù)實(shí)現(xiàn)需求, 當(dāng)然這不算是消息轉(zhuǎn)發(fā),注意即使他媽的輸出語句是不需要參數(shù)的,但是交互的方法也必須提供


方案5
  • 修改Person, 用消息轉(zhuǎn)發(fā)的技術(shù)去處理, 具體的介紹在方案4的代碼之后


方案6

?
1,2種我就不說了, 至于第3種,我想沒有人會這樣搞, 這種方案可能會用在別的情況下, 總之既然是方案, 那就沒有好壞之分, 只有適用和不適用之說, 第4種我下面簡單給出偽代碼, 第5種要重點(diǎn)探討的, 第6種會介紹一下,給出核心代碼

方案4 的部分代碼

@interface Person :NSObject
- (void)runFrom:(NSString*)addressStart to:(NSString*)addressEnd;
@end

@implementation Person
- (void)runFrom:(NSString*)addressStart to:(NSString*)addressEnd{
  Log("走啊走,走啊走,走啊走!");
}
@end

@interface Person(HelloWorld)
+ (void)helloWorldInit;
@end

@implementation Person(HelloWorld)
+ (void)helloWorldInit{
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       Method originM = class_getInstanceMethod(self, @selector(runFrom:to:));
       Method newM =  class_getInstanceMethod(self, @selector(helloworld: to:));
      ////這里不需要考慮 replace的問題([詳見上一篇里方法交換的部分)

        method_exchangeImplementations(originM,newM);
    });
}

- (void)helloworld:(NSString*)cao to:(NSString*)nima{
  Log("產(chǎn)品有病啊, 輸出一句hello world, 日!");
  [self helloworld:cao to:nima];
}
@end


方案5 的實(shí)現(xiàn)方案介紹

0 \color{#ff0000}{首先先不考慮 返回值是結(jié)構(gòu)體或者浮點(diǎn)數(shù)的情況, 后面會講的}

1 要實(shí)現(xiàn)消息轉(zhuǎn)發(fā), 在正常sel存在IMP的情況下, 如果不做處理, 是不會進(jìn)入轉(zhuǎn)發(fā)流程的, 所以要做的事情是手動代碼控制, 進(jìn)入轉(zhuǎn)發(fā)流程

手動進(jìn)入轉(zhuǎn)發(fā)流程有多種方案, 但原理都是修改sel的IMP(這個runtime提供了接口)
1.1 讓當(dāng)前的sel指向一個 和sel原始IMP簽名不一樣的函數(shù)或block, 因?yàn)楹灻粚? 找不到實(shí)現(xiàn)進(jìn)入轉(zhuǎn)發(fā)
1.2 直接讓sel指向 系統(tǒng)轉(zhuǎn)發(fā)函數(shù) _objc_msgForward, 強(qiáng)制進(jìn)入轉(zhuǎn)發(fā)

2 進(jìn)入轉(zhuǎn)發(fā)流程后, 在第三步進(jìn)行操作-[NSObject forwardInvocation:invo]

3 -[NSObject forwardInvocation:invo]會將函數(shù)簽名和參數(shù)類型,返回類型, 消息接收者等包裝到invo

4 我們要將 當(dāng)前的sel, 即runFrom:to: 通過 invo 指定一個新的 newTarget對象, 讓這個newTarget 去執(zhí)行剩下的操作

5 newTarget 執(zhí)行 runFrom:to:, 首先 輸出 hello wrold, 然后再想辦法回調(diào)原來的實(shí)現(xiàn)

6 所以 第4步中, 在指定給newTarget的時候, 要將當(dāng)前對象eg:originalTarget傳遞給newTarget, 因?yàn)榧僭O(shè) newTarget能調(diào)用到原來的 runFrom:to:, 但是原始的runFrom:to:里可能通過 originalTarget來獲取相關(guān)的成員變量等信息, 這就要求了 newTarget在回調(diào)原來的方法時要原封不動的通過 [originalTarget runFrom:arg0 to:arg1]的方式去調(diào)用,這不需要傳遞參數(shù), 因?yàn)檗D(zhuǎn)發(fā)給newTargerunFrom:to:時,參數(shù)也傳遞了過來

7 第6步的分析又出現(xiàn)新的問題, 就是 [originalTarget runFrom:arg0 to:arg1] 會直接進(jìn)入消息轉(zhuǎn)發(fā), 因?yàn)樯厦嬉呀?jīng)說了, originalTargetrunFrom:to:已經(jīng)被替換成了_objc_msgForward, 那怎么解決呢

8 第7步解決的方案, 那只有在將 runFrom:to的IMP替換為 _objc_msgForward之前, 就先記錄下來, 然后在 4步里創(chuàng)建newTarget的時候, 將IMP賦值給newTarget, 這樣newTarget可以直接調(diào)用
IMP(newTarget.originalTarget, NSSelectorFromeString(@"runFrom:to:"), arg0,arg1)

9 反思, 上述流程里是靜態(tài)創(chuàng)建了一個轉(zhuǎn)發(fā)對象newTarget, 那么newTarget的類型,即類的設(shè)計可以考慮靜態(tài)xcode的方式去創(chuàng)建, 也可以考慮動態(tài)創(chuàng)建, 那既然這里講的是runtime, 那就用runtime來創(chuàng)建類, 豈不是逼格更高?!!

10 選擇方案, 動態(tài)創(chuàng)建當(dāng)前類Person的子類, 然后添加 子類的 runFrom:to:的IMP, 而且動態(tài)創(chuàng)建的子類要有擴(kuò)充的內(nèi)存, 用來添加一個元素的對象originalTarget, 這里就用到了 runtime的 add_Var的技巧了, 具體流程圖如下:(有點(diǎn)丑,但不要糾結(jié))

Snip20190518_1.png

核心代碼

@interface Person : NSObject
@property (nonatomic,strong) NSString* name;

- (void)runFrom:(NSString*)startAddStr to:(NSString*)endAddStr;
@end



@implementation Person
- (void)runFrom:(NSString*)startAddStr to:(NSString*)endAddStr{
    NSLog(@"%@正在run... 從%@ 到 %@",self.name,startAddStr,endAddStr);
}


+ (void)initialize{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self helloRegisterMethod];
    });

}

#pragma mark - 每個對象都調(diào)換
+ (void)helloRegisterMethod{

    ////找到目標(biāo)方法
    SEL tarSEL = sel_getUid("runFrom:to:");
    Method tarM = class_getInstanceMethod(self, tarSEL);

    ////如果找到
    if (tarM) {
        ///子類的類名 當(dāng)前類的名字 + _ + subClass
        NSString* subClass = [NSStringFromClass(self) stringByAppendingFormat:@"_subClass"];

        ///創(chuàng)建子類 順便給一個var的空間, 用來保存轉(zhuǎn)發(fā)前的父類的實(shí)例對象, 目的是調(diào)用父類的實(shí)現(xiàn)的時候, 將這個對象傳給IMP, 保證原來的方法中做的事情不會被更改
        Class sub = objc_allocateClassPair(self, subClass.UTF8String, sizeof(void*));

        if (sub) {
            /////這里先獲取到IMP的地址, 如果在block內(nèi)部獲取, 不知道為什么獲取的是 _objc_msgForward的地址
            IMP org = method_getImplementation(tarM);

            ////添加一個var
            NSString* varName = [NSStringFromClass(self) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
            ///這里解釋下第3g 參數(shù), 內(nèi)存對齊的問題, 他的值與 要添加var的類型有關(guān), 也和cpu的架構(gòu)有關(guān),如果是指針, 則傳log2(sizeof(type*)), 官方文檔說的很清楚
            class_addIvar(sub,
                          varName.UTF8String,
                          sizeof(void*),
                          log2(sizeof(void*)),
                          @encode(NSString*));


            ////為子類添加 一個同樣的sel的實(shí)現(xiàn), 相當(dāng)于是覆蓋了父類
            bool addMethod = class_addMethod(sub, tarSEL, imp_implementationWithBlock(^void(id obj,NSString* arg1,NSString* arg2){
                ///先輸出hello world
                NSLog(@"hello world!");

                NSString* varName = [NSStringFromClass(self.class) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
                Ivar var = class_getInstanceVariable(sub, varName.UTF8String);
                if (var) {
                    ///再調(diào)用原始的imp
                    org(object_getIvar(obj, var),tarSEL,arg1,arg2);
                }


            }), method_getTypeEncoding(tarM));
            if (addMethod) {
                NSLog(@"添加成功");
            }

            ///注冊子類
            objc_registerClassPair(sub);

            class_replaceMethod(self, tarSEL, _objc_msgForward, method_getTypeEncoding(tarM));

        }
    }
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{


    NSString* subClass = [NSStringFromClass(self.class) stringByAppendingFormat:@"_subClass"];
    Class sub = objc_getClass(subClass.UTF8String);
    if (sub) {
        id subObj = [sub new];

         NSString* varName = [NSStringFromClass(self.class) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(anInvocation.selector)];
        Ivar var = class_getInstanceVariable(sub, varName.UTF8String);
        if (var) {
            object_setIvar(subObj, var, anInvocation.target);
        }

        return [anInvocation invokeWithTarget:subObj];

    }

}
@end

方案6 的實(shí)現(xiàn)

  • Person里加一個類方法, 最好在Person初始化或者第一次用的的時候調(diào)用
  • 在這個類方法中像上一篇實(shí)現(xiàn)KVO的原理一樣, 只不過這里不是替換setter,這里是替換 runFrom:to:
  • 新的實(shí)現(xiàn)里 先輸出 hello wrold, 然后調(diào)用父類的實(shí)現(xiàn)
  • 這里要注意的一點(diǎn)是,怎么調(diào)用父類的代碼, 這里先插入一段關(guān)于super調(diào)用的問題

super代表的僅僅是編譯器指令, 并不是一個實(shí)體的對象, 比如 [super callFun:arg0], 代碼解析如下

///我們寫的代碼
[super callFun:arg0];

///最后編譯器編譯成
objc_msgSendSuper(tmpSuper->receiver, callFun, arg0)

///查看objc_msgSendSuper的定義
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL  _Nonnull op, ...)


///查看接頭體的定義 
struct objc_super {
   __unsafe_unretained _Nonnull id receiver;

#if !defined(__cplusplus)  &&  !__OBJC2__
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
        };

//參數(shù)1 代表消息接收者, 參數(shù)2代表superClass的類對象
//編譯器創(chuàng)建臨時的tmpSuper偽代碼如下
struct objc_super* tmpSuper = malloc(sizeof(struct objc_super));
tmpSuper->receiver = self;
tmpSuper->super_class =  class_getSuperclass( object_getClass(self) );


///然后將創(chuàng)建好的 tmpSuper傳給 objc_msgSendSuper
objc_msgSendSuper(tmpSuper, callFun, arg0);

///objc_msgSendSuper的內(nèi)部會根據(jù) tmpSuper的 super_class, 找到父類類對象的地址
/// 然后去方法列表里尋找方法, 找到方法之后eg:findIMP, 就會直接通過IMP調(diào)用
findIMP(tmpSuper->receiver, callFun, arg0);

///所以很多面試?yán)飭柕?[super class], 返回的class是什么類型, 其實(shí)就像上面說的過程一樣,最后接收消息的對象始終沒有變, 所以還是當(dāng)前的類
///有的文檔說objc_msgSendSuper的內(nèi)部在找到方法的時候, 會再次調(diào)用 objc_msgSend(tmpSuper->receiver, callFun, arg0)
///我想應(yīng)該不是這樣, 這樣和 [self callFun:arg0] 沒有區(qū)別了, 因?yàn)閟uper表示到父類里找實(shí)現(xiàn)
///調(diào)用父類里處理的邏輯, 主要是和self覆蓋的callFun: 分開來, 所以我認(rèn)為是找到findIMP后, 直接執(zhí)行了

free(tmpSuper);

需求實(shí)現(xiàn)核心代碼

 Class reallyClass = object_getClass(self);

    NSString* tmpClassName = [_ClassPrefix stringByAppendingString:NSStringFromClass(reallyClass)];

    ///創(chuàng)建新的class
    Class newClass = objc_allocateClassPair(reallyClass, tmpClassName.UTF8String, 0);

    ///將Person的isa指向新建的類
    object_setClass(self, newClass);

    class_addMethod(newClass, NSSelectorFromString(@"runFrom:to:"), imp_implementationWithBlock(^void(__weak id obj, id value1, id value2){
        NSLog(@"hello world");
      
         ////自己構(gòu)建objc_msgSendSuper的第1個結(jié)構(gòu)體
        struct objc_super tmpSuper = {
              obj,
              class_getSuperclass( object_getClass(obj) );
        };
        objc_msgSendSuper(&tmpSuper, NSSelectorFromString(@"runFrom:to:"), value1, value2)

    }), "v@:@");
    objc_registerClassPair(newClass);

上述是為整個類替換掉! 能不能只指定某個對象, 其他對象不影響呢!!, 其實(shí)上一篇的KVO實(shí)現(xiàn)就是針對某個對象的, 其他對象不受影響


場景2 和上述需求一樣, 監(jiān)聽某個方法, 在方法執(zhí)行之后或之前做事情, 但是不同的是, 場景2要求只監(jiān)聽某些對象, 沒有被監(jiān)聽的對象不影響

比如Car這個類, 監(jiān)聽方法 runFrom:to:, 希望在這個方法執(zhí)行后, 再輸出一句 洗車, 但是只是Car的實(shí)例 car1會改變動作, 其他的car2, car3. car4等不受影響


對比場景1

? 和場景1不一樣的地方是, 這里只針對某些對象, 有點(diǎn)像上一篇的KVO(深入理解OC的運(yùn)行時(Runtime)), 這里也有 幾種對應(yīng)關(guān)系:
? 1VS1 比如 vc1 想監(jiān)聽car1的這個動作, 然后在car1執(zhí)行完方法后, 做自己的事情
? 1VS多, 比如view1也想像VC一樣監(jiān)聽 car1, 做自己的操作
? 多VS1 VC想同時監(jiān)聽 car1, car2
.... 總之和kvo的性質(zhì)一樣, 但是這里的實(shí)現(xiàn)和kvo有所不同


解決方案選擇

利用runtime的進(jìn)行轉(zhuǎn)發(fā), 用實(shí)現(xiàn)KVO一樣的原理, 但是這里是不再改變原有的類, 代碼如下

Car定義

@interface Car : NSObject
/** 車牌號*/
@property(strong, nonatomic) NSString* licence;
- (void)runFrom:(NSString*)start to:(NSString*)end;
@end


@implementation
- (void)runFrom:(NSString*)start to:(NSString*)end{
    NSLog(@"車牌是:%@ 從 %@ 開到了 %@",self.licence, start, end);
}
@end

全局字典的格式


 @{
    @"對象1的hash字符串":@{  對象1相關(guān)的信息字典

        @"sel1字符串":@{    對象1的 sel1 相關(guān)信息的字

            @"before": @[block1, block2, ...].multable
            @"replace": @[block1, block2, ...].multable
            @"after":@[block1,block2, ... ].multable

        }.mutable,




        @"sel2字符串":@{    對象1的 sel2 相關(guān)信息的字典
            ///和sel1同樣的結(jié)構(gòu)
        }.mutable,




        ...


    }.multable,



 @"對象2的hash字符串":@{
    和對象1同樣的結(jié)構(gòu)


 }.multable

 }.multable

轉(zhuǎn)發(fā)分類 相關(guān)宏的頭文件


#ifndef LBForwardToolMacro_h
#define LBForwardToolMacro_h

#ifdef __OBJC__
#define LB_CAT(A,B) A##B

#pragma mark - LBF error macro
#define LBFEC(_C) LB_CAT(LBFEC,_C)
#define LBFOT(_V) LB_CAT(LBFOT, _V)

#endif

錯誤類的定義


#import <Foundation/Foundation.h>

#import "LBForwardType.h"


#define TimeBegin_ 2000

/** LBForwardErrorCode */
typedef NS_ENUM(int,LBFEC) {
    /** 其他錯誤 */
    LBFEC(Other) = INT_MIN,

    /** 回調(diào)不能空 */
    LBFEC(EmptyOperation) = TimeBegin_,

    /** sel不能空 */
    LBFEC(EmptySEL),

    /** delloc的監(jiān)聽時間點(diǎn)必須是調(diào)用dealloc之前 */
    LBFEC(DellocTime),

    /** 禁止監(jiān)聽alloc */
    LBFEC(AllocForbid),

    /** 禁止監(jiān)聽initXXXX */
    LBFEC(InitForbid),

    /** 禁止監(jiān)聽ARC相關(guān)的 內(nèi)存管理 */
    LBFEC(ARCForbid),

    /** 禁止監(jiān)聽轉(zhuǎn)函數(shù) forwardInvocation: */
    LBFEC(SYSForward),

    /** 沒有錯誤 */
    LBFEC(NoError),
};

#undef TimeBegin_



NS_ASSUME_NONNULL_BEGIN

@interface LBForwardError : NSObject
/** 錯誤碼 */
@property (nonatomic,readonly) LBFEC eCode;

/** 錯誤描述 */
@property (nonatomic,strong) NSString* eDes;

+ (instancetype)lbEWithCode:(LBFEC)code;

@property (nonatomic,copy,readonly) void (^otherErrorDes)(NSString* _Nonnull des);

+ (instancetype)lbOEWithDes:(NSString*)des;
@end

NS_ASSUME_NONNULL_END

#define LBError(_C) [LBForwardError lbEWithCode:(_C)]



@implementation LBForwardError
+ (instancetype)lbEWithCode:(LBFEC)code{
    return [[self alloc] initWithCode:code];
}


- (instancetype)initWithCode:(LBFEC)code{
    if (self = [super init]) {
        self->_eDes = [self switchCode:code];
    }
    return self;
}


- (NSString*)switchCode:(LBFEC)code{
    _eCode = code;
    return [LBForwardError switchCode:code];
}

+ (NSString*)switchCode:(LBFEC)code{

    switch (code) {
        case LBFECEmptyOperation: return @"回調(diào)不能空";

        case LBFECEmptySEL: return @"原SEL不能空";

        case LBFECDellocTime:return @"回調(diào)必須在調(diào)用dealloc之前";

        case LBFECAllocForbid:return @"alloc 不能監(jiān)聽";

        case LBFECInitForbid: return @"initXXX 不能監(jiān)聽";

        case LBFECARCForbid: return @"ARC(retain release, autoRelease) 不能監(jiān)聽";

        case LBFECSYSForward:return @"禁止監(jiān)聽forwardInvocation:";

        case LBFECNoError: return @"no error";


        default:return @"其他錯誤";
    }
}

+ (instancetype)lbOEWithDes:(NSString*)des{
    LBForwardError* error = [self lbEWithCode:LBFECOther];

    if (des.length)
        error->_eDes = [des copy];

    return error;
}
@end




這個類主要的作用是注冊監(jiān)聽方法的時候, 出錯的描述

轉(zhuǎn)發(fā)的分類頭文件

#import "LBForwardType.h"


#define TimeBegin_ 3000

/** LBForwardOperationTime */
typedef enum: NSInteger{
    /** 在調(diào)用方法之前 回調(diào) */
    LBFOT(Before) = TimeBegin_,

    /** 替換之前的方法 */
    LBFOT(Replace),

    /** 調(diào)用完之前的方法后 再回調(diào) */
    LBFOT(After)
}LBFOT;

#undef TimeBegin_



@class LBForwardError;
typedef void(^LBFEBlock)(LBForwardError* _Nonnull error);


NS_ASSUME_NONNULL_BEGIN

@interface NSObject (LBForward)

/**
 對self的方法進(jìn)行監(jiān)聽, 在指定的time做自己的操作

 @param opBlock 你的操作 block, 簽名格式要對應(yīng)sel block的第一個參數(shù) == self, 后面開始是 sel的參數(shù)列表, block里沒有傳sel出來
 @param oSel 原方法的sel
 @param time 操作的時間點(diǎn)
 @param error 錯誤說明

 PS: 如果error為空 可能會拋異常, 異常里的 userInfo[@"error"] == LBForwardError
 */
- (void)lbf_hookOperationBlock:(id)opBlock
                   originalSel:(SEL)oSel
                   executeTime:(LBFOT)time
                         catch:(LBFEBlock _Nullable)error;

- (void)lbf_resign;
- (void)lbf_resignSEL:(SEL)oSel;


- (void)lbf_resignSEL:(SEL)oSel
          executeTime:(LBFOT)time;
@end

NS_ASSUME_NONNULL_END

轉(zhuǎn)發(fā)的分類的實(shí)現(xiàn)

#import "NSObject+LBForward.h"

#import <objc/message.h>

#import "LBForwardError.h"



#pragma mark - macors
#define LB_FORWARD_SET_OP_B_KEY     @"before"
#define LB_FORWARD_SET_OP_R_KEY     @"replace"
#define LB_FORWARD_SET_OP_A_KEY     @"after"

#define LB_FORWARD_SET_SELF_CAPACITY  5
#define LB_FORWARD_SET_SEL_CAPACITY   3
#define LB_FORWARD_SET_OP_CAPACITY  2



#define LB_HOOK_LOCK_KEY @"registerLock"

#define SYS_SELF_DEALLOC @"dealloc"
#define SYS_SELF_ARC_RT  @"retain"
#define SYS_SELF_ARC_RC  @"retainCount"
#define SYS_SELF_ARC_RL  @"release"
#define SYS_SELF_ARC_AR  @"autorelease"
#define SYS_SELF_FORWARD @"forwardInvocation:"
#define SYS_SELF_PREFIX_INIT @"init"

#define LB_HOOK_CLASS_PREFIX @"LBF_CLASS_"
#define LB_HOOK_SEL_PREFIX   @"LBF_SEL_"


#define LB_ERROR_TITLE @"出錯, 請看異常里的userinfo[@\"error\"] LBForwardError相關(guān)的報錯"
#define LB_Throw(_info) @throw [NSException exceptionWithName:LB_ERROR_TITLE reason:@"" userInfo:@{@"error":_info}]


#define LB_FORWARD_PARAM_NAME_OP    opBlock

#define LB_FORWARD_PARAM_NAME_SEL   oSel

#define LB_FORWARD_PARAM_NAME_TIME  workTime


#define LB_FORWARD_PARAM_NAME_E         registerError
#define LB_FORWARD_PARAM_NAME_E_TYPE    (void(^)(LBForwardError*))LB_FORWARD_PARAM_NAME_E


#pragma mark - block的結(jié)構(gòu)
typedef struct LBBlockLayout LBBlockLayout;


struct LBBlockLayout{
    void *isa;                      ///block指向的類, 這里oc是為了統(tǒng)一, 因?yàn)楫?dāng)一個參數(shù)是id的時候, 可以傳任意的block,而且還可以通過 NSInvocation 指定target, 設(shè)置好簽名參數(shù)調(diào)用block的實(shí)現(xiàn)

    int flags;                      ///標(biāo)記
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);             // (1<<25)
        void (*dispose)(void *src);
        const char *signature;                          // (1<<30)
    } *descriptor;
};

typedef enum:int{
    LBBlockFlagsCopyDispose = (1 << 25),
    LBBlockFlagsIsGlobal = (1 << 28),
    LBBlockFlagsHasSignature = (1 << 30)
} LBBlockFlag;

typedef NSNumber* LBForwardIMPAdd;



#pragma mark - 全局的Map
#define LB_FORWARD_SET_CAPACITY 20
static NSMutableDictionary* LB_FORWARD_SET(){
    static NSMutableDictionary* dic_;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dic_ = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_CAPACITY];
    });
    return dic_;
}
#define LB_FORWARD_SET LB_FORWARD_SET()




#pragma mark - self相關(guān)信息的key
static inline NSString* LB_FORWARD_SET_SELF_KEY(id self){
    if(!object_isClass(self))
        return [NSString stringWithFormat:@"%ld",[self hash]];
    return [NSString stringWithFormat:@"%p",self];
}
#define _LB_FORWARD_SET_SELF_KEY LB_FORWARD_SET_SELF_KEY(self)
#define _LB_FORWARD_SET_SELF     LB_FORWARD_SET[_LB_FORWARD_SET_SELF_KEY]



#pragma mark - sel 相關(guān)信息的key
static inline NSString* LB_FORWARD_SET_SEL_KEY(SEL LB_FORWARD_PARAM_NAME_SEL){
    return NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL);
}
#define _LB_FORWARD_SET_SEL_KEY LB_FORWARD_SET_SEL_KEY(LB_FORWARD_PARAM_NAME_SEL)
#define _LB_FORWARD_SET_SEL     _LB_FORWARD_SET_SELF[_LB_FORWARD_SET_SEL_KEY]


#define _LB_FORWARD_SET_OP_B    _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_B_KEY]
#define _LB_FORWARD_SET_OP_R    _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_R_KEY]
#define _LB_FORWARD_SET_OP_A    _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_A_KEY]



#pragma mark - inline functions
#pragma mark - 快速根據(jù)code創(chuàng)建錯誤信息
static inline LBForwardError* _LB_FORWARD_ERROR(LBFEC code){
    return [LBForwardError lbEWithCode:code];
}




#pragma mark - 快速根據(jù)des創(chuàng)建 其他錯誤信息的error
static inline LBForwardError* _LB_FORWARD_OTHER_E(NSString* des){
    return [LBForwardError lbOEWithDes:des];
}





#pragma mark - 檢查當(dāng)前的對象是否是動態(tài)創(chuàng)建的class
static inline bool _LB_FORWARD_CLASS_IS_DYNAMIC(id self){
    return [NSStringFromClass(object_getClass(self)) hasPrefix:LB_HOOK_CLASS_PREFIX];
}
#define _LB_FORWARD_CLASS_IS_DYNAMIC_VALUE _LB_FORWARD_CLASS_IS_DYNAMIC(self)





#pragma mark - 快速獲取sel的簽名對象(對象方法)
static inline NSMethodSignature* _LB_FORWARD_SEL_ORIGINAL_SIGNA(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
    if (object_isClass(self))
        return [self instanceMethodSignatureForSelector:LB_FORWARD_PARAM_NAME_SEL];

    return [self methodSignatureForSelector:LB_FORWARD_PARAM_NAME_SEL];
}
#define _LB_FORWARD_SEL_ORIGINAL_SIGNA_VALUE _LB_FORWARD_SEL_ORIGINAL_METHOD(self,LB_FORWARD_PARAM_NAME_SEL)





#pragma mark - 快速獲取sel的Method(對象方法)
static inline Method _LB_FORWARD_SEL_ORIGINAL_METHOD(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
    return class_getInstanceMethod(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
}
#define _LB_FORWARD_SEL_ORIGINAL_METHOD_VALUE _LB_FORWARD_SEL_ORIGINAL_METHOD(self,LB_FORWARD_PARAM_NAME_SEL)





#pragma mark - 快速獲取sel的簽名(c字符串)
static inline const char* _LB_FORWARD_SEL_ORIGINAL_ENCODE(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
    return method_getTypeEncoding(_LB_FORWARD_SEL_ORIGINAL_METHOD_VALUE);
}
#define _LB_FORWARD_SEL_ORIGINAL_ENCODE_VALUE _LB_FORWARD_SEL_ORIGINAL_ENCODE(self,LB_FORWARD_PARAM_NAME_SEL)



#pragma mark - 判斷是不是 initXXX的方法
static inline bool _LB_FORWARD_SEL_IS_INITIALIZE(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
    NSString* selString = NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL);
    if (selString.length)
        if ([selString hasPrefix:SYS_SELF_PREFIX_INIT])
            return true;

    return false;
}




#pragma mark - 快速獲取當(dāng)前對象 要動態(tài)被創(chuàng)建的類名
static inline NSString* _LB_FORWARD_DYNAMIC_CLASS_NAME(id self){
    return [LB_HOOK_CLASS_PREFIX stringByAppendingString:NSStringFromClass(object_getClass(self))];
}
#define _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE _LB_FORWARD_DYNAMIC_CLASS_NAME(self)




#pragma mark - 快速獲取當(dāng)前動態(tài)創(chuàng)建的子類 被添加的新的sel的字符串
static inline NSString* _LB_FORWARD_DYNAMIC_SEL_NAME(SEL LB_FORWARD_PARAM_NAME_SEL){
    return [LB_HOOK_SEL_PREFIX stringByAppendingString:NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];
}
#define _LB_FORWARD_DYNAMIC_SEL_NAME_VALUE _LB_FORWARD_DYNAMIC_SEL_NAME(LB_FORWARD_PARAM_NAME_SEL)



#pragma mark - 根據(jù)操作的枚舉值返回對應(yīng)的key
static inline NSString* _LB_FORWARD_SEL_OP_TIME(LBFOT LB_FORWARD_PARAM_NAME_TIME){
    switch (LB_FORWARD_PARAM_NAME_TIME) {
        case LBFOTBefore:return LB_FORWARD_SET_OP_B_KEY;

        case LBFOTReplace:return LB_FORWARD_SET_OP_R_KEY;

        case LBFOTAfter:return LB_FORWARD_SET_OP_A_KEY;

        default:return nil;
    }
}
#define _LB_FORWARD_SEL_OP_TIME_VALUE _LB_FORWARD_SEL_OP_TIME(LB_FORWARD_PARAM_NAME_TIME)





#pragma mark - 獲取sel的imp 這時候self已經(jīng)指向動態(tài)的子類了
static inline IMP _LB_FORWARD_SEL_ORIGINAL_IMP(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
    IMP oImp = class_getMethodImplementation(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
    oImp = !oImp ? nil:class_getMethodImplementation(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
    return oImp;
}
#define _LB_FORWARD_SEL_ORIGINAL_IMP_VALUE _LB_FORWARD_SEL_ORIGINAL_IMP(self,LB_FORWARD_PARAM_NAME_SEL)





#pragma mark - 輔助函數(shù)
#pragma mark - 判斷block是否是合法的簽名
static bool _LB_FORWARD_OP_AVAILABLE_SIGNATURE(id self, SEL LB_FORWARD_PARAM_NAME_SEL, id LB_FORWARD_PARAM_NAME_OP);




static void LB_FORWARD_ENTRANCE(id self, SEL invoSel, NSInvocation* sysInVo);



#pragma mark - 類實(shí)現(xiàn)
@implementation NSObject (LBForward)
- (void)lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
                   originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
                   executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME
                         catch:LB_FORWARD_PARAM_NAME_E_TYPE{

    @synchronized (LB_HOOK_LOCK_KEY) {

        if (LB_FORWARD_PARAM_NAME_E_TYPE)
            return [self _lbf_hookOperationBlock:LB_FORWARD_PARAM_NAME_OP
                                     originalSel:LB_FORWARD_PARAM_NAME_SEL
                                     executeTime:LB_FORWARD_PARAM_NAME_TIME
                                           catch:LB_FORWARD_PARAM_NAME_E];

        [self _lbf_hookOperationBlock:LB_FORWARD_PARAM_NAME_OP
                          originalSel:LB_FORWARD_PARAM_NAME_SEL
                          executeTime:LB_FORWARD_PARAM_NAME_TIME];
    }
}



#pragma mark - 主方法里registerError 不為空的時候的流程
- (void)_lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
                    originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
                    executeTime:(int)LB_FORWARD_PARAM_NAME_TIME
                          catch:LB_FORWARD_PARAM_NAME_E_TYPE{

    [self checkParmsWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
                           originalSel:LB_FORWARD_PARAM_NAME_SEL
                           executeTime:LB_FORWARD_PARAM_NAME_TIME
                                 catch:LB_FORWARD_PARAM_NAME_E];



    ////注冊相關(guān)信息
    [self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
                             originalSel:LB_FORWARD_PARAM_NAME_SEL
                             executeTime:LB_FORWARD_PARAM_NAME_TIME];
}

#pragma mark - 主方法里registerError為空的時候的流程
- (void)_lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
                    originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
                    executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{


    [self checkParmsWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
                           originalSel:LB_FORWARD_PARAM_NAME_SEL
                           executeTime:LB_FORWARD_PARAM_NAME_TIME
                                 catch:nil];



    ////注冊相關(guān)信息
    [self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
                             originalSel:LB_FORWARD_PARAM_NAME_SEL
                             executeTime:LB_FORWARD_PARAM_NAME_TIME];

}

#pragma mark - 參數(shù)相關(guān)檢查
- (void)checkParmsWithOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
                         originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
                         executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME
                               catch:LB_FORWARD_PARAM_NAME_E_TYPE{
    ///空的操作
    if (!LB_FORWARD_PARAM_NAME_OP){
        !LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_ERROR(LBFECEmptyOperation));

        if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_ERROR(LBFECEmptyOperation));
    }






    ///空的sel
    if (!LB_FORWARD_PARAM_NAME_SEL){
        !LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_ERROR(LBFECEmptySEL));

        if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_ERROR(LBFECEmptySEL));
    }






    ///沒有具體的實(shí)現(xiàn)
    if(!_LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL)){
        NSString* errorStr = [NSString stringWithFormat:@"對象:%p(%@) 的%@ 沒有對應(yīng)的實(shí)現(xiàn)",
                              self,object_getClass(self),
                              NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];

        !LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_OTHER_E(errorStr));

        if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_OTHER_E(errorStr));
    }




    ///不能監(jiān)聽的方法
    LBForwardError* fobidMethod = [self fileterSEL:LB_FORWARD_PARAM_NAME_SEL
                                       executeTime:LB_FORWARD_PARAM_NAME_TIME];
    if (fobidMethod){
        !LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(fobidMethod);
        if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(fobidMethod);
    }



    ///簽名不符合
    if(!_LB_FORWARD_OP_AVAILABLE_SIGNATURE(self, LB_FORWARD_PARAM_NAME_SEL, LB_FORWARD_PARAM_NAME_OP)){
        NSString* errorStr = [NSString stringWithFormat:@"對象:%p(%@) 的%@ 簽名不符合, 請閱讀 \"lbf_hookOperationBlock:originalSel:executeTime:catch:\"說明",
                              self,object_getClass(self),
                              NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];

        !LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_OTHER_E(errorStr));
        if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_OTHER_E(errorStr));
    }
}



#pragma mark - 注冊相關(guān)信息
- (void)registerInfoWithOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
                           originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
                           executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
    Class reallyClass = [self isAvaiableClass];

    ///創(chuàng)建子類
    if (!reallyClass)
        [self createSubClassWithSEL:LB_FORWARD_PARAM_NAME_SEL
                        executeTime:LB_FORWARD_PARAM_NAME_TIME];




    ///緩存相關(guān)的信息
    [self cacheOPWithBlock:LB_FORWARD_PARAM_NAME_OP
               originalSel:LB_FORWARD_PARAM_NAME_SEL
               executeTime:LB_FORWARD_PARAM_NAME_TIME];
}


#pragma mark - 當(dāng)前類是否創(chuàng)建過子類
- (Class)isAvaiableClass{
    if (_LB_FORWARD_CLASS_IS_DYNAMIC(self)) return object_getClass(self);
    
    NSString* dynamicClassName = _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE;
    return objc_getClass(dynamicClassName.UTF8String);
}

#pragma mark - 獲取self_info(內(nèi)部會設(shè)置isa)
- (NSMutableDictionary*)cacheSelfSet{
    NSString* key = _LB_FORWARD_SET_SELF_KEY;
    NSMutableDictionary* result = [LB_FORWARD_SET objectForKey:key];
    if (!result) {
        result = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_SELF_CAPACITY];
        [LB_FORWARD_SET setObject:result forKey:key];
    }

    ///這個時候isAvaiableClass一定有值
    if (!_LB_FORWARD_CLASS_IS_DYNAMIC_VALUE)
        object_setClass(self, [self isAvaiableClass]);

    return result;
}

#pragma mark - 獲取sel_info
- (NSMutableDictionary*)cacheSelSetWith:(SEL)LB_FORWARD_PARAM_NAME_SEL
                               superSet:(NSMutableDictionary*)superSet{
    NSString* key = _LB_FORWARD_SET_SEL_KEY;
    NSMutableDictionary* result = [superSet objectForKey:key];
    if (!result) {
        result = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_SEL_CAPACITY];

        [superSet setObject:result forKey:key];
    }
    return result;
}




#pragma mark - 緩存相關(guān)信息
- (void)cacheOPWithBlock:(id)LB_FORWARD_PARAM_NAME_OP
             originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
             executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{


    ///sel_info
    NSMutableDictionary* superSet = [self cacheSelSetWith:LB_FORWARD_PARAM_NAME_SEL
                                                 superSet:[self cacheSelfSet]];

    ///添加方法 交換方法
    [self tryAddMethodWithSEL:LB_FORWARD_PARAM_NAME_SEL
                  executeTime:LB_FORWARD_PARAM_NAME_TIME];



    NSString* key = _LB_FORWARD_SEL_OP_TIME_VALUE;

    if (key) {
        NSMutableArray* result = [superSet objectForKey:key];
        if (!result) {
            result = [NSMutableArray arrayWithCapacity:LB_FORWARD_SET_OP_CAPACITY];
            [superSet setObject:result forKey:key];
        }

        [result addObject:LB_FORWARD_PARAM_NAME_OP];
    }
}













#pragma mark - 過濾方法
- (LBForwardError*)fileterSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
                  executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{


    ///dealloc
    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_DEALLOC)))
        if(LB_FORWARD_PARAM_NAME_TIME != LBFOTBefore)
            return _LB_FORWARD_ERROR(LBFECDellocTime);


    ///arc
    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RT)))
        return _LB_FORWARD_ERROR(LBFECARCForbid);


    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RL)))
        return _LB_FORWARD_ERROR(LBFECARCForbid);


    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RC)))
        return _LB_FORWARD_ERROR(LBFECARCForbid);

    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_AR)))
        return _LB_FORWARD_ERROR(LBFECARCForbid);

    if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_FORWARD)))
        return _LB_FORWARD_ERROR(LBFECARCForbid);


    ///initXXX
    if(_LB_FORWARD_SEL_IS_INITIALIZE(self, LB_FORWARD_PARAM_NAME_SEL))
        return _LB_FORWARD_ERROR(LBFECInitForbid);


    return nil;

}


#pragma mark - 動態(tài)創(chuàng)建類
- (Class)createSubClassWithSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
                   executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
    NSString* dynamicClassName = _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE;

    Class dynamicClass = objc_allocateClassPair(object_getClass(self), dynamicClassName.UTF8String, 0);

    if (dynamicClass) {
        ///將self 的 isa 指向 新建的子類
//        object_setClass(self, dynamicClass);


        ///添加一個 class方法 返回父類
#warning class方法
        objc_registerClassPair(dynamicClass);
    }

    return dynamicClass;
}


#pragma mark - 方法注冊相關(guān)
- (void)tryAddMethodWithSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
                executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{


    Class reallyClass = object_getClass(self);


    SEL subSel = NSSelectorFromString(_LB_FORWARD_DYNAMIC_SEL_NAME_VALUE);


    ///第二次雖然獲取到的是 objc_msgforward的IMP, 但是class_addMethod會添加失敗, 因?yàn)榈?次的時候已經(jīng)正確填充信息了, 所以這里不要糾結(jié)
    IMP selOImp = _LB_FORWARD_SEL_ORIGINAL_IMP_VALUE;

    ///這里的簽名 就是 原始的sel的簽名
    const char* selOEncode = _LB_FORWARD_SEL_ORIGINAL_ENCODE_VALUE;



    if (class_addMethod(reallyClass,
                        subSel,
                        selOImp,
                        selOEncode)) {


        ///將原來的方法 強(qiáng)制指向系統(tǒng)調(diào)用函數(shù), 這樣外界調(diào)用的時候就會進(jìn)入轉(zhuǎn)發(fā)
        class_replaceMethod(reallyClass,
                            LB_FORWARD_PARAM_NAME_SEL,
                            _objc_msgForward,
                            selOEncode
                            );



        ///將當(dāng)前class的 轉(zhuǎn)發(fā)函數(shù)替換到 自定義的, 方便處理
        class_replaceMethod(reallyClass,
                            @selector(forwardInvocation:),
                            (IMP)LB_FORWARD_ENTRANCE,
                            "v@:");

    }
}

#pragma mark - 注銷
- (void)lbf_resign{
    [LB_FORWARD_SET removeObjectForKey:_LB_FORWARD_SET_SELF_KEY];
}

- (void)lbf_resignSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL{
    if (!LB_FORWARD_PARAM_NAME_SEL) return[self lbf_resign];

    NSMutableDictionary* self_info = _LB_FORWARD_SET_SELF;
    [self_info removeObjectForKey:_LB_FORWARD_SET_SEL_KEY];
    if(self_info.allKeys.count == 0) [self lbf_resign];
}


- (void)lbf_resignSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
          executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{

    if (!LB_FORWARD_PARAM_NAME_SEL) return[self lbf_resignSEL:LB_FORWARD_PARAM_NAME_SEL];

    NSMutableDictionary* sel_info = _LB_FORWARD_SET_SEL;
    [sel_info removeObjectForKey:_LB_FORWARD_SEL_OP_TIME_VALUE];
    if (!sel_info.allKeys.count) [self lbf_resignSEL:LB_FORWARD_PARAM_NAME_SEL];
}

@end




static void LB_FORWARD_EXCUTE_OI(id self, NSInvocation* sysInvo, SEL LB_FORWARD_PARAM_NAME_SEL){
    sysInvo.selector = NSSelectorFromString(_LB_FORWARD_DYNAMIC_SEL_NAME_VALUE);
    sysInvo.target = self;
    [sysInvo invoke];
}
#define _LB_FORWARD_EXCUTE_OI() LB_FORWARD_EXCUTE_OI(self,sysInvo,LB_FORWARD_PARAM_NAME_SEL)

static void LB_FORWARD_EXCUTE_OP(NSArray* ops, NSInvocation* sysInvo){

    void* arg = NULL;
    [sysInvo getArgument:&arg atIndex:0];
    [sysInvo setArgument:&arg atIndex:1];

    for (int i = 0; i < ops.count; ++i) {
        sysInvo.target = ops[i];
        [sysInvo invoke];
    }
}



#pragma mark - 全局轉(zhuǎn)發(fā)函數(shù)
__unused void LB_FORWARD_ENTRANCE(id self, SEL invoSel, NSInvocation* sysInvo){
    SEL oSel = sysInvo.selector;


    ///befores block
    LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_B,sysInvo);

    ///這里的問題是 只要有一個替代的操作, 原方法就執(zhí)行不了, Aspeacts 也是同樣的情況
    if ([_LB_FORWARD_SET_OP_R count])
        LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_R, sysInvo);
    else{
        _LB_FORWARD_EXCUTE_OI();
    }


    ///after
    LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_A,sysInvo);
}




bool _LB_FORWARD_OP_AVAILABLE_SIGNATURE(id self, SEL LB_FORWARD_PARAM_NAME_SEL, id LB_FORWARD_PARAM_NAME_OP){
    LBBlockLayout* tmpBlockLayout = (__bridge LBBlockLayout *)(LB_FORWARD_PARAM_NAME_OP);
    int flags = tmpBlockLayout -> flags;

    if (flags & LBBlockFlagsHasSignature) {

        ////這2步都是指針的加減
        void* signatureAddress = (void*)(tmpBlockLayout -> descriptor) + 2 * sizeof(unsigned long);

        if (flags & LBBlockFlagsCopyDispose)
            signatureAddress += 2 * sizeof(void*);



        const char* signalStr = *((char**)signatureAddress);
        if (signalStr) {

            NSMethodSignature* blockS = [NSMethodSignature signatureWithObjCTypes:signalStr];
            NSMethodSignature* selS = _LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL);

            if (!selS) return NO;


            NSInteger tmpBArgCount = blockS.numberOfArguments;

            if (tmpBArgCount > selS.numberOfArguments)
                return NO;

            if (tmpBArgCount > 1)
                if ([blockS getArgumentTypeAtIndex:1][0] != '@') return NO;


            for (NSUInteger i = 2; i < tmpBArgCount; ++i) {
                const char *methodType = [selS getArgumentTypeAtIndex:i];
                const char *blockType = [blockS getArgumentTypeAtIndex:i];

                if (!methodType || !blockType ) return NO;


                if (methodType[0] != blockType[0]) return NO;
            }

            return true;
        }
    }

    return false;
}

流程

? 1. 創(chuàng)建car1對象
? 2. 調(diào)用lbf_hookOperationBlock: originalSel: executeTime: catch:
? ? 2.1 catch一定要傳, 不然異常
? ? 2.2 檢查 各個參數(shù)的是否空, 檢查sel是否實(shí)現(xiàn)和過濾相關(guān)的方法,檢查block的簽名是否和sel的一致
? ? 2.3 注冊相關(guān)信息
? ? ? ? 2.3.1 判斷是否動態(tài)創(chuàng)建過子類, 沒創(chuàng)建就創(chuàng)建子類
? ? ? ? 2.3.2 先從緩存里取相關(guān)的數(shù)據(jù)(block), 沒取到, 就創(chuàng)建, 創(chuàng)建的過程里把 當(dāng)前的selfisa指向了新的類LBF_CLASS_originaclass,然后動態(tài)添加了一個新的SEL === LBF_SEL_originalSel, 并且指向了原方法的IMP,然后將原方法oSel指向了objc_msgForward, 然后將轉(zhuǎn)發(fā)函數(shù)forwardInvocation:指向了全局處理的函數(shù)LB_FORWARD_ENTRANCE, 當(dāng)調(diào)用注冊的方法的時候, 會來到全局處理的函數(shù)里, 在這里面根據(jù)記錄進(jìn)行篩選
? ? 2.4 可以調(diào)用resign相關(guān)的函數(shù)注掉監(jiān)聽

PS:這個簡單的demo,有很多問題, 只是簡單的測試了下, 里面記錄用的是全局的map, 所以要自己處理注銷的問題,如果是用runtime里的關(guān)聯(lián),那么self 被dealloc的時候會自動釋放掉之前關(guān)聯(lián)的內(nèi)存(Aspeacts), 里面沒有實(shí)現(xiàn)類方法的監(jiān)聽, 但是寫這個demo, 基本runtime的 知識點(diǎn)都用到了, 包括block的簽名獲取, 怎么通過方法調(diào)用block等等

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

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

  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,335評論 0 7
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,249評論 0 9
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,989評論 2 3
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,654評論 1 32
  • 人類因?yàn)橛兴季S、行為和判斷能力而判定為人。 機(jī)器因人類多線程思索而制造成為多個單線程唯一功能使用。 人類和機(jī)器的界...
    sshsky閱讀 299評論 0 1

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