zoukankan      html  css  js  c++  java
  • Method Swizzling

    在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还能使用Method Swizzling方法

    原理

    在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

    每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。

    我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,

    我们可以利用 class_replaceMethod 来修改类,

    我们可以利用 method_setImplementation 来直接设置某个方法的IMP,

    归根结底,都是偷换了selector的IMP,如下图所示:

    实践1

    举个例子好了,我想钩一下NSArray的lastObject 方法,只需两个步骤。

    第一步:给NSArray加一个我自己的lastObject

    #import "NSArray+Swizzle.h"  
    @implementation NSArray (Swizzle)  
    - (id)myLastObject  
    {  
    id ret = [self myLastObject];  
    NSLog(@"**********  myLastObject *********** ");  
    return ret;  
    }  
    @end  
    

    乍一看,这不递归了么?别忘记这是我们准备调换IMP的selector,[self myLastObject] 将会执行真的 [self lastObject] 。

    第二步:调换IMP

    #import <objc/runtime.h>  
    #import "NSArray+Swizzle.h"  
    
    
    int main(int argc, char *argv[])  
    {  
    @autoreleasepool {  
          
        Method ori_Method =  class_getInstanceMethod([NSArray class], @selector(lastObject));  
        Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));  
        method_exchangeImplementations(ori_Method, my_Method);  
          
        NSArray *array = @[@"0",@"1",@"2",@"3"];  
        NSString *string = [array lastObject];  
        NSLog(@"TEST RESULT : %@",string);  
          
        return 0;  
    }  
    }	
    

    控制台输出Log:

    2013-07-18 16:26:12.585 Hook[1740:c07] **********  myLastObject ***********   
    2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3
    

    实践2:拦截系统方法

    需求:比如iOS6 升级 iOS7 后需要版本适配,根据不同系统使用不同样式图片(拟物化和扁平化),如何通过不去手动一个个修改每个UIImage的imageNamed:方法就可以实现为该方法中加入版本判断语句?

    步骤:

    1. 为UIImage建一个分类(UIImage+Category)

    2. 在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,比如版本判断

       +(UIImage *)xh_imageNamed:(NSString *)name {
       double version = [[UIDevice currentDevice].systemVersion doubleValue];
       if (version >= 7.0) {
           // 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
           name = [name stringByAppendingString:@"_os7"];
       }
       return [UIImage xh_imageNamed:name];
       }
      
    3. 分类中重写UIImage的load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)

       +(void)load {
       // 获取两个类的类方法
       Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
       Method m2 = class_getClassMethod([UIImage class], @selector(xh_imageNamed:));
       // 开始交换方法实现
       method_exchangeImplementations(m1, m2);
       }
      

    注意:自定义方法中最后一定要再调用一下系统的方法,让其有加载图片的功能,但是由于方法交换,系统的方法名已经变成了我们自定义的方法名(有点绕,就是用我们的名字能调用系统的方法,用系统的名字能调用我们的方法),这就实现了系统方法的拦截!

    利用以上思路,我们还可以给 NSObject 添加分类,统计创建了多少个对象,给控制器添加分类,统计有创建了多少个控制器

  • 相关阅读:
    docker建镜像
    注册路由的简易实现
    docker的小技巧记录(如果使用了更多会继续添加)
    Alembic使用
    SQLAlchemy的常用数据类型
    记录SQLAlchemy的基本使用
    linux创建桌面快捷方式
    vim编辑器命令
    redis发布订阅
    谨慎使用mysql的utf8
  • 原文地址:https://www.cnblogs.com/sunyanyan/p/5377917.html
Copyright © 2011-2022 走看看