iOS黑魔法-Method Swizzling

iOS黑魔法-Method Swizzling
  Objective-C Runtime  运行时之四:Method Swizzling

(详情: http://blog.jobbole.com/79580/

 Method Swizzling是改变一个selector的实际实现的技术。通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

使用原因:

1.例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个viewcontrollerviewDidAppear中添加跟踪代码;但是这太过麻烦,需要在每个view controller中写重复的代码。创建一个子类可能是一种实现方式,但需要同时创建UIViewController, UITableViewController, UINavigationController及其它UIKitview controller的子类,这同样会产生许多重复的代码。

2.iOS系统方法总是有可能因为越界而让程序崩溃(crash),我们这时可以考虑自己写方法替换系统的方法了。

刨根问底ObjectiveCRuntimehttp://www.cocoachina.com/ios/20141224/10740.html

 MethodSwizzling

我们可以使用苹果的黑魔法Method SwizzlingMethod Swizzling本质上就是对IMPSEL进行交换。

MethodSwizzling原理

Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。

而且Method Swizzling也是iOSAOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。

首先,张图了解一下Method Swizzling的实原理

图片

                                                 (一)

  

图片

                        (图二)

     上面图一中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们在图二中添加了selector3IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了方法互换

OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。

在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SELIMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP



@implementationNSArray(Extension)

+ (void)load {

    static dispatch_once_tonceToken;

    dispatch_once(&onceToken,^{

    [self swizzleInstanceMethod:objc_getClass("__NSArrayI")originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];

    });

}

 /**

 *  用来替换取出数组的元素的方法

 *

 * @param index 数组下标

 *

 * @return 返回取出的值,如果越界返回nil

 */

- (instancetype)safe_objectAtIndex:(NSUInteger)index {

    if (self.count - 1< index) {

        @try {

            return [self safe_objectAtIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n",class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return nil;

        }

        @finally {

        }

    } else {

        return [self safe_objectAtIndex:index];

    }

}

 

@end

 

看到上面的代码,肯定有人会问: 你太粗心了,你在safe_objectAtIndex方法中又调用了[selfsafe_objectAtIndex:index];

;,这难道不会产生递归调用吗?
答:然而....并不会。

还记得我们上面的图一和图二吗?Method Swizzling的实现原理可以理解为方法互换。假设我们将AB两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。

例如我们上面的代码,系统调用NSArrayobjectAtIndex方法时,实际上执行的是我们实现的safe_objectAtIndex方法。而我们在safe_objectAtIndex方法内部调用[selfsafe_objectAtIndex:index];时,执行的是NSArrayobjectAtIndex方法。

 

MethodSwizzling类簇

之前我也说到,在我们项目开发过程中,经常因为NSArray数组越界或者NSDictionarykey或者value值为nil等问题导致的崩溃,对于这些问题苹果并不会报一个警告,而是直接崩溃,感觉苹果这样确实有点太狠了

由此,我们可以根据上面所学,对NSArrayNSMutableArrayNSDictionaryNSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是....你发现MethodSwizzling根本就不起作用,代码也没写错啊,到底是什么鬼?

这是因为Method SwizzlingNSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArrayobjectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。

所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其真身进行操作。

大家发现了吗,我的代码例子中,__NSArrayI才是NSArray真正的类,而NSMutableArray又不一样。我们可以通过runtime函数获取真正的类:

objc_getClass("__NSArrayI")

 

NSLog(@"%@", [@"ygf" class]);

    NSLog(@"%@", [[NSMutableString stringWithFormat:@"ygf"class]);

    NSLog(@"%@", [[NSArray arrayWithObjects:@"ygf"nilclass]);

    NSLog(@"%@", [[NSMutableArray arrayWithObjects:@"ygf"nilclass]);

    NSLog(@"%@", [[NSDictionary dictionaryWithObjectsAndKeys:@"ygf"@"name"nil]class]);

NSLog(@"%@", [[NSMutableDictionary dictionaryWithObjectsAndKeys:@"ygf",@"name"nilclass]);

 

2016-04-06 18:07:49.380 YGFSwizzling[6600:226036]__NSCFConstantString

2016-04-06 18:07:49.380 YGFSwizzling[6600:226036]__NSCFString

2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSArrayI

2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSArrayM

2016-04-06 18:07:49.381 YGFSwizzling[6600:226036]__NSDictionaryI

2016-04-06 18:07:49.381 YGFSwizzling[6600:226036] __NSDictionaryM

 

例子:

//

// UIViewController+Extension.m

//  YGFSwizzling

//

//  Created byguangfu yang on 16/4/6.

//  Copyright ©2016 yangguangfu. All rights reserved.

//

 

#import <objc/runtime.h>

#import "UIViewController+Extension.h"

 

@implementation NSObject(Extension)

/**

 *  交换两个对象方法

 *

 * @param class          类名称

 * @param originSelector 原始方法名称

 * @param otherSelector  交换的方法名称

 */

+ (void)swizzleInstanceMethod:(Class)classoriginSelector:(SEL)originSelectorotherSelector:(SEL)otherSelector{

    Method otherMehtod = class_getInstanceMethod(class, otherSelector);

    Method originMehtod = class_getInstanceMethod(class, originSelector);

    // 交换2个方法的实现

    if (class_addMethod(class, originSelector, method_getImplementation(otherMehtod), method_getTypeEncoding(originMehtod))) {

        class_replaceMethod(class, otherSelector, method_getImplementation(originMehtod), method_getTypeEncoding(originMehtod));

    } else {

        method_exchangeImplementations(otherMehtod, originMehtod);

    }

}

 

@end

 

@implementation UIViewController(Extension)

 

+ (void)load {

    [self swizzleInstanceMethod:self originSelector:@selector(viewWillAppear:) otherSelector:@selector(mr_viewWillAppear:)];

}

 

/**

 *  用来替换viewDidAppear,方便追踪

 *

 * @param animated 是否有动画

 */

- (void)mr_viewWillAppear:(BOOL)animated {

    [self mr_viewWillAppear:YES];

    NSLog(@"我是好人%@",self);

}

 

 

@end

 

@implementation NSArray(Extension)

 

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

    [self swizzleInstanceMethod:objc_getClass("__NSArrayI"originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];

    });

}

 

/**

 *  用来替换取出数组的元素的方法

 *

 * @param index 数组下标

 *

 * @return 返回取出的值,如果越界返回nil

 */

- (instancetype)safe_objectAtIndex:(NSUInteger)index {

    if (self.count - 1< index) {

        @try {

            return [self safe_objectAtIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return nil;

        }

        @finally {

        }

    } else {

        return [self safe_objectAtIndex:index];

    }

   

}

 

@end

 

@implementation NSMutableArray (Extension)

 

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        [self swizzleInstanceMethod:objc_getClass("__NSArrayM"originSelector:@selector(objectAtIndex:)otherSelector:@selector(safe_objectAtIndex:)];

        [self swizzleInstanceMethod:objc_getClass("__NSArrayM"originSelector:@selector(removeObjectAtIndex:)otherSelector:@selector(safe_removeObjectsAtIndex:)];

        [self swizzleInstanceMethod:objc_getClass("__NSArrayM"originSelector:@selector(removeObject:inRange:)otherSelector:@selector(safe_removeObjectsInRange:)];

    });

}

 

- (instancetype)safe_objectAtIndex:(NSUInteger)index {

    if (self.count - 1< index) {

        @try {

            return [self safe_objectAtIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return nil;

        }

        @finally {

        }

    } else {

        return [self safe_objectAtIndex:index];

    }

}

 

- (void)safe_removeObjectsAtIndex:(NSUInteger)index {

    if (self.count - 1< index) {

        @try {

            return [self safe_removeObjectsAtIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return ;

        }

        @finally {

        }

    } else {

        return [self safe_removeObjectsAtIndex:index];

    }

}

 

- (void)safe_removeObjectsInRange:(NSRange)range {

    NSUInteger end = range.location + range.length - 1;

    if (end > self.count - 1) {

        @try {

            [self safe_removeObjectsInRange:range];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

        }

        @finally {

        }

    } else {

        [self safe_removeObjectsInRange:range];

    }

}

 

@end

 

@implementation NSString (Extension)

 

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        [self swizzleInstanceMethod:objc_getClass("__NSCFConstantString"originSelector:@selector(substringToIndex:)otherSelector:@selector(safe_substringToIndex:)];

    });

}

 

- (instancetype)safe_substringToIndex:(NSUInteger)index {

    if (self.length - 1< index) {

        @try {

            return [self safe_substringToIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return nil;

        }

        @finally {

        }

    } else {

        return [self safe_substringToIndex:index];

    }

}

 

@end

 

@implementation NSMutableString (Extension)

 

+ (void)load {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        [self swizzleInstanceMethod:objc_getClass("__NSCFString"originSelector:@selector(substringToIndex:)otherSelector:@selector(safe_mutable_substringToIndex:)];

    });

}

 

- (instancetype)safe_mutable_substringToIndex:(NSUInteger)index {

    if (self.length - 1< index) {

        @try {

            return [self safe_mutable_substringToIndex:index];

        }

        @catch (NSException *exception) {

            NSLog(@"---------- %sCrash Because Method %s ----------\n"class_getName(self.class), __func__);

            NSLog(@"%@", [exception callStackSymbols]);

            return nil;

        }

        @finally {

        }

    } else {

        return [self safe_mutable_substringToIndex:index];

    }

}

 

@end

 

 

 

注意事项

Swizzling通常被称作是一种黑魔法,容易产生不可预知的行为和无法预见的后果。虽然它不是最安全的,但如果遵从以下几点预防措施的话,还是比较安全的:

1 总是调用方法的原始实现(除非有更好的理由不这么做)API提供了一个输入与输出约定,但其内部实现是一个黑盒。Swizzle一个方法而不调用原始实现可能会打破私有状态底层操作,从而影响到程序的其它部分。

2 避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。

3 明白是怎么回事:简单地拷贝粘贴swizzle代码而不理解它是如何工作的,不仅危险,而且会浪费学习Objective-C运行时的机会。阅读Objective-C RuntimeReference和查看<objc/runtime.h>头文件以了解事件是如何发生的。

小心操作:无论我们对Foundation, UIKit或其它内建框架执行Swizzle操作抱有多大信心,需要知道在下一版本中许多事可能会不一样。

 

Method Swizzling危险

既然MethodSwizzling可以对这个类的Dispatch Table进行操作,操作后的结果对所有当前类及子类都会产生影响,所以有人认为Method Swizzling是一种危险的技术,用不好很容易导致一些不可预见的bug,这些bug一般都是非常难发现和调试的。
这个问题可以引用念茜大神的一句话:使用Method Swizzling 编程就好比切菜时使用锋利的刀,一些人因为担心切到自己所以害怕锋利的刀具,可是事实上,使用钝刀往往更容易出事,而利刀更为安全

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值