objcsendmsg如何正确传递参数?

99ANYc3cd6
预计阅读时长 18 分钟
位置: 首页 参数 正文

objc_msgSend 是 Objective-C 消息传递机制的核心,它的作用是动态地给一个对象(或类)发送消息,并执行与该消息对应的代码(即方法)。

objc_msgSend 的基本签名

objc_msgSend 的函数签名非常复杂,因为它需要处理任意数量的参数,它的基本形式如下:

// id 是对象的类型,objc_object 的 typedef
// SEL 是选择器(方法名)的类型,objc_selector 的 typedef
id objc_msgSend(id self, SEL op, ...);
  • id self: 消息的接收者,一个指向对象的指针。
  • SEL op: 方法的选择器(SEL),它代表方法的名字,你可以用 @selector() 语法来获取一个 SEL。
  • 省略号,表示可以接受任意数量的参数,这些参数的类型和数量必须与你要调用的方法签名完全匹配。

带参数的调用规则

objc_msgSend 的调用规则遵循 C 语言的可变参数函数规则,但有一个关键点在传递参数时,所有参数都必须按值传递,并且必须是原始数据类型(如 int, float, double, char, pointer 等),不能是 C 结构体。

对于 Objective-C 对象(如 NSString*, NSArray*),传递的是指向该对象的指针(即其 id 类型)。

参数传递顺序

参数从右向左压入栈中,对于一个方法 -(void)methodWithArg1:(int)a1 arg2:(float)a2;,调用时 objc_msgSend 的参数顺序是:

  1. self (接收者)
  2. op (选择器)
  3. a2 (第二个参数,float)
  4. a1 (第一个参数,int)

实践示例

假设我们有一个 Person 类,它有一个带参数的方法。

定义类和方法

// Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)greetWithName:(NSString *)name andAge:(NSInteger)age;
@end
NS_ASSUME_NONNULL_END
// Person.m
#import "Person.h"
@implementation Person
- (void)greetWithName:(NSString *)name andAge:(NSInteger)age {
    NSLog(@"Hello, my name is %@ and I am %ld years old.", name, (long)age);
}
@end

使用 objc_msgSend 调用带参数的方法

我们不用 [person greetWithName:@"Alice" andAge:30] 这种语法,而是直接使用运行时函数。

#import <Foundation/Foundation.h>
#import <objc/runtime.h> // 必须导入这个头文件
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 创建一个 Person 实例
        Person *person = [[Person alloc] init];
        // 2. 获取方法的选择器
        // @selector() 是编译器提供的语法糖,用于将方法名转换为 SEL
        SEL greetSelector = @selector(greetWithName:andAge:);
        // 3. 调用 objc_msgSend
        // 注意参数的顺序和类型!
        objc_msgSend(person,
                     greetSelector,
                     @"Alice",      // 对应 name 参数 (NSString *)
                     (NSInteger)30  // 对应 age 参数 (NSInteger)
        );
        // --- 另一个例子:调用一个带多个不同类型参数的方法 ---
        // 假设 Person 类还有一个方法
        // - (void)performAction:(NSString *)action withValue:(double)value isEnabled:(BOOL)enabled;
        SEL actionSelector = @selector(performAction:withValue:isEnabled:);
        objc_msgSend(person,
                     actionSelector,
                     @"run",      // NSString *
                     10.5,        // double
                     YES          // BOOL
        );
        // --- 重要:处理返回值 ---
        // 如果方法有返回值,你需要处理它
        // 调用一个返回 NSString 的方法:- (NSString *)getFullName;
        SEL fullNameSelector = @selector(getFullName);
        id fullName = objc_msgSend(person, fullNameSelector);
        NSLog(@"Full name: %@", fullName);
        // --- 处理基本数据类型返回值 ---
        // 调用一个返回 int 的方法:- (int)getCount;
        SEL countSelector = @selector(getCount);
        int count = (int)objc_msgSend(person, countSelector);
        NSLog(@"Count: %d", count);
    }
    return 0;
}

特殊情况:处理结构体和 id

当方法参数或返回值是 C 结构体时,情况会变得复杂,因为 objc_msgSend 无法直接处理它们,这时需要使用一系列特殊的变体函数。

参数是结构体

如果方法接收一个结构体作为参数,你需要使用 objc_msgSend_stret(structure send)。

假设有一个方法: - (void)processPoint:(CGPoint)point;

调用方式如下:

CGPoint myPoint = CGPointMake(100, 200);
SEL processPointSelector = @selector(processPoint:);
// 使用 objc_msgSend_stret 代替 objc_msgSend
// 第一个参数不再是 id,而是用来存储结构体返回值的内存地址
// 对于返回 void 的方法,这个参数可以传 NULL
objc_msgSend_stret(NULL, person, processPointSelector, myPoint);

返回值是结构体

如果方法的返回值是一个结构体,你也必须使用 objc_msgSend_stret

假设有一个方法: - (CGRect)boundingRect;

调用方式如下:

SEL boundingRectSelector = @selector(boundingRect);
// 必须定义一个结构体变量来接收返回值
CGRect resultRect;
// 第一个参数必须是 &resultRect,即结构体变量的内存地址
objc_msgSend_stret(&resultRect, person, boundingRectSelector);
NSLog(@"Bounding rect: %@", NSStringFromCGRect(resultRect));

处理浮点数

在 32 位系统上,浮点数的处理也有特殊要求,现代的 64 位系统通常已经统一,但在需要兼容旧代码时,你可能需要使用 objc_msgSend_fpret 来处理返回值为浮点数的方法。

// 假设有一个方法 - (float)calculatePi;
SEL piSelector = @selector(calculatePi);
float piValue;
// 使用 objc_msgSend_fpret 处理浮点返回值
piValue = (float)objc_msgSend_fpret(person, piSelector);
NSLog(@"Pi value: %f", piValue);

总结表格

方法类型 应用的函数 调用示例
普通方法(返回 id 或基本类型) objc_msgSend objc_msgSend(receiver, selector, arg1, arg2);
参数包含结构体 objc_msgSend_stret objc_msgSend_stret(NULL, receiver, selector, structArg);
返回值为结构体 objc_msgSend_stret struct result; objc_msgSend_stret(&result, receiver, selector);
返回值为浮点数 (32-bit) objc_msgSend_fpret float result = (float)objc_msgSend_fpret(receiver, selector);

为什么要用 objc_msgSend

直接使用 objc_msgSend 在日常开发中非常罕见,因为编译器已经帮我们处理了这一切,但在以下场景中,它非常有用:

  1. 动态方法调用:在运行时才知道要调用哪个方法或哪个对象的方法,通过字符串反射调用方法。
  2. 消息转发:在 forwardingTargetForSelector:methodSignatureForSelector: 等方法中,你需要手动将消息转发给另一个对象。
  3. 底层框架和 AOP:一些底层库或面向切面编程的框架会利用它来拦截和替换方法调用。
  4. 性能分析:通过直接调用运行时函数,可以绕过一些编译器优化,进行性能测试。

希望这个详细的解释能帮助你理解 objc_msgSend 是如何处理参数的!

-- 展开阅读全文 --
头像
thinkbook15拆机视频
« 上一篇 今天
androbench参数具体指哪些?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

最近发表

标签列表

目录[+]