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

NSLog(@"SHOW TIME");

一、Method-Swizzling是个啥

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

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

二、代码演示下流程

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

@interface MethodSwizzling : NSObject

-(void)test1;
-(void)test2;

@end

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

#import "MethodSwizzling.h"
#import <objc/runtime.h>

@implementation MethodSwizzling

+ (void)load {
    // 获取test1、test2方法
    Method method_test1 = class_getInstanceMethod(self, @selector(test1));
    Method method_test2 = class_getInstanceMethod(self, @selector(test2));
    // 交换两个方法的实现
    method_exchangeImplementations(method_test1, method_test2);
}

-(void)test1 {
    NSLog(@"test1");
}

-(void)test2 {
    NSLog(@"test2");
}

@end

2.3 其他地方调用test1方法:

MethodSwizzling *obj = [[MethodSwizzling alloc] init];
[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],结果又会是什么呢?

-(void)test2 {
    // 实际调用test1函数
    [self test2];
    
    NSLog(@"test2");
}

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

2020-08-06 19:23:35.309874+0800 RuntimeTest[29953:4227972] test1
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文件

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

.m文件

#import "UIButton+QuickClick.h"
#import <objc/runtime.h>
 
@implementation UIButton (QuickClick)
static const char* delayTime_str = "delayTime_str";
 
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originMethod =   class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method replacedMethod = class_getInstanceMethod(self, @selector(miSendAction:to:forEvent:));
        method_exchangeImplementations(originMethod, replacedMethod);
    });
}
 
- (void)miSendAction:(nonnull SEL)action to:(id)target forEvent:(UIEvent *)event
{
    if (self.delayTime > 0) {
        if (self.userInteractionEnabled) {
            [self miSendAction:action to:target forEvent:event];
        }
        self.userInteractionEnabled = NO;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                     (int64_t)(self.delayTime * NSEC_PER_SEC)),
                                     dispatch_get_main_queue(), ^{
                                         self.userInteractionEnabled = YES;
                                     });
    }else{
        [self miSendAction:action to:target forEvent:event];
    }
}
 
- (NSTimeInterval)delayTime
{
    return [objc_getAssociatedObject(self, delayTime_str) doubleValue];
}
 
- (void)setDelayTime:(NSTimeInterval)delayTime
{
    objc_setAssociatedObject(self, delayTime_str, @(delayTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
@end

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

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

@implementation NSMutableDictionary (Safe)

+ (void)load {
    Class dictCls = NSClassFromString(@"__NSDictionaryM");
    Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
    Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(na_setObject:forKey:));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (void)na_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
    if (!anObject)
        return;
    [self na_setObject:anObject forKey:aKey];
}

@end
@implementation NSArray (Safe)

+ (void)load {
    Method originalMethod = class_getClassMethod(self, @selector(arrayWithObjects:count:));
    Method swizzledMethod = class_getClassMethod(self, @selector(na_arrayWithObjects:count:));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

+ (instancetype)na_arrayWithObjects:(const id [])objects count:(NSUInteger)cnt {
    id nObjects[cnt];
    int i=0, j=0;
    for (; i<cnt && j<cnt; i++) {
        if (objects[i]) {
            nObjects[j] = objects[i];
            j++;
        }
    }
    
    return [self na_arrayWithObjects:nObjects count:j];
}
@end

@implementation NSMutableArray (Safe)

+ (void)load {
    Class arrayCls = NSClassFromString(@"__NSArrayM");
    
    Method originalMethod1 = class_getInstanceMethod(arrayCls, @selector(insertObject:atIndex:));
    Method swizzledMethod1 = class_getInstanceMethod(arrayCls, @selector(na_insertObject:atIndex:));
    method_exchangeImplementations(originalMethod1, swizzledMethod1);
    
    Method originalMethod2 = class_getInstanceMethod(arrayCls, @selector(setObject:atIndex:));
    Method swizzledMethod2 = class_getInstanceMethod(arrayCls, @selector(na_setObject:atIndex:));
    method_exchangeImplementations(originalMethod2, swizzledMethod2);
}

- (void)na_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (!anObject)
        return;
    [self na_insertObject:anObject atIndex:index];
}

- (void)na_setObject:(id)anObject atIndex:(NSUInteger)index {
    if (!anObject)
        return;
    [self na_setObject:anObject atIndex:index];
}

@end

 

NSLog(@"END...");

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TableView 无数据 runtime method swizzling 是一种常用的技术手段,用于在 TableView 中没有数据时,自动地替换原有的方法实现来展示自定义的占位图或提示信息。 在 iOS 开发中,当 TableView 没有数据时,通常会显示一张空白的背景或者一些提示文字,告诉用户当前没有任何数据。而使用 runtime method swizzling 技术,我们可以在 TableView 的相关方法中插入自定义的代码,从而实现自动切换显示空白背景或者提示信息。 具体的实现步骤如下: 1. 创建一个自定义的占位图或提示信息视图,以便在没有数据时显示在 TableView 上。 2. 通过 runtime method swizzling 技术,将 TableView 的 reloadData 方法替换为我们自定义的方法实现。 3. 在自定义的方法实现中,判断 TableView 数据源的数量,如果为零,则将自定义的占位图或提示信息视图添加到 TableView 上,并将 TableView 的背景设置为透明。 4. 如果数据源数量不为零,则将 TableView 的背景设置为默认的 TableView 背景,并调用原有的 reloadData 方法来刷新 TableView。 使用 runtime method swizzling 技术来实现 TableView 无数据时的自定义占位图或提示信息的展示可以提高开发效率,减少了代码的重复编写。同时,由于是替换方法的实现,所以不会对原有的代码产生太多影响,维护成本也较小。但是需要注意的是,使用 runtime method swizzling 技术需要谨慎,遵循苹果官方的 API 规范,以免引发一些潜在的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值