我使用objc_msgSendSuper
和objc_msgSendSuper_stret
来调用超类的超类实现。
objc_msgSendSuper
效果如下:
struct objc_super supersSuper;
supersSuper.receiver = self;
supersSuper.super_class = object_getClass(class_getSuperclass(class_getSuperclass(self)));
return objc_msgSendSuper(&supersSuper, _cmd);
但使用objc_msgSendSuper_stret
会引发运行时错误EXC_BAD_ACCESS(code=EXC_i386_GPFLT)
:
// In Tier3 class
+ (CGPoint) point{
struct objc_super supersSuper;
supersSuper.receiver = self;
supersSuper.super_class = object_getClass(class_getSuperclass(class_getSuperclass(self)));
// raise EXC_BAD_ACCESS(code=EXC_i386_GPFLT) when invoking objc_msgSendSuper_stret
return ((CGPoint(*)(struct objc_super *, SEL))objc_msgSendSuper_stret)(&supersSuper, _cmd);
}
选择器确实存在于目标类中:
[Tier1 point]; // return {10, 10}
我看到很多objc_msgSend
,objc_msgSendSuper
和objc_msgSend_stret
示例,但很少使用objc_msgSendSuper_stret
找到示例。
更新
我发现objc_msgSend_stret
的旧API如下:
void objc_msgSend_stret(void * stretAddr, id theReceiver, SEL theSelector, ...)
但是当前的API是:
void objc_msgSend_stret(id self, SEL op, ...)
我认为Apple确实改变了objc_msgSend_stret
的功能签名。不是吗?
更新-2
我确实尝试了以下内容:
CGPoint retVal = CGPointMake(0, 0);
// Cast to old API signature.
((void(*)(CGPoint *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(point));
return retVal;
此转换将使函数objc_msgSend_stret
调用成功。确实调用了目标方法,目标方法返回{10,10}。但是我们得到的返回值不正确,它仍然是{0,0}。
顺便说一下,我发现代码无法在32位架构下工作(仍然会引发EXC_BAD_ACCESS(code=EXC_i386_GPFLT)
)。它只能在64位架构下运行。
更新-3
班级声明:
#import <objc/runtime.h>
#import <objc/message.h>
@interface TestClass : NSObject
+ (CGRect)rect;
+ (CGPoint)point;
@end
@implementation TestClass
+ (CGRect)rect;{
NSLog(@"+[TestClass rect] is invoked");
return CGRectMake(3, 3, 3, 3);
}
+ (CGPoint)point;{
NSLog(@"+[TestClass point] is invoked");
return CGPointMake(10, 10);
}
+ (CGRect)rectRuntimeTest;{
CGRect retVal = CGRectMake(0, 0, 0, 0);
((void(*)(CGRect *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(rect));
return retVal;
}
+ (CGPoint)pointRuntimeTest{
CGPoint retVal = CGPointMake(0, 0);
((void(*)(CGPoint *, id, SEL))objc_msgSend_stret)(&retVal, self, @selector(point));
return retVal;
}
@end
在application:didFinishLaunchingWithOptions:
中,测试以下内容:
CGRect retRect = [TestClass rectRuntimeTest];
NSLog(@"retVal: %@", NSStringFromCGRect(retRect));
CGPoint retPoint = [TestClass pointRuntimeTest];
NSLog(@"retPoint: %@", NSStringFromCGPoint(retPoint));
输出(在64位架构iOS模拟器下):
> +[TestClass rect] is invoked
> retVal: {{3, 3}, {3, 3}}
> +[TestClass point] is invoked
> retPoint: {0, 0} // Should be {10, 10} which is returned from +[TestClass point]
输出(在32位架构iOS模拟器下):
`EXC_BAD_ACCESS (code=2, address=0xbfffca78)`
答案 0 :(得分:0)
从this问题看,CGPoint
似乎是一个“简单”的返回值,因此在这种情况下您应该使用objc_msgSendSuper
。返回CGRect时,您显然应该使用objc_msgSendSuper_stret
。
关于_stret变种的奇怪签名,不要让私人签名打扰你,依赖公共签名是正确的。详细展示了here。
除此之外,你应该总是将objc_msg * Send *转换为非变量(即所有参数定义良好)的形式,并调用它(你在某些地方做,但你应该在所有地方)
答案 1 :(得分:0)
我可能错了,但我做了一些研究,当结构在寄存器中返回时,所谓的“简单”返回值是“简单的”。
如果根据这些ABI符合某些标准标准,POD(普通旧数据)结构将通过寄存器返回:
因此,为了总结一切,我们必须在以下时间使用objc_msgSend_stret
函数:
sizeof(struct) == 1 | 2 | 4 | 8
sizeof(struct) > 16
sizeof(struct) > 4
sizeof(struct) > 64
否则只需使用objc_msgSend
我对arm64并不是100%肯定,因为标准太过抽象了:)