Objective-C的消息与方法签名

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

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

1、选择器

在对象消息传递过程中,选择器是一种文本字符串,用于指明调用实例或类中的哪些方法。

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

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

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

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

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

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

创建SEL变量:

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

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

NSInvocation 是以对象方法呈现封装的 Objective-C 消息。用于在对象之间、应用程序之间存储和转发消息,主要由NSTimer对象和分布式对象系统来实现。

NSInvocation对象包含Objective-C消息的必要元素:目标target、选择器SEL、参数和返回值。可以直接设置这些元素,并且在发送消息时自动设置返回值。

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

NSInvocation 不支持使用可变数量的参数或联合参数调用方法。应该使用类方法+invocationWithMethodSignature:创建NSInvocation实例。

默认情况下,该类不会保留调用的参数。这些参数可能会释放,应该显式地保留这些参数,或者调用-retainArguments方法来让调用对象保留它们本身。

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

2.1、封装一个消息
/* 使用指定的方法签名 sig 构造消息
 */
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

//接收者的方法签名。
@property(readonly, retain) NSMethodSignature *methodSignature;
2.2、配置NSInvocation
2.2.1、配置接收器、选择器
//接收器的选择器,默认为 nil。
@property SEL selector;
 
//目标是发送消息的接收者。默认为 nil。
@property(assign) id target;
2.2.2、配置接收器的参数
/* 将 argumentLocation 的数据复制为index的参数
 * @param 分配给接收器的参数的无类型缓冲区
 * @param idx 索引;0 和 1 表示隐藏的参数 self 和 _cmd。通常使用 2 或者更大值
 * @note 复制的字节数由参数大小决定
 */
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

/* 将存储在索引 idx 的参数复制到缓冲区指向的存储中 argumentLocation。
 * @note 缓冲区的大小必须足够大以容纳参数值。
 */
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

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

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

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

2.2.3、是否保留参数

为了提高效率,新创建的NSInvocation实例不持有参数,也不持有其目标。 如果需要缓存,则应指示NSInvocation对象保留其参数,否则在调用之前可能会释放参数。 NSTimer 对象总是指示其调用保留其参数,例如,因为在计时器触发之前通常会有一个延迟。

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

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

/* 接收器复制其返回值的无类型缓冲区。
 * @param 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

什么是方法签名?方法签名由方法返回类型的一个或多个字符组成,后面是隐式参数self_cmd的字符串编码,接着可能是更多显式参数。

NSMethodSignature 以对象方式对方法签名的封装。使用methodReturnTypemethodReturnLength属性确定字符串编码和返回类型的长度。使用-getArgumentTypeAtIndex: 方法和numberOfArguments属性单独访问参数。在方法签名不匹配时,使用NSMethodSignature转发消息。

/* 必须重写该方法,创建 NSMethodSignature 实例
 * 消息转发通过该方法创建的 NSMethodSignature 创建 NSInvocation 对象
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *sign = [super methodSignatureForSelector:aSelector];
    if (sign == nil && aSelector == NSSelectorFromString(@"unrecognized selector")){
        if ([ModelHelper instancesRespondToSelector:aSelector]){
            sign = [ModelHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    return sign;
}
                    
/* 将消息转发给其它对象处理
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if ([ModelHelper instancesRespondToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:self.helper];
    }
}

类型编码

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

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

  • @encode(BOOL) (c):返回值类型
  • @encode(id) (@) :接收者self
  • @encode(SEL) (:) :选择器 _cmd
  • @encode(NSString *) (@) :第一个参数
3.1、创建方法签名
/* 返回指定Objective-C方法类型字符串的NSMethodSignature对象。
 * @param types 包含方法参数的类型编码
 */
+ (NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

/* 获取指定位置参数的类型编码
 * @param idx 参数索引;0 代表 self ,1 代表 _cmd;显式参数从索引2开始
 * @return 该索引处参数的类型编码
 * @note 如果索引超过参数数量,则引发异常NSInvalidArgumentException
 */
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx;

/* 接收器中的参数数量
 * @note 至少有两个参数 self 和 _cmd
 */
@property(readonly) NSUInteger numberOfArguments;

/* 所有参数总共占用内存的字节数
 * @note 内存因应用程序运行的硬件体系结构而异
 */
@property(readonly) NSUInteger frameLength;

/* 方法的返回类型
 */
@property(readonly) const char *methodReturnType;

/* 方法的返回值大小
*/
@property(readonly) NSUInteger methodReturnLength;

/* 确定同步状态
 * @return 接收器异步调用,则为YES,否则为NO
 * @note 如果方法是单向的,则远程消息的发送方不会阻止等待回复。
 */
- (BOOL)isOneway;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容