zoukankan      html  css  js  c++  java
  • swizzle method 和消息转发机制的实际使用

     我的工程结构,如图 1-0

             图  1-0

     在看具体实现以前,先捋以下 实现思路。

     ViewController 中有一个-(void)Amethod;A方法。

    -(void)Amethod{
        NSLog(@"Amethod");
    }

     1.swizzle method

    在ViewController 的 -(void)viewDidLoad中调用 Amentohd;运行输出

                                             图 1-1

    创建一个ViewController 的Category,重写+(void)Load;方法(因为这个方法只会在类被加载时,调用一次。至于initialize,它们之间区别,参考 iOS - + initialize 与 +load),在实现-(void)Bmethod;

    +(void)load{
         Class class = [self class];
        Method m3 =class_getInstanceMethod(class, @selector(Amethod));
        Method m4  =class_getInstanceMethod(class, @selector(Bmethod));
        method_exchangeImplementations(m3, m4);
        
    }

    -(void)Bmethod{

        NSLog(@"swizzle method succuess :Bmenthod");

    }

    运行结果如下图1-2,

                            1-2

    这就是一个简单的交换方法的思路,如果你认真的看了上面的 参考文章,就会发现+(void)Load;是在所有类加载的时候执行的, 那么如果在这个方法里,执行过多操作,是会推延APP启动时间的。所以不要在里面做过多的操作。

    由于swizzle method is not atomic,不是线程安全的,所以你可以在dispatce_once 中完成swizzle。

    +(void)load{
                Class class = [self class];
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method m3 =class_getInstanceMethod(class, @selector(Amethod));
            Method m4  =class_getInstanceMethod(class, @selector(Bmethod));
    /*下面代码的主要思路就是 主类有没有实现Amethod, 实现了,就交换Amenthod和Bmenthod,没实现就给她加上Amethod,但IMP和enconding type是Bmethod的。
    在加上Bmenthod,但IMP 和 encoding type是Amethod的。
    */ 
            BOOL success = class_addMethod(class, method_getName(m3), method_getImplementation(m4), method_getTypeEncoding(m4));
            if (success) {
                class_replaceMethod(class, method_getName(m4), method_getImplementation(m3), method_getTypeEncoding(m3));
            }else{
            method_exchangeImplementations(m3, m4);
            }
        });
    }

     以上,就是 简单的swizzle method。你也可以在Bmethod 中再次调用Bmethod。不会出现递归。动动脑子就知道了。

    2.消息转发 : 有三种方法是去补救实现相应方法。也可以做多继承使用。

     我们在ViewController 中声明一个 button ,但是不实现它的绑定方法

        UIButton *pushA = [[UIButton alloc]initWithFrame:CGRectMake(200, 200, 50, 50)];
        [pushA setTitle:@"pusha" forState:(UIControlStateNormal)];
        [pushA addTarget:self action:@selector(pushA:) forControlEvents:(UIControlEventTouchUpInside)];
        [self.view addSubview:pushA];

    在它的分类中,实现下面代码,当一个实例方法没有实现就会调用 +(BOOL)resolveInstanceMethod:(SEL)sel;相对应对还有类方法+(BOOL)resolveClassMethod:(SEL)sel;

    +(BOOL)resolveInstanceMethod:(SEL)sel{
        NSString *selectionString = NSStringFromSelector(sel);
        if ([selectionString isEqualToString:@"pushA:"]) {
            class_addMethod(self, sel, (IMP)pushA, "v@:*");// 在我之前关于YYModel的文中提到 Encoding type
        }
        return [super resolveInstanceMethod:sel];// 这里无论返回yes 还是NO ,如果方法没有实现都会调用第二种补救方式
    }
    //函数的具体实现 也是IMP
    void pushA(id self,SEL sel,UIButton *sender){ NSLog(@"动态添加方法%@",self); AViewController *vc = [AViewController new]; AViewController *sourceVC = self; [sourceVC.navigationController pushViewController:vc animated:YES]; }

      上面是第一种 补救方法。第二种就是-(id)forwardingTargetForSelector:(SEL)aSelector;

    在ViewController 中在[self performSelector:@selector(Cmethod)];但不在Viewcontroller 中实现Cmenthod(关于 -(void)performSelector 还有很多方法,也会稍后添加)

    在分类中,添加转发方法,

    -(id)forwardingTargetForSelector:(SEL)aSelector{
        AViewController * avc = [AViewController new];
        if ([avc respondsToSelector:aSelector]) {
             return avc;
        }
        return [super forwardingTargetForSelector:aSelector];
    }

    在AViewController实现 -(void)Cmenthod;就完成消息转发给另一个对象。

    第三种补救方式是- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;配合- (void)forwardInvocation:(NSInvocation *)anInvocation

    第三种是一个完整的消息转发,但是开销很大。

    在Viewcontroller 中的-(void)viewDidLoad 中调用 

        [self performSelector:@selector(changeBackgroundColor)];

    目的是,改变视图的背景颜色(关于performSelector:系列,我会在单独总结)

    在viewcontroller的分类中实现第三部补救方法,

    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    
    //我现在是给是吧消息 传给 BViewController,并且追加了参数和返回值,修改了选择子
        NSString *selString = NSStringFromSelector(aSelector);
        if ([selString isEqualToString:@"changeBackgroundColor"]) {
            NSMethodSignature *sign = [[BViewController class] instanceMethodSignatureForSelector:@selector(changeBackgroundColor:)];
            sign = [NSMethodSignature signatureWithObjCTypes:"B@:*"];//参考 type enconding
            return sign;
        }
        return nil;
    }

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

        NSString *selString = NSStringFromSelector(anInvocation.selector);

        if ([selString isEqualToString:@"changeBackgroundColor"]) {

            BViewController *vc = [BViewController new];

                    [anInvocation setTarget:vc]; // 设置target

                    [anInvocation setSelector:@selector(changeBackgroundColor:)]; //设置SEL

                    char * color1 = "r1111";

                    [anInvocation setArgument:&color1 atIndex:2]; //设置arg

               bool returnValue = true;

                 [anInvocation setReturnValue:&returnValue]; //设置返回值为 true。(我的思路是 改变方法的返回值, 但实际上没有起到作用)

                 [anInvocation getReturnValue:&returnValue];// 获取返回值

              NSLog(@"Return Value = %d", returnValue);// 此处打印返回值 结果却是为 true

                  [anInvocation retainArguments];//   retain 一遍参数,防止被销毁

                    [anInvocation invoke];// 发送消息,还有一个-(void)invokeWithTarget:(id)target;方法是让taget对象接受消息

        }else{

            [super forwardInvocation:anInvocation];

        }

    }

    
    

    在 BViewController 中实现 该 方法

    -(bool)changeBackgroundColor:(char *)color{
        NSLog(@"改变背景颜色 %s %s ",__func__,color);
        return false;// 需要注意的是这里我设置了返回值是flase,而nsinvocation对象我设置的是true
    }
    

    运行得到 

                            图 1-3

    嗯, 消息成功转发了,但是我想修改返回值为ture,却没有成功。我设置返回值为true后,立即打印 确实是true,而动态调试的结果却是flase;如下图1-4

                            图 1-4

    所以我将 ViewController+swizzle 中的 获取及打印returenValue 放在了, invoke 的后面,然而结果还是如此。如下图1-5

                              图 1-5

    emmm,这种情况,我认为arg 既然需要retain,是不是返回值也需要retain,但是NSInvocation 的文档里没有 retain 方法。。。。我无计可施,带考证。。。。

  • 相关阅读:
    MFC中的菜单(Menu)
    MFC中获取系统无任何操作的时间
    MFC中使用快捷键Accelerator
    Git的安装和使用
    给pdf添加目录
    练习题2
    练习题1
    主键和外键
    数据库知识点
    多表查询
  • 原文地址:https://www.cnblogs.com/DafaRan/p/7878226.html
Copyright © 2011-2022 走看看