咱们这里不会通过源码介绍Runtime,已经有很多文章介绍了,而且太晦涩,读起来不舒服,也不会介绍Runtime的一些基本原理,这个作为iOS开发最熟悉了,只是通过一些我们平时用到的操作,来宏观的介绍NSInvocation
和NSMethodSignature
,随便聊聊,做一些简单的记录,还记得刚接触这个的时候咱们脑海里面的问号吗?
什么是方法,什么是选择器,什么是方法签名,什么是IMP,什么是消息?下面简单的回顾下
选择器是方法的名称。你肯定对以下选择器非常熟悉:alloc
,init
,release
,dictionaryWithObjectsAndKeys:
,setObject:forKey:
等,而且冒号是选择器的一部分。这就是我们确定此方法需要参数的方式。不过你也可以不带参数名,但是这样做不推荐doFoo :::
。这是一个带有三个参数的方法,可以像[someObject doFoo:arg1:arg2:arg3]
一样调用它。不需要在选择器的每个部分之前都包含字母。Cocoa框架下
。
它们具有SEL
类型:SEL aSelector = @selector(doSomething :)
或SEL aSelector = NSSelectorFromString(@“ doSomething:”)
方法签名叫起来比较专业,其实他就是一个记录方法返回值和参数的数据类型罢了。 他们可以在运行时用NSMethodSignature
和C的char *
来表示
消息就是上面的提到的选择器加上你要随着选择器发送的参数。比如[dict setObject:obj forKey:key]
,这个消息就是选择器 SEL aSelector = @selector(setObject: forKey:)
,加上参数obj和key。可以将消息封装在NSInvocation
中,这两点就是提到的SEL
和arguments
,选择器 + 参数列表,后续还有Target
和return value
进一步介绍,这里还涉及到方签名。
struct objc_method {
SEL _Nonnull method_name //方法名
char * _Nullable method_types //方法签名
IMP _Nonnull method_imp // 方法实现
}
方法是选择器(SEL
)和实现(IMP
)的组合。IMP
其实就是一个函数指针。我个人理解,这里的method_types
就是我们下面要提到的SEL
对应的方法签名
方法实际的可执行代码。他在运行时用IMP
表示,实际上就是一个函数指针。
A record of the type information for the return value and parameters of a method.
一个对于方法返回值和参数的记录。 也可以叫做一个对于方法的签名
介绍NSInvocation
之前,咱们先来了解下这个类NSMethodSignature
,根据上面文档的介绍,主要是记录了方法的返回值和参数。咱们先来看看生成一个NSMethodSignature
所需要的步骤
根据头文件提供的两个方法,一个+方法一个-方法
NSMethodSignature *sign1 = [@"" methodSignatureForSelector:@selector(initWithFormat:)];
NSMethodSignature *sign2 = [NSClassFromString(@"NSString") instanceMethodSignatureForSelector:@selector(initWithFormat:)];
NSLog(@"%@---%@",sign1,sign2);
打印结果如下:
(lldb) po sign1
<NSMethodSignature: 0x600001fb42a0>
number of arguments = 3
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 0: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (:) ':'
flags {}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 2: -------- -------- -------- --------
type encoding (@) '@'
flags {isObject}
modifiers {}
frame {offset = 16, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
2020-05-18 17:15:32.545730+0800 YTKNetworkDemo[14057:11030644] <NSMethodSignature: 0x600001fb42a0>---<NSMethodSignature: 0x600001fb42a0>
这里可以看出,同一个方法,无论哪种方式拿到的方法签名对象都是一样的,而且这里还有个小知识点po
和直接NSLog
打印的时候,为什么不同呢?这是因为NSObject
提供了两个协议方法
@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;
description
专门是用来为log服务的,而debugDescription
就体现在lldb
上面的调试指令。
除了这个不同,我们还从打印的日志中看到了type encoding (@) '@'
,先看下如下代码
Method m = class_getInstanceMethod(NSString.class, @selector(initWithFormat:));
const char *c = method_getTypeEncoding(m);
打印@24@0:8@16
,你肯定会有下面的疑惑
1.这里的
@
符号代表什么?
2.这里的数字代表什么
3.SEL
+Arguments
的消息原型和
Class_selector(id self ,SEL _cmd,...) objc_msgSend
的原型(prototype
)void objc_msgSend(id self,SEL cmd,...)
有什么关联,为什么长的差不多?
为了辅助运行时系统,编译器对字符串中每个方法的返回和参数类型进行编码,并将字符串与方法选择器相关联
OC类型编码表
例如NSString
的类方法isEqualToString:
的方法签名为B24@0:8@16
@encode(BOOL) (B)
返回值
@encode(id) (@)
默认第一个参数 self
@encode(SEL) (:)
默认第二个参数 _cmd
@encode(NSString *) (@)
实际上的第一个参数NSString
那么下面的打印就很容易理解了
'NSString'|'initWithFormat:locale:arguments:' of encoding '@40@0:8@16@24[1{__va_list_tag=II^v^v}]32'
'NSString'|'initWithCoder:' of encoding '@24@0:8@16'
'NSString'|'initWithString:' of encoding '@24@0:8@16'
方法签名包含一个或多个用于方法返回类型的字符,后跟隐式参数self和_cmd的字符串编码,后跟零个或多个显式参数。
[返回值][target][action][参数]
1.各种符号就是参数类型的字符串编码,方便与SEL
关联,而且OC方法默认带了self
和_cmd
这两个参数,所以这也是为什么能直接在方法中用这两个”关键字”的原因,所以配合上面的编码表 B(返回值)24@(self)0:(_cmd)8@(第一个参数NSString)
2.根据上面的offset描述,可以揣测出大概的意思是类基地址的偏移,比如上面的@24
代表返回值,一般在最后,其中@0
代表基地址偏移0,指针变量8个字节,然后:8
代表_cmd
,再然后@16
代表对应的参数,这里的参数是字符串,因此就只有8个字节,如果你用NSRange
可以试试,这里就会扩充出16个字节,里面存了两个unsign long long
类型
3.根据上面的疑惑点,发送消息是转换成void objc_msgSend(id self,SEL cmd,...)
,会根据接受者和SEL
选择器来调用适当的方法。那么一旦找到对应的方法实现之后,会直接跳转过去,之所以能这样是因为Objective-C对象的每个方法都可以视为简单的C函数,其原型如下
。每个类中都有一张表格,其中的指针都会指向这种函数,而选择器对应的名称就是查表的Key,SEL可以简单理解为字符串 + 签名。其中这里原型的样子和objc_msgSend
很像,这显然不是巧合,这是为了利用尾调用优化(tail-call optimization),另方法跳转变得更加简单。如果函数的最后是调用另一个函数,那么久可以利用尾调用优化技术。编译器会生成调转另一个函数所需的指令码,而且不会向调用堆栈中推入新的栈帧。只有当函数的最后一个操作仅仅是调用其他函数而不会将其返回值另做他用,才可以执行尾调用优化。别小看这个优化,如果不这么做,那么每次调用OC的方法,都要为objc_msgSend函数准备栈帧,若不优化,很容易发生Stack OverFlow
根据上面的介绍,NSMethodSignature
本质上就是对方法返回值和参数的签名。那么下面根据Runtime的消息转发,来动态给类添加一个方法。
@interface MKJAutoDictionary : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) id obj;
@end
@interface MKJAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *storeDict;
@end
@implementation MKJAutoDictionary
@dynamic name, obj;
- (instancetype)init
{
self = [super init];
if (self) {
_storeDict = [[NSMutableDictionary alloc] init];
}
return self;
}
就这样声明了一个类,目的是调用属性的方法时,自动存储到字典里面。这里我们给新增的两个属性name
和obj
设置为@dynamic
,这个关键字就不介绍了,我们的另一个置顶博客有介绍。那么当外面调用时
extern void instrumentObjcMessageSends(BOOL);
instrumentObjcMessageSends(YES);
MKJAutoDictionary *atd = [[MKJAutoDictionary alloc] init];
[atd performSelector:@selector(setName:) withObject:@"123456"];
instrumentObjcMessageSends(NO);
这里肯定会蹦,由于我们给了dynamic
关键字,那么这里有一个多余的函数,主要我是用来证明是否进入消息转发。
这里有个传送门介绍方法
根据介绍我们可以在private/tmp
目录下找到msgSends-xxxxx
的一个日志文件,打开就能在里面看到整个消息调用过程。
...
- MKJAutoDictionary NSObject performSelector:withObject:
+ MKJAutoDictionary MKJAutoDictionary resolveInstanceMethod:
+ MKJAutoDictionary MKJAutoDictionary resolveInstanceMethod:
- MKJAutoDictionary NSObject forwardingTargetForSelector:
- MKJAutoDictionary NSObject forwardingTargetForSelector:
- MKJAutoDictionary NSObject methodSignatureForSelector:
- MKJAutoDictionary NSObject methodSignatureForSelector:
- MKJAutoDictionary NSObject class
- MKJAutoDictionary NSObject doesNotRecognizeSelector:
- MKJAutoDictionary NSObject doesNotRecognizeSelector:
- MKJAutoDictionary NSObject class
...
上面只是一个简单的小插曲,告诉大家有个方法能打印整个调用过程。
下面我们通过Runtime的消息转发,动态给类添加一个方法,来理解下Method
结构体以及上面提到的签名是如何使用的
void autoDictSetter(id self, SEL _cmd, id value){
MKJAutoDictionary *dict = (MKJAutoDictionary *)self;
[dict.storeDict setValue:value forKey:@"123"];
}
id autoDictGetter(id self, SEL _cmd){
MKJAutoDictionary *dict = (MKJAutoDictionary *)self;
return [dict.storeDict valueForKey:@"123"];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selName = NSStringFromSelector(sel);
if ([selName containsString:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictSetter, "v@:@");
}else{
class_addMethod(self, sel, (IMP)autoDictGetter, "@@:");
}
return YES;
}
这里我把功能简化了,只是演示一下如何动态给类添加方法,首先进入resolveInstanceMethod
,然后根据需要调用class_addMethod
方法,回顾下Method结构体,由SEL,IMP和签名组成,那么当我们动态添加的时候,根据参数就能看出class_addMethod第一个参数self
就是Target,第二个参数就是SEL,第三个就是IMP,第四个就是我们所说的方法签名,还记得这句话吗? 他们可以在运行时用NSMethodSignature
和C的char *
来表示
An Objective-C message rendered as an object.
把消息呈现为对象形式。可以存储消息的所有配置和直接调用给任意对象,这就是万物皆对象的一种实践了。
这个东西就是苹果工程师提供的一个高层消息转发系统。他是一个命令对象,可以给任意OC对象发送消息,那么与之类似的还有一个performSelector
,这里咱们介绍NSInvocation
,相比前者有他的短板
performSelector
最多接收两个参数,如果参数多余两个 ,就需要组装成字典类型了Int Double NSInteger
为参数的方法使用时会导致一些诡异的问题使用这个类大致可以总结为如下几个步骤:
Selector
来初始化方法签名对象 NSMethodSignature
NSInvocation
对象,必须使用 invocationWithMethodSignature:
方法Target
和Selector
NSInvocation
对象的invoke
方法NSInvocation
的getReturnValue
来获取返回值,注意该方法仅仅就是把返回数据拷贝到提供的内存缓存区,并不会考虑这里的内存管理- (void)viewDidLoad {
[super viewDidLoad];
......
NSString *name1 = @"Kimi猫";
NSString *category1 = @"波斯猫";
SEL targetSel = @selector(getCatAction:category:);
NSMethodSignature *signature1 = [self methodSignatureForSelector:targetSel];
NSInvocation *invocation1 = [NSInvocation invocationWithMethodSignature:signature1];
[invocation1 setTarget:self];
[invocation1 setSelector:targetSel];
[invocation1 setArgument:&name1 atIndex:2];
[invocation1 setArgument:&category1 atIndex:3];
// 越界崩溃
// [invocation1 setArgument:&category1 atIndex:4];
[invocation1 invoke];
// Error Code
// Cat *cat1 = nil;
// [invocation getReturnValue:&cat1];
// NSLog(@"%@",cat1);
// Plan A
Cat *__unsafe_unretained cat1 = nil;
[invocation1 getReturnValue:&cat1];
Cat *finalCat = cat1;
// Plan B
// void *cat1 = NULL;
// [invocation getReturnValue:&cat1];
// Cat *finalCat = (__bridge Cat *)cat1;
NSLog(@"Get Cat Instance Description---%@",finalCat);
......
}
- (Cat *)getCatAction:(NSString *)name category:(NSString *)category{
NSLog(@"%@--%@",self,NSStringFromSelector(_cmd));
Cat *t = [[Cat alloc] init];
t.name = name;
t.category = category;
return t;
}
https://developer.apple.com/documentation/foundation/nsinvocation/1437834-setargument?language=objc
这个Demo可以很好的反映出用法以及一些涉及到的坑。首先用法步骤最基本就是如此,这里有几个需要注意的点。
1.setArgument
的下标是从2开始的,0存储的是self
,1存储的是_cmd
,而且参数需要传入一个变量的指针或者内存地址,方便内部直接把数据写入内存,而且signature1
对象中有实际参数数量,如果超过数量就会越界崩溃
2.还是setArgument:atIndex:
认不会强引用它的 argument,如果 argument 在 NSInvocation 执行的时候之前被释放就会造成野指针异常(EXC_BAD_ACCESS)
,必要时加上[invocation retainArguments];
即可
3.getReturnValue
参数也是传入变量的指针地址,这里就有个很关键的东西,看上面的Error Code
注释那一坨,想当然,我们会这么写,但是这样就崩溃了,而且报错的是Bad Acess.....
,这个作为一个iOS开发菜鸟,可以断定,显然是内存泄漏了。但是怎么看都没看到泄漏呀,找到StackOverFlow的介绍
意思是该方法,不管类型是是, 它只负责把返回的数据复制到给定的内存缓冲区内。很显然,它不关心内存管理。如果返回的是被对象指针类型引用,比如上面的Cat *cat1
默认就是__strong
类型的,ARC下,编译器会接收内存管理的操作,因此,编译器认为它已经retain
一次了,然后再后面补了一个release
操作,在超出范围的时候释放掉,但是由于上面的是直接把返回值写入内存,我才不管你什么内存管理,你编译器自己自作多情认为__strong
修饰我已经在自己生产的setter
方法retain
一次了,很明显不正确,所以这次加的release
直接放内存泄漏了。 该段是个人理解,如果理解上有问题,欢迎在评论区指出
那么咱们列举了两个解决方案PlanA
和 PlanB
,原理都是一样的,就是getReturnValue
是简单的内存赋值,不会有任何内存管理,那么我们也给这个对象修饰成让编译器认为不需要retain
的样子,比如__unsafe_unretained
,__weak
,void *
,之后再用其他变量来引用赋值即可。
这里建议用PlanB模式,因为getReturnValue
本来就是给内存缓存区写入数据,缓存区声明为void *
更为合理,然后通过__bridge
的方式转换为OC对象类型把内存管理交给ARC,因此就有了下面的通用方案
@implementation MKJInvocationManager
+ (BOOL)invokeTarget:(id)target
action:(SEL)selector
arguments:(NSArray *)arguments
returnValue:(void* _Nullable)result{
if (target && [target respondsToSelector:selector]) {
NSMethodSignature *sig = [target methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:target];
[invocation setSelector:selector];
for (NSUInteger i = 0; i<[arguments count]; i++) {
if (i >= (sig.numberOfArguments - 2)) {
break;
}
NSUInteger argIndex = i+2;
id argument = arguments[i];
if ([argument isKindOfClass:NSNumber.class]) {
//convert number object to basic num type if needs
BOOL shouldContinue = NO;
NSNumber *num = (NSNumber*)argument;
const char *type = [sig getArgumentTypeAtIndex:argIndex];
if (strcmp(type, @encode(BOOL)) == 0) {
BOOL rawNum = [num boolValue];
[invocation setArgument:&rawNum atIndex:argIndex];
shouldContinue = YES;
}
/....此处省略NSNumber其他类型的判断.../
if (shouldContinue) {
continue;
}
}
if ([argument isKindOfClass:[NSNull class]]) {
argument = nil;
}
[invocation setArgument:&argument atIndex:argIndex];
}
[invocation invoke];
NSString *methodReturnType = [NSString stringWithUTF8String:sig.methodReturnType];
if (result && ![methodReturnType isEqualToString:@"v"]) { //if return type is not void
if([methodReturnType isEqualToString:@"@"]) { //if it's kind of NSObject
// 初始化一个 const void * 类型
CFTypeRef cfResult = nil;
// 获取值
[invocation getReturnValue:&cfResult];
if (cfResult) {
// 手动 retain一次
CFRetain(cfResult);
// 手动retain 才能在这里不崩,如果没有retain,那么 __bridge_transfer 会先执行release
// “被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放 cfReslut 所以上面需要手动retain一次
id transferObj = (__bridge_transfer id)cfResult;
*(void **)result = (__bridge_retained void *)transferObj;
// const void * Assigning to 'void *' from 'CFTypeRef' (aka 'const void *') discards qualifiers
// *(void**)result = cfResult;
}
} else {
[invocation getReturnValue:result];
}
}
return YES;
}
return NO;
}
@end
这段代码就是通用类型了的处理了,首先处理了参数越界问题,又可以处理NSNumber
类型的判断,而且我们返回值接收的类型的是void *
类型,如果不好理解可以把它理解为我们经常使用的NSError * __autoreleasing *
错误的捕获。
看下核心[invocation invoke];
之后的获取返回值的操作,区分两种类型,如果是普通数据类型,直接调用[invocation getReturnValue:result];
即可,但是如果是对象类型,返回值不再是v
的签名而是@
。里面我们用到的类型是CFTypeRef
进行取值,拿到的虽然是void *
类型,甚至是const
修饰的,正常情况直接*(void**)result = cfResult;
就行了,但是类型有const修饰,会有警告,就有了上面的桥接转换。我们要把cfResult
转换成id
类型,用到了__bridge_transfer
,改修饰符我上面也加了注释,是把C转换成OC对象,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放,会手动释放一起,因此我们就需要在之前先执行CFRetain(cfResult)
,避免内存泄漏。后面再把id
类型转换成void *
即可。
下面演示一下
定义一个类,直接通过我们写的NSInvocation
的方式调用
@interface Person : NSObject
- (instancetype)initWithVooVVideoManager:(void(^)(void))playBlock;
- (void)instanceSayHello;
@end
@implementation Person
- (instancetype)initWithVooVVideoManager:(void (^)(void))playBlock{
self = [super init];
if (self) {
NSLog(@"Video初始化%@--%@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));
}
return self;
}
- (void)instanceSayHello{
NSLog(@"Hello instance JX VV I am Back %@--%@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));
}
@end
// 在另一个类中触发
void(^block)(void) = ^(void){
NSLog(@"我丢");
};
id obj = nil;
[MKJInvocationManager invokeTarget:[NSClassFromString(@"Person") alloc] action:@selector(initWithVooVVideoManager:) arguments:@[block] returnValue:&obj];
[MKJInvocationManager invokeTarget:obj action:@selector(instanceSayHello) arguments:nil returnValue:nil];
// 2020-05-19 17:28:45.896318+0800 YTKNetworkDemo[62992:1222263] Video初始化Person--initWithVooVVideoManager:
// 2020-05-19 17:28:45.896409+0800 YTKNetworkDemo[62992:1222263] Hello instance JX VV I am Back Person--instanceSayHello
可以看到,NSInvocation
通过签名初始化后,我们只要组合出这四个参数,就可以中转所有的方法了
1、target
2、selector
3、arguments
4、return value
先回顾下Runtime的消息转发步骤。咱们姑且认为有三次机会处理未被找到的消息。
1.resolveInstanceMethod:与resolveClassMethod:
该方法的实现我们再上面讲NSMethodSignature
的时候通过class_addMethod
介绍了
2.forwardingTargetForSelector
这个更没有什么讲的,就是指定一个新的Target转发,只能转给一个对象
3.forwardInvocation: 和 methodSignatureForSelector:
这个就厉害了,支持将消息转发给任意多个对象,所以多继承也只能采用forwardInvocation:
的方式,由于咱们正好在讲NSInvocation
,就顺便带一个Demo来看看,多继承这种能做,但没必要,毕竟不常用。
模拟不再找不到消息而崩溃
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:nil];
}
在这里,调用invoke
的时候,是可以传nil的,毕竟给nil传递任何消息都会返回nil。
模拟下另一个手动触发消息转发,我们知道如果method
的imp
被指向__objc_msgForward
,消息将直接进入转发模式。下面我们假定上面的Person
对象方法reloadData
已经被检测到崩溃了,因此我们需要手动替换调,一种方法是通过MethodSwizzle
,咱们这里用class_replace
来模拟下
@interface Person : NSObject
- (instancetype)initWithVooVVideoManager:(void(^)(void))playBlock;
- (void)instanceSayHello;
- (void)reloadData;
@end
@implementation Person
- (instancetype)initWithVooVVideoManager:(void (^)(void))playBlock{
self = [super init];
if (self) {
NSLog(@"Video初始化%@--%@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));
}
return self;
}
- (void)instanceSayHello{
NSLog(@"Hello instance JX VV I am Back %@--%@",NSStringFromClass(self.class),NSStringFromSelector(_cmd));
}
- (void)reloadData{
NSLog(@"Person ReloadData");
}
@end
void customForward(id self, SEL _cmd, NSInvocation *invo){
if (invo.selector == @selector(instanceSayHello)) {
NSLog(@"instanceSayHello 被完全转发覆盖");
}else if (invo.selector == @selector(reloadData)){
NSLog(@"reloadData 被完全转发覆盖");
}
}
- (void)manualForwaringMesage{
class_replaceMethod(NSClassFromString(@"Person"), @selector(instanceSayHello), _objc_msgForward, "v@:");
class_replaceMethod(NSClassFromString(@"Person"), @selector(reloadData), _objc_msgForward, "v@:");
class_replaceMethod(NSClassFromString(@"Person"), @selector(forwardInvocation:), (IMP)customForward, "v@:@");
// [MKJInvocationManager invokeTarget:[NSClassFromString(@"Person") alloc] action:@selector(instanceSayHello) arguments:nil returnValue:nil];
Person *p = [Person new];
[p instanceSayHello];
[p reloadData];
}
// 2020-05-19 18:11:38.195088+0800 YTKNetworkDemo[25388:1340307] instanceSayHello 被完全转发覆盖
// 2020-05-19 18:11:38.195437+0800 YTKNetworkDemo[25388:1340307] reloadData 被完全转发覆盖
可以看到instanceSayHello
方法直接跳到了自定义的customForward
上面,首先通过class_replaceMethod
覆盖掉instanceSayHello
的实现为_objc_msgForward
,直接进入消息转发,有三个步骤,上面说了,我们不做处理,直接来到forwardInvocation
,因为该方法已经被replace掉了,替换成了我们自己的customForward
,携带了最终的NSInvocation
信息,然后我们可以在自定义的方法中实现自己的逻辑,完成转发覆盖。上述只是一个简单的例子,如果自定义的函数里根据每个invocation
的SEL
名字动态化新建一个包含完整代码完全不同的invocation,功能将会异常强大。实际上JSPatch
的某些核心部分也正是使用了这种方式直接替换掉某些类里的方法实现。
简单聊了下NSInvocation
和NSMethodSignature
,重新回顾后,发现理解起来更容易了。我记得之前做组件化的时候看到Casa
的CTMediator
方案,中介者模式好像是用performSelector
的方法实现的,或许也可以用NSInvocation
来尝试下,各位大佬如果看完了,看到有哪些观点有问题的,欢迎在评论区留言指正。
参考文章:
performSelector内存泄漏
typeEncoding
方法编码
消息转发和消息发送
NSINvocation return Value EXC_BAD_ACCESS
名词介绍
Log Message Send打印
同上