通过swizzle method分类已有方法扩展

这篇博客介绍了如何利用Method Swizzling技术在Objective-C中为已有类创建分类并扩展方法。首先创建一个继承自NSObject的类,然后通过运行时库导入实现方法交换。接着以融云的RCIM类为例,创建分类并在.h和.m文件中定义方法。通过KCRuntimeSwizzle进行方法的交换,使得在调用原有方法时实际上执行了新的hook方法,实现了方法扩展的目的。博客提到了两篇参考资源,分别是关于Category方法扩展的讨论和Method Swizzling的源码示例。
摘要由CSDN通过智能技术生成
    我们都知道,通过继承的方式可以很容易的实现方法的扩展,但是有时候我们却不得不选择另外一种方法去实现方法的扩展,那就是在分类里面实现方法的扩展。以前就遇到过这么一个坑需求,就是借助第三方的融云sdk实现一个即时通讯的功能。在某个群里,对于vip用户而言只能向管理员发送私聊消息,而管理员既可以发送群聊消息和私聊消息。用户方的消息展示方式是管理员发送的群聊消息和对该用户在该群中发送的私聊消息以及用户在该群中向管理员寻求vip服务的私聊消息须展示在一起。这时候就出现了问题。当用户方app后台处于active状态时,通知栏的显示对于私聊我们需要做特殊处理,需要群id获取群相关信息,然后格式化通知栏显示格式。然后融云sdk代理方法中无法获取到群id。这时候我们就需要通过分类来实现方法的扩展。当然实现过程中需要用到关联(associate实现很简单,大家自行百度)。那么如何实现分类的扩展呢?这就需要我们对oc的运行时特性有一定的了解。

1、创建一个继承自NSObject的类
申明一个类方法如下:

+ (void)swizzle:(Class)c
        origSel:(SEL)origSel
         newSel:(SEL)newSel;

该类方法的实现方法如下:

+ (void)swizzle:(Class)c
        origSel:(SEL)origSel
         newSel:(SEL)newSel {
    Method origMethod = class_getInstanceMethod(c, origSel);
    Method newMethod = class_getInstanceMethod(c, newSel);
    if (class_addMethod(c, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
        class_replaceMethod(c, newSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        method_exchangeImplementations(origMethod, newMethod);
    }
}

在实现文件里面导入运行时库,如下

#import <objc/runtime.h>

2、创建分类,实现已有方法的扩展
融云的RCIM类为例。.h文件的内容如下:

#import <RongIMKit/RongIMKit.h>

@interface RCIM (Swizzle)

+ (void)prepare;

@end

.m文件的内容如下:

#import "RCIM+Swizzle.h"
#import "KCRuntimeSwizzle.h"
#import <objc/runtime.h>

@interface RCIM ()

- (void)onReceived:(RCMessage *)message
              left:(int)nLeft
            object:(id)object;

@end

@implementation RCIM (Swizzle)

+ (void)prepare {
    [KCRuntimeSwizzle swizzle:[RCIM class]
                      origSel:@selector(onReceived:left:object:)
                       newSel:@selector(swizzle_onReceived:left:object:)];
}

- (void)swizzle_onReceived:(RCMessage *)message left:(int)left object:(id)object {
    if (self.userInfoDataSource) {
        objc_setAssociatedObject(self.userInfoDataSource, @"JR_RCIMMessage", message, OBJC_ASSOCIATION_RETAIN);
        objc_setAssociatedObject(self.userInfoDataSource, @"JR_RCIMAppVersion", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], OBJC_ASSOCIATION_RETAIN);
    }
    [KCRuntimeSwizzle swizzle:[RCIM class]
                      origSel:@selector(onReceived:left:object:)
                       newSel:@selector(swizzle_onReceived:left:object:)];
    [self onReceived:message left:left object:object];
    [KCRuntimeSwizzle swizzle:[RCIM class]
                      origSel:@selector(onReceived:left:object:)
                       newSel:@selector(swizzle_onReceived:left:object:)];
}
  • (void)swizzle_onReceived:(RCMessage *)message left:(int)left object:(id)object方法里面第一次调用
    [KCRuntimeSwizzle swizzle:[RCIM class]
    origSel:@selector(onReceived:left:object:)
    newSel:@selector(swizzle_onReceived:left:object:)];是将IMP指针指向原来的实现,这时候我们可以开始调用原来的实现[self onReceived:message left:left object:object]; 第二次调用[KCRuntimeSwizzle swizzle:[RCIM class]
    origSel:@selector(onReceived:left:object:)
    newSel:@selector(swizzle_onReceived:left:object:)];;是让IMP指针会到hook方法的实现文件。

当然,.m文件我们也可以这样写:

#import "RCIM+Swizzle.h"
#import "KCRuntimeSwizzle.h"
#import <objc/runtime.h>

@interface RCIM ()

- (void)onReceived:(RCMessage *)message
              left:(int)nLeft
            object:(id)object;

@end

@implementation RCIM (Swizzle)

+ (void)prepare {
    [KCRuntimeSwizzle swizzle:[RCIM class]
                      origSel:@selector(onReceived:left:object:)
                       newSel:@selector(swizzle_onReceived:left:object:)];
}

- (void)swizzle_onReceived:(RCMessage *)message left:(int)left object:(id)object {
    if (self.userInfoDataSource) {
        objc_setAssociatedObject(self.userInfoDataSource, @"JR_RCIMMessage", message, OBJC_ASSOCIATION_RETAIN);
        objc_setAssociatedObject(self.userInfoDataSource, @"JR_RCIMAppVersion", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], OBJC_ASSOCIATION_RETAIN);
    }
    [self swizzle_onReceived:message left:left object:object];
}

@end

那么,[self swizzle_onReceived:message left:left object:object];这行代码看起来可能有点奇怪,像递归不是么。当然不会是递归,因为在 runtime 的时候,函数实现已经被交换了。其实,这行代码是调用hook前的实现代码,从而满足需求。

参考
Extending Methods in a Category by Method Swizzling

源码
1、KCRumtimeSwizzle
2、UINavigationControllerBackNotify

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值