• OC-消息转发


    当一个对象接受一个消息后 系统会去分发表里面查看是否能接受这个消息 如果不行 则会执行下面其中一个方案 执行了其中一个其他的就不会执行了 都要自己去实现

    //方案一:
    + (BOOL)resolveInstanceMethod:(SEL)sel;
    //方案二:
    - (id)forwardingTargetForSelector:(SEL)aSelector;
    //方案三:
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    方案一

    动态的添加一个方法 替换那个找不到的方法
    但是这个方法是没办法直接调用的 需要用到performSelector
    因为performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验。 然后class_addMethod添加方法是在运行时添加的,你在编译的时候还没有这个本类方法,所以当然不行啦。

    + (BOOL)resolveInstanceMethod:(SEL)sel;

    #import
    可以动态添加函数 实现@dynamic属性

    id autoDictionaryGetter(id self, SEL _cmd);
    void autoDictionarySetter(id self, SEL _cmd, id value);
    
    +(BOOL)resolveInstanceMethod:(SEL)sel //sel是引起异常的那个函数
    {
        NSString *selectorString = NSStringFromSelector(sel);//获取sel方法的名字 转换为string
        
            IMP impSet = class_getMethodImplementation(self, @selector(autoDictionarySetter));
            if ([selectorString hasPrefix:@"set"])//如果名字里面有set 则动态添加autoDictionarySetter
                class_addMethod(self, selector, impSet, "v@:@");//OC的写法添加imp
            else//如果不带则添加autoDictionaryGetter
                class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");//C的方法添加imp
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    获取IMP(implementation)

    指向方法实现的指针 每一个方法都有一个对应的IMP

    OC

    IMP imp = class_getMethodImplementation(self, @selector(funA));
    
    • 1

    C

    直接强行转换 (IMP)funA
    也就是

    class_addMethod([self class], sel, (IMP)funA, "v@:");
    
    • 1

    class_addMethod

    参数一: Class:就是需要一个类类型 比如[self class]
    参数二:传一个方法 这里就是传那个找不到的引起异常的那个方法
    参数三:IMP
    参数四: const char *types:
    ”v@:”意思就是这已是一个void类型的方法,没有参数传入。
    “i@:”就是说这是一个int类型的方法,没有参数传入。
    ”i@😡”就是说这是一个int类型的方法,有一个对象参数传入。
    因为每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,该参数就是用来描述这个方法的返回值、参数的,v 代表返回值为void,@ 表示self,: 表示_cmd。

    方案二(没看 让别的对象去执行这个函数)

    test就是那个找不到的那个函数

    forwardingTargetForSelector//后备接收者

    TestObect.m
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        return [super resolveInstanceMethod:sel];
    }
     
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            return [ForwardTestObect new];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
     
    ForwardTestObect.m
    - (void)test{
        NSLog(@"%@  --- test ---",self);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    方案三

    当对象接收到消息后发现没有实现该消息的方法 则他会先调用
    methodSignatureForSelector//这个函数用来生成方法签名 签名是给forwardInvocation参数NSInvocation调用
    再调用
    -(void)forwardInvocation:(NSInvocation*)anInvocation;//这个方法是属于NSObject类
    然后这个方法会调用
    -(void)doesNotRecognizeSelector:(SEL)aSelector;//用于生产错误信息 表示异常NSInvalidArgumentException发生 无法处理参数选择器对应的消息
    所以我们只需要重定义这个方法就行了
    forwardInvocation需要传入NSInvocation的对象 这个对象存储了目标,选择器,参数等消息转发所需要的全部元素

    NSInvocation

    NSInvocation的头文件 Foundation/NSInvocation.h
    对象中存储了目标,选择器,参数等消息转发所需要的全部元素。

    invokeWithTarget:(id)anObject

    向目标参数对象发送表示接受者的消息 将消息的结果返回给源发送着

    方法签名 method signation

    NSMethodSignature类 对象保存着方法的参数和返回值

    使用方法签名的目的

    为了使运行时系统能够使用传送目的地的对象信息生成NSInvocation实例,所以需要重新定义返回的方法签名对象

    但是受用转送方法处理的消息不能被respondsToSelector:等方法。当需要知道该消息是否位目标对象可处理的消息,必须重新定义respondsToSelector:等方法

    通过doseNotRecognizedSelector禁止子类调用父类

    例子

    @interface B:NSObject
    -(void)funcB;
    @end
    @implementation B
    
    -(void)funcB
    {
        NSLog(@"%s",__func__);
    }
    @end
    
    
    @interface A:NSObject
    @property B* b;//作为A的属性 就是打算调用B的方法
    -(void)funcA;
    @end
    @implementation A
    -(id) init
    {
        self = [super init];
        if(self!=nil)
        {
            _b = [[B alloc]init];
        }
        return self;
    }
    -(void)forwardInvocation:(NSInvocation *)anInvocation
    {
        SEL sel = [anInvocation selector];//获取无法调用的这个消息
        if([_b respondsToSelector:sel])//询问b是否可以调用 如果可以则向目标参数对象发送这个消息 然后将结果返回回来
        {
            NSLog(@"转给b去调用了");
            [anInvocation invokeWithTarget:_b];
        }
        else//如果不能则看父类是否可以
        {
            NSLog(@"b不能调用 转给父类去看看");
            [super forwardInvocation:anInvocation];//这个是不是没有必要写 因为父类有的 子类一定有
        }
    }
    -(void)doesNotRecognizeSelector:(SEL)aSelector
    {
        NSLog(@"A:%s",__func__);
    }
    
    //方法签名重写
    -(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
    {
        if([super respondsToSelector:aSelector])//询问b是否可以调用 如果可以则向目标参数对象发送这个消息 然后将结果返回回来
        {
            return [super methodSignatureForSelector:(SEL)aSelector];
        }
        return [_b methodSignatureForSelector:aSelector];
    }
    @end
    int main()
    {
        id a = [[A alloc]init];
        [a funcB];
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    先查找了自己的分发表 发现没有
    则调用methodSignatureForSelector 通过respondsToSelector:aSelector发现这个消息通过父类递归也没有
    则forwardInvocation 判断_b这个属性是否可以接受这个无法调用的消息 发现可以 则把这个消息通过invokeWithTarget:_b传给_b接受 然后将结果返回 最后调用doesNotRecognizeSelector报一个异常

  • 相关阅读:
    【xv6操作系统】Lab systems calls
    聊天室案例实现保姆级教学
    Linux——权限
    时至今日,Linux会开源,也是一种态度
    k8s无法删除pv,pvc问题
    docker容器日志管理
    济宁市中考报名照片要求及手机拍照采集证件照方法
    【LeetCode】Day95-有效的数独&矩阵置零
    SpringBoot中使用Redis
    【DevOps】Git 图文详解(五):远程仓库
  • 原文地址:https://blog.csdn.net/qq_43535469/article/details/126240612