轻松学习之三——IMP指针的作用

作者: J_雨 | 来源:发表于2015-07-16 10:21 被阅读5244次

    可能大家一直看到有许多朋友在Runtime相关文章中介绍IMP指针的概念,那么IMP究竟有什么实际作用呢?让我们先从一个函数看起来。

    Method Swizzling

    如果对Runtime有一定了解的话,一定听说过或者用过这个函数:

    void method_exchangeImplementations(Method m1, Method m2)
    

    它通常叫做method swizzling,算是ObjC的"黑魔法"了,作用就是在程序运行期间动态的给两个方法互换实现,比如有这样一种使用场景:


    我们的程序中有许多个ViewController,我想在对项目改动最小的情况下,在当每个Controller执行完ViewDidLoad以后就在控制台把自己的名字打印出来,方便我去做调试或者了解项目结构。


    有许多朋友会这样说,让所有控制器都继承一个BaseController不就可以了吗?我在这里要解释一下这样做的缺点:假如你的项目里有许多Controller的话,你就需要把项目里凡是没有继承自BaseController的每个Controller都做一次修改了,而且随意更改层级结构会发生意想不到的错误。

    其实我们的目的就是重写ViewDidLoad的方法,并在他的方法最后加上几句Log,所以我们需要给UIViewController建立一个category,因为我们知道,如果在Catagory中重写一个方法,就会覆盖它的原有方法实现,但是,这样做以后就没有办法调用系统原有的方法,因为在一个方法里调用自己的方法会是一个死循环。所以我们的解决办法就是,另外写一个方法来和viewDidLoad“交换”,这样外部调用viewDidLoad就会调到新建的这个方法中,同样,我们调用新建的方法就会调用到系统的viewDidLoad中了。

    Screen Shot 2015-07-15 at 11.10.02 AM.png

    IMP指针

    其实,还有一种更加简单的方法可以让我们办到相同的目的,运用IMP指针,IMP就是Implementation的缩写,顾名思义,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP,所以,我们可以直接调用方法的IMP指针,来避免方法调用死循环的问题。

    调用一个IMP的方式和调用普通C函数相同,比如:

    id returnObjc = someIMP(objc,SEL,params...);
    

    不过如果你的项目没有做其他配置的话这样调用编译器是不会通过的,我们来看一下先它的定义:

    if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    else
    typedef id (*IMP)(id, SEL, ...); 
    endif
    

    在默认情况下你的工程是打开这个配置的

    Screen Shot 2015-07-15 at 2.29.56 PM.png

    这种情况下IMP被定义为无参数无返回值的函数。所以你需要到工程里搜索到这个选项并把它关闭。这样的麻烦就是,每次使用,你都需要修改工程配置,所以这里我再介绍另外一种办法:重新定义一个和有参数的IMP指针相同的指针类型,在获取IMP时把它强转为此类型。这样运用IMP指针后,就不需要额外的给ViewController写新的方法:

    Screen Shot 2015-07-16 at 9.56.19 AM.png

    还有一个地方我们需要注意,如果这样直接调用IMP的话就会发生经典的EXC_BAD_ACCESS错误,我们定义的IMP指针是一个有返回值的类型,而其实我们获取的viewDidLoad这个方法是没有返回值的,所以我们需要新定义一个和IMP相同类型的函数指针比如VIMP,把他的返回值定位Void,这样如果你修改的方法有返回值就用IMP,没有返回值就用VIMP。

    Screen Shot 2015-07-15 at 5.49.54 PM.png Screen Shot 2015-07-15 at 3.36.05 PM.png

    值得注意的是,如果你重写的方法有返回值,不要忘记在最后做return。

    总结

    实际上直接调用一个方法的IMP指针的效率是高于调用方法本身的,所以,如果你有一个合适的时机获取到方法的IMP的话,你可以试着调用它。
    这是只是IMP使用的场景之一,它还有许多作用,希望大家多多发现。

    相关文章

      网友评论

      • 碳酸钾:如何是一个带有参数的函数怎么传参呢,如果是多参又返回的又怎么搞呢,还有如何调用IMP指针,其中,第一个参数是什么,第二个参数是什么,可不可以置为空呢,请不吝赐教
        碳酸钾:@碳酸钾 楼主,不要一味地抄袭,如果你也搞不清楚,弄的模棱两个,何必拿出来忽悠人呢,其实,你几时写的再多的博客,这并不能称为你找工作的时候的资本
      • 清蒸鱼跃龙门:我没用VIMP也没遇到EXC_BAD_ACCESS,这是为啥呢?
      • 清蒸鱼跃龙门:按照你的代码打印了一下Log,发现了一个未知的类:UIInputWindowController。。好神奇的存在,你知道这是什么吗
      • xfx_itachi:请教一下imp_implementationWithBlock(id block)这个函数的参数类型是什么呢,我看apple文档是
        * @param block The block that implements this method. Its signature should
        * be: method_return_type ^(id self, method_args...).
        * The selector is not available as a parameter to this block.
        * The block is copied with \c Block_copy().

        所以文章中的“imp_implementationWithBlock(^(id target, SEL action))”是否应该改为“imp_implementationWithBlock(^(id target))”呢



        穿山甲到底说了什么:是的,你的回答是对的,不能加 SEL 参数, 作者可能 笔误
        清蒸鱼跃龙门:而且笔者的block里没用到action,不知道写来干嘛
        leftwater:@xfx_itachi nubility
      • valentizx:感谢分享 楼主还会出一些有关runtime的东西吗?
      • RemisKrlet:初学的人弱弱的问一下,load方法是类加载时调用,只会调用一次,为何里面要使用一次性代码?:smile:
        J_雨:@Tmycc load方法是当这个类本身或者它的分类被加载到runtime以后调用
        RemisKrlet:@J_雨 原因是load在类和分类都会调用还是?我对这个还不太明白。
        J_雨:@Tmycc 因为load不能保证只加载一次
      • RemisKrlet:写的很不错。
      • kevin元:谢谢作者,加深了对imp的理解,感谢
      • b7360a36e397:正在学习OC,遇到一个经典的“牛”类代码案例,其中的IMP指针调用出现问题,一直没找到答案,原来是工程设置问题,重新定义VIMP之后没有报错了,调用也正常了。支持楼主分享更多经验
      • 半岛夏天://Method Swizzling 更详细的写法。mark一下 :yum:
        #import "UIViewController+Tracking.h"
        #import <objc/runtime.h>

        @implementation UIViewController (Tracking)

        + (void)load {
        NSString *className = NSStringFromClass(self.class);
        NSLog(@"classname %@", className);
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
        Class class = [self class];

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
        originalSelector,
        method_getImplementation(swizzledMethod),
        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
        class_replaceMethod(class,
        swizzledSelector,
        method_getImplementation(originalMethod),
        method_getTypeEncoding(originalMethod));
        } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        });
        }


        #pragma mark - Method Swizzling

        - (void)xxx_viewWillAppear:(BOOL)animated {
        NSLog(@"viewWillAppear: %@", self);
        [self xxx_viewWillAppear:animated];
        }
        穿山甲到底说了什么:为什么 在`if (didAddMethod)` false 分支下 还要走交换呢,望,解答谢谢
        ` method_exchangeImplementations(originalMethod, swizzledMethod);`
      • 腊冬:赞

      本文标题:轻松学习之三——IMP指针的作用

      本文链接:https://www.haomeiwen.com/subject/wkpwxttx.html