我们都知道,通过继承的方式可以很容易的实现方法的扩展,但是有时候我们却不得不选择另外一种方法去实现方法的扩展,那就是在分类里面实现方法的扩展。以前就遇到过这么一个坑需求,就是借助第三方的融云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前的实现代码,从而满足需求。