iOS安全–Objective-C Method Swizzling

Object-c 运行时允许你修改selector(method name)到implementation(the method code itself)的映射,在不修改 iOS 系统类库或第三方类库的源码基础上,修改原有调用逻辑。

也就是说替换两个方法的实现,相当于Hook。

下面先看一个例子:

比如我们想替换NSObject中的lastObject方法,先定义替换方法:

1
2
3
4
5
- (id)xxx_lastObject{
    id ret = [self xxx_lastObject];
    NSLog(@"************* xxx_lastObject *******");
    return ret;
}

然后进行如下替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void class_swizzleMethod(Class c, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(c, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(c, swizzledSelector);
    BOOL didAddMethod = class_addMethod(c,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    if(didAddMethod){
        class_replaceMethod(c,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

再尝试调用lastObject方法:

1
2
3
4
NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
   
    NSString *lastObject = [array lastObject];
    NSLog(@"lastObject reslut:%@",lastObject);

输出结果:

Snip20150121_1

原理:

OC的方法是一种叫Method的结构体,这种objc_method类型的结构体定义为:

1
2
3
4
5
struct objc_method
   SEL method_name        
   Method method_types    
   IMP method_imp        
}

1.选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。

2.方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。

3.实现(typedef id (*IMP)(id, SEL, …)):这种数据类型是实现某个方法的函数开始位置的指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向元类(metaclass)的指针)。第二个参数是方法的选择器,后面跟的都是参数。

理解这些概念之间关系最好的方式是:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entry)都是一个方法(Method),其中key值是一个唯一的名字——选择器(SEL),它对应到一个实现(IMP)——实际上就是指向标准C函数的指针。

而我们上面的操作其实就是交换了两个函数的method_imp,也就是说调用lastObject的时候其实会去调用xxx_lastObject的实现函数。

应该注意的地方:

1.+load vs. +initialize
Swizzling应该在+load方法中实现。
每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。

因为method swizzling会影响全局,所以减少冒险情况就很重要。+load能够保证在类初始化的时候就会被加载,这为改变系统行为提供了一些统一性。但+initialize并不能保证在什么时候被调用——事实上也有可能永远也不会被调用,例如应用程序从未直接的给该类发送消息。

2.dispatch_once
Swizzling应该在dispatch_once中实现。

还是因为swizzling会改变全局,我们需要在运行时采取所有可用的防范措施。保障原子性就是一个措施,它确保代码即使在多线程环境下也只会被执行一次。GCD中的diapatch_once就提供这些保障,它应该被当做swizzling的标准实践。

3.避免冲突

给分类方法加前缀,一定要确保不要让你代码库中其他代码(或是依赖库)在做与你相同的事。

其它的大家可以参考:http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c

 

 

本文链接:http://www.alonemonkey.com/ioss-method-swizzling.html

4条评论

    1. AloneMonkey

      也有说明,主要看代码实现把。、

    1. AloneMonkey

      欢迎一起交流~

Comments are closed.