iOS Method swizzling与ISA swizzling实现与区别

1. 什么是Method swizzling与ISA swizzling

  • Method swizzling:指在运行时替换两个方法的实现(IMP),例如swizzling(Method A, Method B),交换后则变为Method A —> B的IMP,而Method B —> A的IMP;常用于APP统计事件无痕埋点,修改系统API实现等。
  • ISA swizzling:对象的isa指针定义了它的类,所以ISA swizzling指修改对象所指向的类。KVO则是使用该技术实现的,还有Zombie objects检测也用到了该技术

2. 如何实现Method swizzling

要实现方法混写,需要用到运行时API:void method_exchangeImplementations(Method m1, Method m2),具体实现如下。

//定义设置Error宏
#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...)    \
    if (ERROR_VAR) {    \
        NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
        *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
                                         code:-1    \
                                     userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
    }

#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)

#pragma mark - Swizzle

@implementation NSObject (NE_Swizzle)

//方法混写
+ (BOOL)ne_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel error:(NSError**)error
{
    Method origMethod = class_getInstanceMethod(self, origSel);
    if (!origMethod) {
        SetNSError(error, @"original method %@ not found for class %@", NSStringFromSelector(origSel), [self class]);
        return NO;
    }
    
    Method altMethod = class_getInstanceMethod(self, altSel);
    if (!altMethod) {
        SetNSError(error, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel), [self class]);
        return  NO;
    }
    
    class_addMethod(self,
                    origSel,
                    class_getMethodImplementation(self, origSel),
                    method_getTypeEncoding(origMethod));
    class_addMethod(self,
                    altSel,
                    class_getMethodImplementation(self, altSel),
                    method_getTypeEncoding(altMethod));
    
    method_exchangeImplementations(class_getInstanceMethod(self, origSel), class_getInstanceMethod(self, altSel));
    
    return YES;
}

//Class实质上也是一个对象
+ (BOOL)ne_swizzleClassMethod:(SEL)origSel withClassMethod:(SEL)altSel error:(NSError**)error
{
    return [object_getClass((id)self) ne_swizzleMethod:origSel withMethod:altSel error:error];
}

3. 如何实现ISA swizzling

  • 要实现方法混写,需要用到运行时API:Class object_setClass(id obj, Class cls) 具体实现如下。

#pragma mark - Swizzle

@implementation NSObject (NE_Swizzle)

- (BOOL)ne_setClass:(Class)altClass error:(NSError**)error
{
    if (class_getInstanceSize([self class]) == class_getInstanceSize(altClass)) {
        object_setClass(self, altClass);
        return YES;
    } else {
        SetNSError(error, @"classes must be same size to swizzle. original: %@ alternate: %@" , NSStringFromClass(altClass), NSStringFromClass([self class]));
        return NO;
    }
}

@end

  • 重要:被混写的两个类的大小一定要相等,也就是说对象即将指向的新Class中声明任何的ivar或者合成属性。因为被混写对象的内存是已经分配好的,如果添加ivar,那它们就会指向已分配内存外的区域,那样很容易覆盖内存中这个对象后面对象的isa指针,导致EXC_BAD_ACCESS或者SIGABRT。例子如下:
@interface People : NSObject

@property (nonatomic, assign) NSUInteger age;

@end

@implementation People

@end

@interface Student : People

@property (nonatomic, strong) NSString *name;

@end

@implementation Student

@end

// main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        
    People *p1 = [People new];
        
    object_setClass(p1, [Student class]);
        
    p1.age = 16;
        
    People *p2 = [People new];
        
    p2.age  = 18;
        
    return YES;
    }
}

  • 如果Student存在name属性,由于混写的类的大小不一致,所以代码运行则发生崩溃。因此必须去除name属性。

4. 二者有什么区别

  • Method swizzling

    1. 影响一个类的所有对象(实例)
    2. 对象都指向的同一个类
    3. 调用API:void method_exchangeImplementations(Method m1, Method m2)实现混写
  • ISA swizzling

    1. 只会影响到当前目标对象
    2. 对象的类会发生变化
    3. 使用子类技术混写,即必须保证混写类的大小相等,调用API:Class object_setClass(id obj, Class cls)实现

你可能感兴趣的:(iOS Method swizzling与ISA swizzling实现与区别)