Objective-C的消息与方法签名

Objective-C 的方法调用就是一个对象消息传递的过程。在对象消息传递的过程中,涉及到了几个概念:

  • 选择器:是一种文本字符串,用于指明调用的哪些方法;
  • 消息:NSInvocation封装了消息,包含消息的所有元素:目标target、选择器SEL、参数和返回值;
  • 方法签名NSMethodSignature:定义了方法输入参数的数据类型和方法的返回值

1、选择器

在 Objective-C 的对象消息传递过程中,选择器是一种文本字符串,用于指明调用对象或类中的哪些方法。
Objective-C 运行时系统使用选择器,为目标对象提供正确的方法实现代码 :

选择器示例
logModel
sumA: b:
sumA: : //空选择器分段

在 Objective-C 中,消息的选择器直接与一个或者多个类方法/实例方法声明对应。

- (void)logModel;
- (int)sumA:(NSNumber *)a b:(int)b;
- (int)sumA:(int)a :(int)b;

当 Objective-C 代码被编译时,编译器会创建数据结构和函数调用语句,使用它们以动态方式将接收器和消息选择器的实现代码对应起来。
在执行程序时,Runtime 库利用这些信息找到并调用适当的方法。

1.1、选择器类型 SEL
//SEL 的声明:是 objc_selector 类型的结构指针
typedef struct objc_selector *SEL:

选择器类型SEL是一种特殊的Objective-C数据类型,用于在编译源代码时替换选择器值的唯一标识符;具有相同选择器值的方法具有相同的SEL标识符

创建SEL变量:

  • @selector指令:在编译时创建一个选择器变量;
  • NSSelectorFromString(NSString *) 函数:在程序运行时创建一个选择器变量。

2、对 Objective-C 消息的封装 :NSInvocation

NSInvocation 是封装的 Objective-C 消息,以对象方法呈现。

NSInvocation 对象用于在对象之间和应用程序之间存储和转发消息,主要由NSTimer对象和分布式对象系统来实现。NSInvocation 对象包含Objective-C消息的所有元素:目标target、选择器SEL、参数和返回值。可以直接设置这些元素中的每个元素,并且在发送NSInvocation对象时自动设置返回值。

一个NSInvocation 对象可以重复地分派到不同的目标;它的参数可以根据不同的结果在调度之间进行修改;甚至它的选择器也可以更改为具有相同方法签名(参数和返回类型)的另一个选择器。这种灵活性使NSInvocation可用于许多具有重复参数和变量的消息;而不是为每条消息重新输入略有不同的表达式,每次根据需要修改NSInvocation对象,然后再将其分派给新目标。

NSInvocation 不支持使用可变数量的参数或联合参数调用方法。应该使用+invocationWithMethodSignature: 类方法来创建NSInvocation对象,而不是使用+alloc-init创建这些对象。

默认情况下,该类不会保留包含的调用的参数。如果这些对象可能在创建NSInvocation 实例和使用它之间消失,那么应该显式地保留这些对象,或者调用-retainArguments 方法来让调用对象保留它们本身。

注意:NSInvocation符合NSCoding协议,但仅支持NSPortCoder编码。 NSInvocation不支持存档。

2.1、封装一个 Objective-C 消息
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

//接收者的方法签名。
@property(readonly, retain) NSMethodSignature *methodSignature;

调用该方法获取一个NSInvocation对象,该对象使用指定的方法签名sig构造消息。

2.2、配置NSInvocation
2.2.1、配置接收器、选择器
//接收器的选择器,如未设置则为nil。
@property SEL selector;  
 
//目标是通过调用发送的消息的接收者。
//接收者的目标对象,如果接收者没有目标则为nil。
@property(assign) id target;  
2.2.2、配置接收器的参数
//将 argumentLocation 的内容复制为index的参数。
// 复制的字节数由参数大小决定。
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

//将存储在索引 idx 的参数复制到缓冲区指向的存储中 argumentLocation。 
//缓冲区的大小必须足够大以容纳参数值。
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
  • 参数argumentLocation :要分配给接收器的参数的无类型缓冲区。
  • 参数idx :参数的索引;索引 01 分别表示隐藏的参数 self_cmd 。 在消息中传递的参数通常使用索引 2 和更大。

当参数值是对象时,将指针传递给应从中复制对象的变量(或内存):

NSArray *anArray;
[invocation setArgument:&anArray atIndex:3];
[invocation getArgument:&anArray atIndex:3];

如果index的值大于选择器的实际参数数,则此方法引发NSInvalidArgumentException

2.2.3、是否保留参数

为了提高效率,新创建的NSInvocation对象不保留或复制其参数,也不保留其目标,复制 C 字符串或复制任何关联的块。 如果打算对其进行缓存,则应指示NSInvocation对象保留其参数,否则在调用之前可能会释放参数。 NSTimer 对象总是指示其调用保留其参数,例如,因为在计时器触发之前通常会有一个延迟。

//如果接收者保留了其参数,则为YES,否则为NO。
@property(readonly) BOOL argumentsRetained;  

/* 在调用此方法之前,argumentsRetained返回NO; 
 * 在调用此方法之后,argumentsRetained返回YES。
 *
 * 如果接收方尚未这样做,则保留接收方的目标和所有对象参数,并复制其所有C字符串参数和块
 * 如果已设置returnvalue,则也会保留或复制该值。
 */
- (void)retainArguments;
2.2.4、接收者的返回值
/* 设置接收者的返回值。
 * 通常在发送 -invoke或 -invokeWithTarget:消息前需要设置此值。
 *
 * 参数 retLoc :一个无类型缓冲区,其内容被复制为接收方的返回值。
 */
- (void)setReturnValue:(void *)retLoc;

/* 接收器复制其返回值的无类型缓冲区。 
 * 参数 retLoc 应该足够大以容纳其值。 
 */
- (void)getReturnValue:(void *)retLoc;

使用方法签名NSMethodSignature的方法-methodReturnLength确定缓冲区所需的大小:

NSUInteger length = [[myInvocation methodSignature] methodReturnLength];
buffer = (void *)malloc(length);
[invocation getReturnValue:buffer];

当返回值是对象时,将指针传递给应放置对象的变量(或内存):

id anObject;
NSArray *anArray;
[invocation1 getReturnValue:&anObject];
[invocation2 getReturnValue:&anArray];

如果从未调用过NSInvocation对象,则此方法的结果是未定义的。

2.3、发送消息
/* 调用:将接收者的消息(带参数)发送到其目标并设置返回值。
 * 注意:在调用此方法之前,必须设置接收器的目标,选择器和参数值。
 */
- (void)invoke;

/* 指定目标的调用:设置接收者的目标,将接收者的消息(带参数)发送到该目标,并设置返回值。
 * 注意:在调用此方法之前,必须设置接收器的选择器和参数值。
 */
- (void)invokeWithTarget:(id)target;

3、方法签名NSMethodSignature

NSMethodSignature 对方法签名的封装,以对象方法呈现。

在方法签名不匹配时,使用NSMethodSignature转发消息。通常使用NSObject-methodSignatureForSelector:实例方法创建NSMethodSignature对象;然后,它用于创建NSInvocation对象,该对象作为参数传递给-forwardInvocation:消息,以将NSInvocation发送到任何其他对象可以处理消息。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([ModelHelper instancesRespondToSelector:anInvocation.selector])
    {
        [anInvocation invokeWithTarget:self.helper];
    }
}

//必须重写这个方法,消息转发使用这个方法获得的信息创建NSInvocation对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *sign = [super methodSignatureForSelector:aSelector];
    NSLog(@"第三步:方法%s 的入参 %s,最终返回:%@",__func__,sel_getName(aSelector),sign);
    
    if (sign == nil && aSelector == NSSelectorFromString(@"aEmptyMethod3"))
    {
        if ([ModelHelper instancesRespondToSelector:aSelector])
        {
            sign = [ModelHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    
    return sign;
}
类型编码

NSMethodSignature对象是用代表返回字符串编码和方法参数类型的字符数组初始化的。使用@encode()编译器指令获得特定类型的字符串编码。因为字符串编码是特定于实现的,所以不应该硬编码这些值。

什么是方法签名?方法签名由方法返回类型的一个或多个字符组成,后面是隐式参数self_cmd的字符串编码,接着可能是更多显式参数。使用methodReturnTypemethodReturnLength属性确定字符串编码和返回类型的长度。可以使用-getArgumentTypeAtIndex: 方法和numberOfArguments属性单独访问参数。

例如,NSString实例方法-containsString:具有带以下参数的方法签名:

  • @encode(BOOL) (c):返回值类型
  • @encode(id) (@) :接收者self
  • @encode(SEL) (:) :选择器 _cmd
  • @encode(NSString *) (@) :第一个参数
3.1、创建方法签名
/* 返回指定Objective-C方法类型字符串的NSMethodSignature对象。
 *  types 包含方法参数的类型编码的字符数组。
 */
+ (NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
3.2、获取有关参数类型的信息
3.2.1、获取指定位置参数的类型编码
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx;
  • 参数 idx:参数的索引;索引以0开头。隐式参数self_cmd(位于索引0和1; 显式参数从索引2开始。
  • 返回值:该索引处参数的类型编码。

注意:如果索引超过参数数量,则引发NSInvalidArgumentException

3.2.2、接收器中的参数数量
@property(readonly) NSUInteger numberOfArguments;

始终至少有两个参数,因为NSMethodSignature对象包含隐式参数self_cmd,它们是传递给每个方法实现的前两个参数。

3.2.3、所有参数总共占用堆栈的字节数
@property(readonly) NSUInteger frameLength;

此数字因应用程序运行的硬件体系结构而异。

3.3、获取返回值类型的信息
//一个C字符串,用于编码Objective-C类型编码中方法的返回类型。
@property(readonly) const char *methodReturnType;

//返回值所需的字节数。
@property(readonly) NSUInteger methodReturnLength;
3.4、确定同步状态

接收器在通过分布式对象调用时是否是异步的。

- (BOOL)isOneway;

如果接收器在通过分布式对象调用时是异步的,则为YES,否则为NO。
如果方法是单向的,则远程消息的发送方不会阻止等待回复。

猜你喜欢

转载自blog.csdn.net/weixin_33877885/article/details/87511226
今日推荐