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 ) ;

输出结果:

原理:

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

 

你可能感兴趣的:(iOS)