OC的消息机制深入理解

前言

这篇文章紧接上一篇, runtime理论我想大家都知道的, 上一篇是站在个人理解的角度, 探讨了相关的一些动态原理,和基本实现思路,最后拓展到runtime框架, 而且也简单写了动态添加属性和KVO简单实现的demo,如果有兴趣可以去看深入理解OC的运行时(Runtime)

编码相关

具体可通过编译指令 @encode(type) typeof()

@encode(type) 必须传入类型 不能是具体的变量
typeof() 传入的可以是变量, 可以是类型, 通过typeof获取到类型然后传给@encode,最后会返回对应的 const char*, 这个就是类型的编码, 同样可以获取到block函数的(相当于没有获取到), 这里举出一些例子:

** 先定义一个Log输出的宏(来自RAC宏的运用改编,有时间会专门写一篇宏相关的东西) ,这里设置的最多10个参数, 如果不够,可以自己看着往后加 **


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

使用

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

//// 打印结果
100
1234.319946
97
A
919123

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

///打印结果
0x7ffee56ce8e8
1234.319946
0x7ffee56ce8e8

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

///打印结果:
aaa
bbb
ccc
ssssss
array
123
890


///打印oc对象 如果直接在宏的参数里填写字典,数组, 加上括号,整体括起来
NSArray* tmp = @[@"xxxx",@"bbbb",@{@"array":@123}];
    Log(@"123",
        (@{@"1233":@123}),
        tmp,
        "这个是c的字符串",
        324.12331f
        );

///打印结果:
{
    1233 = 123;
}
(
    xxxx,
    bbbb,
        {
        array = 123;
    }
)
这个是c的字符串
324.123322

各种数字(整型,浮点)编码

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

各种指针的编码 就是在对应的类型前面加上^

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语言里数组的编码

short[]: ^s
int[10]: [10i]
long[5]: [5q]
long long[8]: [9q]
NSInteger[7]: [7q]
unsigned short[]: ^S
....///规律自己寻找

const相关的编码

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]

函数指针和block相关 ?代表着匿名

   ////局部的函数指针和block
    void (*funtest)(void) = NULL;  ^?
    void (^blocktest)(void) = NULL; @?
    int (*funtest1)(int) = NULL; ^?
    int (^blocktest1)(int) = NULL;@?

结构体,联合体相关

///系统的
@encode(typeof(CGRect)) {CGRect={CGPoint=dd}{CGSize=dd}}

///自己定义的
struct tmp{
        int a;
        struct{
            int b;
            NSString* c;
        };
    };
///编码格式结果
{tmp=i{?=i@}}


///联合体
union{
        int a;
        int b;
    }d;
////d的编码是 ?代表匿名 d这里是一个变量
(?=ii) 

union tmpU{
        int a;
        int b;
    }d;
////此时d的编码是
(tmpU=ii) 

oc相关的编码

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

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


////类里方法的编码通过runtime函数获取到method, 然后再获取编码


方法签名

 1. 在NSObject里有一个签名的类NSMethodSignature
 2. 获取NSMethodSignature的途径有
  2.1 通过NSObject的接口

例子如下:


@implementation VC :UIViewController
+ (void)classMT{}
- (void)objcMT{}
@end
/**
-[NSObject methodSignatureForSelector:SEL]这个方法在头文件里声明的是对象方法, 但是以类方法的调用形式去调用也可以, 不知道为什么
*/

///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


总结: 1.如果是获取对象方法的签名, 就用对象去调用methodSignatureForSelector或者用类去调用instanceMethodSignatureForSelector
   2.如果是获取类方法的签名, 就用类去调用methodSignatureForSelector

获取签名对象后, 函数签名代码如下:


- (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 
////对应 IMP的调用参数表
//// 第0个 id  @
//// 第1个SEL 对应 :
//// 第2个开始是参数 上面的是 int 所以是 i
//// 第3个 对应上面的 NSString*  @
//// 返回值要单独获取, 这里是void 所以是V

  2.2 通过runtime的接口

还是上面的那个类定义

Method m = class_getInstanceMethod(ViewController.class, @selector(signatest3:arg2:));
    NSLog(@"%s",method_getTypeEncoding(m));
/// i28@0:8i16@20  这些数字代表什么含义,没懂, 但是去掉数字就和上面对应了,而且最开始的是从返回算的



 3. 自己签名

创建签名对象 NSMethodSignature 的时候,给的签名类型,注意创建签名的时候, 要把返回值也带上

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


消息转发(利用系统提供的接口转发)

 在oc里如果对对象发送消息时, 最后没有找到方法的实现, 系统会自动进入消息转发流程, 而且在oc的层面提供了接口,在这些接口里我们可以转发消息, 这里拿对象方法来说

  1. 进入+(bool)resolveInstanceMethod:(SEL)

这里系统会将之前传入的SEL传递过来,我们知道, SEL是oc接收消息后去寻找方法匹配的标识,和IMP是分开的, 这里把SEL方法的标识传过来, 表示当前类没有与SEL对应的实现IMP, 所以这里就为这个SEL指定一个实现地址, 然后返回YES告诉系统处理了, 系统就不会往下走了

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

- (void)viewDidLoad{
  [super viewDidLoad];

  ////这里瞎调用了一个方法, 写成这样是为了消除警告
  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("已经被改变指向了,来到了这里执行");
        }), "v@:");


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

@end

这一步主要的用途是在本类中动态为sel添加一个Method, 如果这里返回false, 或什么都不管, 就会去下一步. 这里要说明一点, 就算将当前没有实现的noMethodTest的sel 改为forwardTestFun1的sel传给父类,还是会崩溃

  2. 消息转发的第2步:- (id)forwardingTargetForSelector:(SEL)aSelector

这一步要求给系统传一个对象, 要求这个对象有sel的实现, 比如传一个Person对象, 他实现了这sel

 3. 如果2不处理, 即返回了nil,或者没有实现会进入到这里- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector, 这个方法要求对sel提供一个签名对象 eg:

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

这里的签名一定要和传入的sel对应, 这里表示 - (void)funName{ }, v==void, @ == id : == sel 因为 objc_msgsend()的参数是 id, sel, 所以这里要提供@和:

 4. 如果3不实现或者,返回nil,崩溃, 如果返回了签名则会执行: - (void)forwardInvocation:(NSInvocation *)anInvocation, 这个方法会传递一个NSInvocation对象, 签名已经设置好了, 就是上面的一步我们设置的签名, 这个方法要求提供一个对象供anInvocation调用, 而且要求对象必须实现了 同名的sel的实现, 因为这里的anInvocationselector系统赋值的是传过来的sel,所以一定会调用新对象同名的方法,如果想指定新对象别的方法,这里需要更改anInvocationselector属性值,但是对应实现的方法签名必须和当前参数sel的一致。代码如下

@implementation Son :NSObject
- (void)test{
  Log("消息会不会被转发来呢?");
}

- (void)noMethodTest{
  Log("消息一定会转发到这里");
}
@end


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


///这里可以获取到参数信息
NSMethodSignature* si = anInvocation.methodSignature;
    for (NSInteger i = 2; i < si.numberOfArguments; ++i) {
        @autoreleasepool {
            __autoreleasing NSObject* s;
            [anInvocation getArgument:&s atIndex:i];
            NSLog(@"%@",s);
        }
    }
}
///如果不实现noMethodTest, 崩溃, 方法找不到,如果实现了会调用noMethodTest


手动触发消息转发(方法监听AOP编程)

  1. 手动触发消息转发还是和原理一样的, 要实现即使 当前调用的sel有对应的IMP, 也要触发转发流程. 这里要说一点就是, 处理的操作肯定是在 转发流程的其中一步, 因为既然是转发, 一定会走到转发流程的函数,所以我们处理的地方就是在那几步当中(针对当前类来说,后面拓展到通用的时候实现方案又不一样的)

场景1 假设现在的需求是 项目里所有Person对象用到的地方, 只要调用了- [Person runFrom: to:] 都必须在调用前打印 hello world


解决方案选择

方案1
  • 找到工程里所有的Person实例化的对象, 然后查找哪里调用了- [Person runFrom: to:], 在每一个调用前都加上输出语句


方案2
  • 修改Person中对应方法的实现, 在- [Person runFrom: to:]的实现里, 最开始加上输出语句


方案3
  • 修改Person类的实现, 在Person.m文件里加入私有的类比如 _PersonHelloWord 继承自 Person, 修改Person的初始化操作返回 _PersonHelloWord, 然后_PersonHelloWord的实现里重写- [Person runFrom: to:], 加入输出语句, 再调用父类的方法


方案4
  • 不修改Person类, 也不创建子类, 利用runtime的方案, 这里可以为Person添加一个分类, 在分类里写一个新的方法, 然后利用方法交换的技术实现需求, 当然这不算是消息转发,注意即使他妈的输出语句是不需要参数的,但是交互的方法也必须提供


方案5
  • 修改Person, 用消息转发的技术去处理, 具体的介绍在方案4的代码之后


方案6


1,2种我就不说了, 至于第3种,我想没有人会这样搞, 这种方案可能会用在别的情况下, 总之既然是方案, 那就没有好坏之分, 只有适用和不适用之说, 第4种我下面简单给出伪代码, 第5种要重点探讨的, 第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("产品有病啊, 输出一句hello world, 日!");
  [self helloworld:cao to:nima];
}
@end


方案5 的实现方案介绍

0 \color{#ff0000}{首先先不考虑 返回值是结构体或者浮点数的情况, 后面会讲的}

1 要实现消息转发, 在正常sel存在IMP的情况下, 如果不做处理, 是不会进入转发流程的, 所以要做的事情是手动代码控制, 进入转发流程

手动进入转发流程有多种方案, 但原理都是修改sel的IMP(这个runtime提供了接口)
1.1 让当前的sel指向一个 和sel原始IMP签名不一样的函数或block, 因为签名不对, 找不到实现进入转发
1.2 直接让sel指向 系统转发函数 _objc_msgForward, 强制进入转发

2 进入转发流程后, 在第三步进行操作-[NSObject forwardInvocation:invo]

3 -[NSObject forwardInvocation:invo]会将函数签名和参数类型,返回类型, 消息接收者等包装到invo

4 我们要将 当前的sel, 即runFrom:to: 通过 invo 指定一个新的 newTarget对象, 让这个newTarget 去执行剩下的操作

5 newTarget 执行 runFrom:to:, 首先 输出 hello wrold, 然后再想办法回调原来的实现

6 所以 第4步中, 在指定给newTarget的时候, 要将当前对象eg:originalTarget传递给newTarget, 因为假设 newTarget能调用到原来的 runFrom:to:, 但是原始的runFrom:to:里可能通过 originalTarget来获取相关的成员变量等信息, 这就要求了 newTarget在回调原来的方法时要原封不动的通过 [originalTarget runFrom:arg0 to:arg1]的方式去调用,这不需要传递参数, 因为转发给newTargerunFrom:to:时,参数也传递了过来

7 第6步的分析又出现新的问题, 就是 [originalTarget runFrom:arg0 to:arg1] 会直接进入消息转发, 因为上面已经说了, originalTargetrunFrom:to:已经被替换成了_objc_msgForward, 那怎么解决呢

8 第7步解决的方案, 那只有在将 runFrom:to的IMP替换为 _objc_msgForward之前, 就先记录下来, 然后在 4步里创建newTarget的时候, 将IMP赋值给newTarget, 这样newTarget可以直接调用
IMP(newTarget.originalTarget, NSSelectorFromeString(@"runFrom:to:"), arg0,arg1)

9 反思, 上述流程里是静态创建了一个转发对象newTarget, 那么newTarget的类型,即类的设计可以考虑静态xcode的方式去创建, 也可以考虑动态创建, 那既然这里讲的是runtime, 那就用runtime来创建类, 岂不是逼格更高?!!

10 选择方案, 动态创建当前类Person的子类, 然后添加 子类的 runFrom:to:的IMP, 而且动态创建的子类要有扩充的内存, 用来添加一个元素的对象originalTarget, 这里就用到了 runtime的 add_Var的技巧了, 具体流程图如下:(有点丑,但不要纠结)

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 - 每个对象都调换
+ (void)helloRegisterMethod{

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

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

        ///创建子类 顺便给一个var的空间, 用来保存转发前的父类的实例对象, 目的是调用父类的实现的时候, 将这个对象传给IMP, 保证原来的方法中做的事情不会被更改
        Class sub = objc_allocateClassPair(self, subClass.UTF8String, sizeof(void*));

        if (sub) {
            /////这里先获取到IMP的地址, 如果在block内部获取, 不知道为什么获取的是 _objc_msgForward的地址
            IMP org = method_getImplementation(tarM);

            ////添加一个var
            NSString* varName = [NSStringFromClass(self) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
            ///这里解释下第3g 参数, 内存对齐的问题, 他的值与 要添加var的类型有关, 也和cpu的架构有关,如果是指针, 则传log2(sizeof(type*)), 官方文档说的很清楚
            class_addIvar(sub,
                          varName.UTF8String,
                          sizeof(void*),
                          log2(sizeof(void*)),
                          @encode(NSString*));


            ////为子类添加 一个同样的sel的实现, 相当于是覆盖了父类
            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) {
                    ///再调用原始的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 的实现

  • Person里加一个类方法, 最好在Person初始化或者第一次用的的时候调用
  • 在这个类方法中像上一篇实现KVO的原理一样, 只不过这里不是替换setter,这里是替换 runFrom:to:
  • 新的实现里 先输出 hello wrold, 然后调用父类的实现
  • 这里要注意的一点是,怎么调用父类的代码, 这里先插入一段关于super调用的问题

super代表的仅仅是编译器指令, 并不是一个实体的对象, 比如 [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
        };

//参数1 代表消息接收者, 参数2代表superClass的类对象
//编译器创建临时的tmpSuper伪代码如下
struct objc_super* tmpSuper = malloc(sizeof(struct objc_super));
tmpSuper->receiver = self;
tmpSuper->super_class =  class_getSuperclass( object_getClass(self) );


///然后将创建好的 tmpSuper传给 objc_msgSendSuper
objc_msgSendSuper(tmpSuper, callFun, arg0);

///objc_msgSendSuper的内部会根据 tmpSuper的 super_class, 找到父类类对象的地址
/// 然后去方法列表里寻找方法, 找到方法之后eg:findIMP, 就会直接通过IMP调用
findIMP(tmpSuper->receiver, callFun, arg0);

///所以很多面试里问道 [super class], 返回的class是什么类型, 其实就像上面说的过程一样,最后接收消息的对象始终没有变, 所以还是当前的类
///有的文档说objc_msgSendSuper的内部在找到方法的时候, 会再次调用 objc_msgSend(tmpSuper->receiver, callFun, arg0)
///我想应该不是这样, 这样和 [self callFun:arg0] 没有区别了, 因为super表示到父类里找实现
///调用父类里处理的逻辑, 主要是和self覆盖的callFun: 分开来, 所以我认为是找到findIMP后, 直接执行了

free(tmpSuper);

需求实现核心代码

 Class reallyClass = object_getClass(self);

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

    ///创建新的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");
      
         ////自己构建objc_msgSendSuper的第1个结构体
        struct objc_super tmpSuper = {
              obj,
              class_getSuperclass( object_getClass(obj) );
        };
        objc_msgSendSuper(&tmpSuper, NSSelectorFromString(@"runFrom:to:"), value1, value2)

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

上述是为整个类替换掉! 能不能只指定某个对象, 其他对象不影响呢!!, 其实上一篇的KVO实现就是针对某个对象的, 其他对象不受影响


场景2 和上述需求一样, 监听某个方法, 在方法执行之后或之前做事情, 但是不同的是, 场景2要求只监听某些对象, 没有被监听的对象不影响

比如Car这个类, 监听方法 runFrom:to:, 希望在这个方法执行后, 再输出一句 洗车, 但是只是Car的实例 car1会改变动作, 其他的car2, car3. car4等不受影响


对比场景1

  和场景1不一样的地方是, 这里只针对某些对象, 有点像上一篇的KVO(深入理解OC的运行时(Runtime)), 这里也有 几种对应关系:
1VS1 比如 vc1 想监听car1的这个动作, 然后在car1执行完方法后, 做自己的事情
1VS多, 比如view1也想像VC一样监听 car1, 做自己的操作
多VS1 VC想同时监听 car1, car2
.... 总之和kvo的性质一样, 但是这里的实现和kvo有所不同


解决方案选择

利用runtime的进行转发, 用实现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相关的信息字典

        @"sel1字符串":@{    对象1的 sel1 相关信息的字

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

        }.mutable,




        @"sel2字符串":@{    对象1的 sel2 相关信息的字典
            ///和sel1同样的结构
        }.mutable,




        ...


    }.multable,



 @"对象2的hash字符串":@{
    和对象1同样的结构


 }.multable

 }.multable

转发分类 相关宏的头文件


#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,

    /** 回调不能空 */
    LBFEC(EmptyOperation) = TimeBegin_,

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

    /** delloc的监听时间点必须是调用dealloc之前 */
    LBFEC(DellocTime),

    /** 禁止监听alloc */
    LBFEC(AllocForbid),

    /** 禁止监听initXXXX */
    LBFEC(InitForbid),

    /** 禁止监听ARC相关的 内存管理 */
    LBFEC(ARCForbid),

    /** 禁止监听转函数 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 @"回调不能空";

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

        case LBFECDellocTime:return @"回调必须在调用dealloc之前";

        case LBFECAllocForbid:return @"alloc 不能监听";

        case LBFECInitForbid: return @"initXXX 不能监听";

        case LBFECARCForbid: return @"ARC(retain release, autoRelease) 不能监听";

        case LBFECSYSForward:return @"禁止监听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




这个类主要的作用是注册监听方法的时候, 出错的描述

转发的分类头文件

#import "LBForwardType.h"


#define TimeBegin_ 3000

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

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

    /** 调用完之前的方法后 再回调 */
    LBFOT(After)
}LBFOT;

#undef TimeBegin_



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


NS_ASSUME_NONNULL_BEGIN

@interface NSObject (LBForward)

/**
 对self的方法进行监听, 在指定的time做自己的操作

 @param opBlock 你的操作 block, 签名格式要对应sel block的第一个参数 == self, 后面开始是 sel的参数列表, block里没有传sel出来
 @param oSel 原方法的sel
 @param time 操作的时间点
 @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

转发的分类的实现

#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相关的报错"
#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的结构
typedef struct LBBlockLayout LBBlockLayout;


struct LBBlockLayout{
    void *isa;                      ///block指向的类, 这里oc是为了统一, 因为当一个参数是id的时候, 可以传任意的block,而且还可以通过 NSInvocation 指定target, 设置好签名参数调用block的实现

    int flags;                      ///标记
    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相关信息的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 相关信息的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 - 快速根据code创建错误信息
static inline LBForwardError* _LB_FORWARD_ERROR(LBFEC code){
    return [LBForwardError lbEWithCode:code];
}




#pragma mark - 快速根据des创建 其他错误信息的error
static inline LBForwardError* _LB_FORWARD_OTHER_E(NSString* des){
    return [LBForwardError lbOEWithDes:des];
}





#pragma mark - 检查当前的对象是否是动态创建的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 - 快速获取当前对象 要动态被创建的类名
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 - 快速获取当前动态创建的子类 被添加的新的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 - 根据操作的枚举值返回对应的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已经指向动态的子类了
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 - 辅助函数
#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 - 类实现
@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];



    ////注册相关信息
    [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];



    ////注册相关信息
    [self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
                             originalSel:LB_FORWARD_PARAM_NAME_SEL
                             executeTime:LB_FORWARD_PARAM_NAME_TIME];

}

#pragma mark - 参数相关检查
- (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));
    }






    ///没有具体的实现
    if(!_LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL)){
        NSString* errorStr = [NSString stringWithFormat:@"对象:%p(%@) 的%@ 没有对应的实现",
                              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));
    }




    ///不能监听的方法
    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 - 注册相关信息
- (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];

    ///创建子类
    if (!reallyClass)
        [self createSubClassWithSEL:LB_FORWARD_PARAM_NAME_SEL
                        executeTime:LB_FORWARD_PARAM_NAME_TIME];




    ///缓存相关的信息
    [self cacheOPWithBlock:LB_FORWARD_PARAM_NAME_OP
               originalSel:LB_FORWARD_PARAM_NAME_SEL
               executeTime:LB_FORWARD_PARAM_NAME_TIME];
}


#pragma mark - 当前类是否创建过子类
- (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(内部会设置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 - 缓存相关信息
- (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 - 动态创建类
- (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 - 方法注册相关
- (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会添加失败, 因为第1次的时候已经正确填充信息了, 所以这里不要纠结
    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)) {


        ///将原来的方法 强制指向系统调用函数, 这样外界调用的时候就会进入转发
        class_replaceMethod(reallyClass,
                            LB_FORWARD_PARAM_NAME_SEL,
                            _objc_msgForward,
                            selOEncode
                            );



        ///将当前class的 转发函数替换到 自定义的, 方便处理
        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 - 全局转发函数
__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);

    ///这里的问题是 只要有一个替代的操作, 原方法就执行不了, 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. 创建car1对象
  2. 调用lbf_hookOperationBlock: originalSel: executeTime: catch:
    2.1 catch一定要传, 不然异常
    2.2 检查 各个参数的是否空, 检查sel是否实现和过滤相关的方法,检查block的签名是否和sel的一致
    2.3 注册相关信息
        2.3.1 判断是否动态创建过子类, 没创建就创建子类
        2.3.2 先从缓存里取相关的数据(block), 没取到, 就创建, 创建的过程里把 当前的selfisa指向了新的类LBF_CLASS_originaclass,然后动态添加了一个新的SEL === LBF_SEL_originalSel, 并且指向了原方法的IMP,然后将原方法oSel指向了objc_msgForward, 然后将转发函数forwardInvocation:指向了全局处理的函数LB_FORWARD_ENTRANCE, 当调用注册的方法的时候, 会来到全局处理的函数里, 在这里面根据记录进行筛选
    2.4 可以调用resign相关的函数注掉监听

PS:这个简单的demo,有很多问题, 只是简单的测试了下, 里面记录用的是全局的map, 所以要自己处理注销的问题,如果是用runtime里的关联,那么self 被dealloc的时候会自动释放掉之前关联的内存(Aspeacts), 里面没有实现类方法的监听, 但是写这个demo, 基本runtime的 知识点都用到了, 包括block的签名获取, 怎么通过方法调用block等等

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,190评论 0 7
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,134评论 0 9
  • 消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 ...
    lylaut阅读 1,835评论 2 3
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 人类因为有思维、行为和判断能力而判定为人。 机器因人类多线程思索而制造成为多个单线程唯一功能使用。 人类和机器的界...
    sshsky阅读 242评论 0 1