当前位置:   article > 正文

iOS-黑魔法Method-Swizzling的原理与使用_ios 黑魔法

ios 黑魔法
NSLog(@"SHOW TIME");

一、Method-Swizzling是个啥

Method-Swizzling实际就是更换方法所对应的实现函数,如下图1-1,更换前调用方法selector1执行的是IMP1函数,更换后调用selector1执行的就变成了IMP2。

                                           图1-1 method-swizzling更换两个selector方法的实现

二、代码演示下流程

2.1 首先创建个继承NSObject的类,.h文件声明两个实例方法test1、test2:

  1. @interface MethodSwizzling : NSObject
  2. -(void)test1;
  3. -(void)test2;
  4. @end

2.2 在.m文件中实现这两个方法,并交换两个方法的实现:

  1. #import "MethodSwizzling.h"
  2. #import <objc/runtime.h>
  3. @implementation MethodSwizzling
  4. + (void)load {
  5. // 获取test1、test2方法
  6. Method method_test1 = class_getInstanceMethod(self, @selector(test1));
  7. Method method_test2 = class_getInstanceMethod(self, @selector(test2));
  8. // 交换两个方法的实现
  9. method_exchangeImplementations(method_test1, method_test2);
  10. }
  11. -(void)test1 {
  12. NSLog(@"test1");
  13. }
  14. -(void)test2 {
  15. NSLog(@"test2");
  16. }
  17. @end

2.3 其他地方调用test1方法:

  1. MethodSwizzling *obj = [[MethodSwizzling alloc] init];
  2. [obj test1];

2.4 输出结果为@“test2”:

2020-08-06 18:30:08.966946+0800 RuntimeTest[28219:4174853] test2

由此可知,在方法交换后,调用test1方法,实际执行的函数是test2。这就是黑魔法Method-Swizzling的作用:更换实际执行的方法函数。

2.5 如果此时在test2方法的输出语句前调用test2[self test2],结果又会是什么呢?

  1. -(void)test2 {
  2. // 实际调用test1函数
  3. [self test2];
  4. NSLog(@"test2");
  5. }

2.6 输出结果为@“test1”、@“test2”:

  1. 2020-08-06 19:23:35.309874+0800 RuntimeTest[29953:4227972] test1
  2. 2020-08-06 19:23:35.310124+0800 RuntimeTest[29953:4227972] test2

小朋友,你是否有很多的问号???

我们来分析一波:
① 当程序考试运行时就会调用+(void)load方法,调用时机就不在这里解释了,在load方法内对两个方法函数进行交换;

② 通过[obj test1]调用test1方法,但此时test1方法的实际操作函数已经在①步骤中被调换到test2方法,所以[obj test1]实际走的是-(void)test2方法;
③ 此时test2方法内部执行[self test2],并不会造成循环,因为此时的test2方法的函数实现已经被调换指向了test1方法,所以会执行-(void)test1方法,输出@"test1"字符;
④ 最后调用test2方法的NSLog,输出@"test2"字符,执行完毕。

三、Method-Swizzling能用来干嘛

3.1 处理Button重复点击

.h文件

  1. #import <UIKit/UIKit.h>
  2. NS_ASSUME_NONNULL_BEGIN
  3. @interface UIButton (QuickClick)
  4. @property (nonatomic,assign) NSTimeInterval delayTime;
  5. @end
  6. NS_ASSUME_NONNULL_END

.m文件

  1. #import "UIButton+QuickClick.h"
  2. #import <objc/runtime.h>
  3. @implementation UIButton (QuickClick)
  4. static const char* delayTime_str = "delayTime_str";
  5. + (void)load
  6. {
  7. static dispatch_once_t onceToken;
  8. dispatch_once(&onceToken, ^{
  9. Method originMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
  10. Method replacedMethod = class_getInstanceMethod(self, @selector(miSendAction:to:forEvent:));
  11. method_exchangeImplementations(originMethod, replacedMethod);
  12. });
  13. }
  14. - (void)miSendAction:(nonnull SEL)action to:(id)target forEvent:(UIEvent *)event
  15. {
  16. if (self.delayTime > 0) {
  17. if (self.userInteractionEnabled) {
  18. [self miSendAction:action to:target forEvent:event];
  19. }
  20. self.userInteractionEnabled = NO;
  21. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  22. (int64_t)(self.delayTime * NSEC_PER_SEC)),
  23. dispatch_get_main_queue(), ^{
  24. self.userInteractionEnabled = YES;
  25. });
  26. }else{
  27. [self miSendAction:action to:target forEvent:event];
  28. }
  29. }
  30. - (NSTimeInterval)delayTime
  31. {
  32. return [objc_getAssociatedObject(self, delayTime_str) doubleValue];
  33. }
  34. - (void)setDelayTime:(NSTimeInterval)delayTime
  35. {
  36. objc_setAssociatedObject(self, delayTime_str, @(delayTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  37. }
  38. @end

3.2 解决往Array或Dictionary中插入nil导致的crash

我们可以用method swizzling修改-[__NSDictionaryM setObject:forKey:] 方法,让它在设值时,先判断是否value为空,为空则不设置。代码如下:

  1. @implementation NSMutableDictionary (Safe)
  2. + (void)load {
  3. Class dictCls = NSClassFromString(@"__NSDictionaryM");
  4. Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
  5. Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(na_setObject:forKey:));
  6. method_exchangeImplementations(originalMethod, swizzledMethod);
  7. }
  8. - (void)na_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
  9. if (!anObject)
  10. return;
  11. [self na_setObject:anObject forKey:aKey];
  12. }
  13. @end
  1. @implementation NSArray (Safe)
  2. + (void)load {
  3. Method originalMethod = class_getClassMethod(self, @selector(arrayWithObjects:count:));
  4. Method swizzledMethod = class_getClassMethod(self, @selector(na_arrayWithObjects:count:));
  5. method_exchangeImplementations(originalMethod, swizzledMethod);
  6. }
  7. + (instancetype)na_arrayWithObjects:(const id [])objects count:(NSUInteger)cnt {
  8. id nObjects[cnt];
  9. int i=0, j=0;
  10. for (; i<cnt && j<cnt; i++) {
  11. if (objects[i]) {
  12. nObjects[j] = objects[i];
  13. j++;
  14. }
  15. }
  16. return [self na_arrayWithObjects:nObjects count:j];
  17. }
  18. @end
  19. @implementation NSMutableArray (Safe)
  20. + (void)load {
  21. Class arrayCls = NSClassFromString(@"__NSArrayM");
  22. Method originalMethod1 = class_getInstanceMethod(arrayCls, @selector(insertObject:atIndex:));
  23. Method swizzledMethod1 = class_getInstanceMethod(arrayCls, @selector(na_insertObject:atIndex:));
  24. method_exchangeImplementations(originalMethod1, swizzledMethod1);
  25. Method originalMethod2 = class_getInstanceMethod(arrayCls, @selector(setObject:atIndex:));
  26. Method swizzledMethod2 = class_getInstanceMethod(arrayCls, @selector(na_setObject:atIndex:));
  27. method_exchangeImplementations(originalMethod2, swizzledMethod2);
  28. }
  29. - (void)na_insertObject:(id)anObject atIndex:(NSUInteger)index {
  30. if (!anObject)
  31. return;
  32. [self na_insertObject:anObject atIndex:index];
  33. }
  34. - (void)na_setObject:(id)anObject atIndex:(NSUInteger)index {
  35. if (!anObject)
  36. return;
  37. [self na_setObject:anObject atIndex:index];
  38. }
  39. @end

 

NSLog(@"END...");

 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号