Objective-C Runtime Method Swizzling 实践

直接上代码

1  交换实例方法:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    //交换的是实例方法
        Class class = [self class];
        SEL originalSelector = @selector(originMethod);//originMethod 已经存在的方法
        SEL swizzleSelector = @selector(swizzleMethod);//swizzleMethod 新添加的方法

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

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

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

    });
}

  

2. 交换类方法

上面的交换的实例方法,交换类方法该怎么写呢?
1)  Class class = [self class]; 换为 Class class = object_getClass((id)self);
注意是object_getClass,不是objc_getClass
2)   上面的class_getInstanceMethod改为class_getClassMethod方法,其他不变,注意Class class = [self class];和class_getInstanceMethod对应;
Class class = object_getClass((id)self)和class_getClassMethod对应

3. 方法交换写完了,不知道你有没有一下疑问:

a 为什么上面的为什么写在+ (void)load ,只能写在load吗?
+ (void)load 这个方法比较特别,这个方法只要启动APP就会调用,不管你启动页面的相关代码里面有没有用到所属的类,都会调用这个方法;这个方法是当类或分类被添加到 Objective-C runtime 时被调用的,重载这个方法可以让我们完成一下初始化的操作,这个方法只会调用一次,在这个方法中千万不要进行耗时的操作,比如文件的读写。笔者当年就出过这个错误,导致APP启动变慢。这个方法是在main()方法执行前就执行了。

另外 +load 方法还有一个和其他方法不同的地方,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。也就是说如果子类没有实现 +load 方法,那么当它被加载时 runtime 是不会去调用父类的 +load 方法的。同理,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。

以上特别决定了Method Swizzling写在+load 方法比较合适。
b 重新父类的load方法,为什么不调用[super load]?
上面已经说了,子类、父类和分类中的 +load 方法的实现是被区别对待的,系统会自动调用分别调用子类和父类中的+load 方法,所以子类一定不能使用[super load]

c Method Swizzling 为什么要加 dispatch_once 
加dispatch_once就是防止多次调用,确保代码只被执行一次,上面都说了+load 方法在类加载的时候会被 runtime 自动调用一次,而且只调用一次,为什么还加dispatch_once?为了防止程序员的手动调用。

d 为什么要调用class_addMethod,直接交换(如下)不就行了吗?

SEL originalSelector = @selector(originMethod);//originMethod 已经存在的方法
SEL swizzleSelector = @selector(swizzleMethod);//swizzleMethod 新添加的方法

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

  

要回到这个问题首首先说一下class_addMethod这个方法的做作用,
class_addMethod就是给类添加新的方法,如果类本身已经存在这个方法,就会添加失败返回NO,这里的类本身的方法,意思是不包括从父类继承过来的方法,什么意思?比如father 类有一个eat方法,son类继承自father类,但是son类没有重写父类(也就是father类)的eat方法,那么使用 eat 类使用class_addMethod添加eat方法,就能添加成功。

那么问题来了,首先我们要使用Method Swizzling,肯定是因为当前类已经存在了这个方法,如果类不存在这个方法直接使用category添加方法就行了,
当前类已经存在了这个方法,有两种情况:
a 对类本身存在方法交换方法,这里的类本身存在方法是指不是从父类继承过来的,这时直接交换即可,如下图:


b 对从父类继承过来的方法直接,交换方法,交换之后如下图:

发现问题了吗?父类方法指向了子类的实现,如果用father 类调用father_A方法就会闪退。
实验


```
Class aClass = [self class];
SEL originalSel = @selector(father_A);
SEL swizzleSel = @selector(Swizz_A);

    Method originalMethod = class_getInstanceMethod(aClass, originalSel);
    Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);

    method_exchangeImplementations(originalMethod, swizzleMethod);

Father *f = [Father new];
[f father_A];

2018-04-05 16:35:19.732717+0800 runtime 之Method Swizzling[65557:2828735] -[Father Swizz_A]: unrecognized selector sent to instance 0x1006348f0
2018-04-05 16:35:19.733565+0800 runtime 之Method Swizzling[65557:2828735] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException‘, reason: ‘-[Father Swizz_A]: unrecognized selector sent to instance 0x1006348f0‘
```

c 如果先把父类的方法,利用class_addMethod添加到子类呢,也就是子类重写父类的方法,添加之后如下:

没看明白是吗?让我们来分析一下添加的代码class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))。
这里一共四个参数,这里的originalSelector就是father_A,method_getImplementation(swizzledMethod)就是Swizz_A的实现。这下明白了吧。

测试

        Class aClass = [self class];
        SEL originalSel = @selector(father_A);
        SEL swizzleSel = @selector(Swizz_A);

        Method originalMethod = class_getInstanceMethod(aClass, originalSel);
        Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);

        BOOL success = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));

        if (success) {
            Method original = class_getInstanceMethod(aClass,originalSel);
            Method swizzle = class_getInstanceMethod(aClass, swizzleSel);
            IMP  originalIML = method_getImplementation(original);

            IMP  swizzleIML = method_getImplementation(swizzle);

可以看到originalIML和swizzleIML指向的是同一个实现

d 添加之后为什么不用method_exchangeImplementations方法呢?
通过上图可以看到由于father_A和Swizz_A指向的是同一个实现,所以交换也没有意义,

e 这里为什么还要调用class_replaceMethod方法呢,好像不调也可以,子类的father_A方法已经指向了Swizz_A,子类调用father_A,相当于调用Swizz_A,也就[son father_A]等同于[son Swizz_A];

在Swizz_A方法调用[super father_A],这个确实可以,就是写法和上面的写法不一样了,上面是
-(void)Swizz_A{
[self father_A];
NSLog(@"son分类里的Swizz_A");
}

-(void)Swizz_A{
[super father_A];
NSLog(@"son分类里的Swizz_A");
}
如果你能确定你要交换的方法是在父类实现的,也可以这么写。

f 调用class_replaceMethod方法,发生了什么呢?如下图;

看到了吧,Swizz_A方法名指向了父类的father_A实现,为什么?
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
这里的swizzledSelector就是Swizz_A,method_getImplementation(originalMethod)就是父类的father_A的实现。

上图不对?让我们实验一下

可以看大指向的是同一个IMP

总结:判断class_addMethod这种写法是比较强壮的写法,如果你确定要交换的方法存在当前类中,可以直接交换。

原文地址:https://www.cnblogs.com/shanyimin/p/8743411.html

时间: 2024-10-10 01:18:30

Objective-C Runtime Method Swizzling 实践的相关文章

Objective-C的hook方案(一): Method Swizzling

原文地址:http://blog.csdn.net/yiyaaixuexi/article/details/9374411 Objective-C的hook方案(一):  Method Swizzling 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写.和借助类别重名方法暴力抢先之外,还有更加灵活的方法吗?在Objective-C编程中,如何实现hook呢?标题有点大,计划分几篇来总结. 本文主要介绍针对selector的hook,主角被标题剧透了———— Method

iOS runtime探究(四): 从runtiem开始实践Category添加属性与黑魔法method swizzling

你要知道的runtime都在这里 转载请注明出处 http://blog.csdn.net/u014205968/article/details/67639335 本文主要讲解runtime相关知识,从原理到实践,由于包含内容过多分为以下五篇文章详细讲解,可自行选择需要了解的方向: 从runtime开始: 理解面向对象的类到面向过程的结构体 从runtime开始: 深入理解OC消息转发机制 从runtime开始: 理解OC的属性property 从runtime开始: 实践Category添加属

理解Objective-C Runtime(四)Method Swizzling

Objective-C对象收到消息之后,究竟会调用何种方法需要在运行期间才能解析出来.那你也许会问:与给定的选择子名称相应的方法是不是也可以在runtime改变呢?没错,就是这样.若能善用此特性,则可发挥出巨大优势,因为我们既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能.这样一来,新功能将在本类的所有实例中生效,而不仅限于覆写了相关方法的那些子类实例.此方案就是大名鼎鼎的「method swizzling」,中文常称之为『方法调配』或『方法调和』或『方法混合』. Meth

Objective-C Runtime 运行时之四:Method Swizzling

理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的

Objective-C Runtime 运行时之四:Method Swizzling(转载)

理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的

runtime 第四部分method swizzling

接上一篇 http://www.cnblogs.com/ddavidXu/p/5924597.html 转载来源http://www.jianshu.com/p/6b905584f536 http://southpeak.github.io/2014/10/30/objective-c-runtime-2/ runtime的黑魔法,就是可以实现交换两个方法的实现,这就意味着我们可以修改系统的方法实现. 栗子:当UIViewController及其子类的对象调用viewWillAppear时,都会

ObjC Runtime 黑魔法 — Method Swizzling

适用情境 项目中大量控制器需要在载入时进行日志统计或进行类似的处理.如果直接往所有控制器中进行代码编写,会产生大量的重复的代码,降低了代码后期的可读性,不利于维护.由于所有部分的逻辑代码相同,针对这种情况,以切面编程(AOP)思想为导向,利用 Method Swizzling 能极大降低这种(日志统计)非主要逻辑代码与控制器的耦合度. AOP概念详解(摘自百度百科) 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理

Method Swizzling 和 AOP 实践(转)

上一篇介绍了 Objective-C Messaging.利用 Objective-C 的 Runtime 特性,我们可以给语言做扩展,帮助解决项目开发中的一些设计和技术问题.这一篇,我们来探索一些利用 Objective-C Runtime 的黑色技巧.这些技巧中最具争议的或许就是 Method Swizzling . 介绍一个技巧,最好的方式就是提出具体的需求,然后用它跟其他的解决方法做比较. 所以,先来看看我们的需求:对 App 的用户行为进行追踪和分析.简单说,就是当用户看到某个 Vie

ios method swizzling

阅读器 iOS开发iOS 本文由TracyYih[博客]翻译自NSHipster的文章Method Swizzling. 在上周associated objects一文中,我们开始探索Objective-C运行时的一些黑魔法.本周我们继续前行,来讨论可能是最受争议的运行时技术:method swizzling. Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch tab