具有省略号数组参数的子类化方法?
我想在init头文件中创build一个具有省略号语法的对象。 即
-(void) initObjectWith:(NSString*)argument arguments:(NSString*)someArgument,...;
我不确定如何在这种情况下传递参数数组。 我怀疑它会是这样的:
- (void) initObjectWithCustomInitializer:(NSString*)argument additionalArgument:(NSString*)additionalArgument argument:(NSString*) someArgument,... { self = [super initObjectWith:argument arguments:someArgument,...]; if (self) { //custom init code here } return self }
这编译但nil终止的“参数”数组只是获得第一个参数。 我如何传递一个无终止数组的对象?
声明该可变参数初始值设定项的超类也应该声明一个非可变参数,它需要一个va_list
(类似于printf
如何具有vprintf
)。 假设这种情况下,超类有两个:
-(void)init:(id)a arguments:(id)b, ...;
和
-(void)init:(id)a arguments:(id)b variadicArgs:(va_list)args;
你会做这样的事情:
- (void)myInit:(id)a newArg:(id)c arguments:(id)b, ... { va_list v; va_start(v, b); self = [super init:a arguments:b variadicArgs:v]; if (self) { //custom init code here } va_end(v); return self; }
当然,你也应该确保你的新构造器有一个非可变的版本!
由于可变参数的实际实现方式以及C语言的局限性,不可能通过...
没有va_list
-taking函数来调用callchain,除非您:
- 使用适合您的代码运行的每个平台的汇编语言
- 知道编译器如何实现
va_list
等等的细节 - 尝试编写一个函数,以某种方式计算参数types的每个可能的组合,并手动传递它们。
在这些选项中,(3)在任何实际情况下显然是不切实际的,(2)随时可能随时更改,恕不另行通知。 这给我们留下了(1)代码运行的每个平台的汇编语言。
在内部,可变参数是针对每种体系结构以ABI特定的方式实现的。 从概念上讲...
说:“我要通过所有我想要的论点,就好像我正在调用一个接受这些论点的函数一样,这取决于你从哪里拿出每个论点。” 我们来看一个体系结构的例子,该体系结构将所有parameter passing到堆栈上,例如OS X和iOS Simulator上的i386
。
给出以下函数的原型和调用:
void f(const char * const format, ...); /* ... */ f("lUf", 0L, 1ULL, 1.0);
编译器将生成下面的程序集(由我写的;一个真正的编译器可能会产生一个有点不同的调用序列,具有相同的效果):
leal L_str, %eax pushl %eax movl $0x3f800000, %eax pushl %eax movl $0x00000000, %eax pushl %eax movl $0x00000001, %eax pushl %eax movl $0x00000000, %eax pushl %eax call _f
这样做的效果是以相反的顺序将每个参数推送到堆栈上。 这是秘密的技巧: 如果f()
被声明为这样,编译器会做同样的事情:
void f(const char * const format, long arg1, unsigned long long arg2, float arg3);
这意味着如果你的函数可以复制堆栈的参数区域并调用可变参数函数,那么这个参数就会简单地通过。 问题:没有通用的方法来确定这个参数区域有多大! 在i386
,在一个函数中有一个帧指针,也有一个帧指针函数调用,你可以欺骗和复制rbp - *rbp
字节,但效率低下,并不适用于所有情况下(尤其是函数采取struct
参数或返回struct
s)。
然后你有像armv6
和armv7
这样的体系结构,其中大部分参数被传递到必须谨慎保存的寄存器x86_64
,其中参数在寄存器中传递, xmm
寄存器计数在%al
传递, ppc
在堆栈位置和寄存器中传递都映射到参数!
在不使用va_list
情况下转发参数的唯一方法是使用程序集为每个体系结构重新实现代码中的整个体系结构ABI逻辑,这与编译器的做法相同。
这也是objc_msgSend()
解决的问题。
“等等!” 你现在说。 “为什么我不能只调用objc_msgSend
而不是用这种方式搞乱大会?!
回答:因为你没有办法告诉编译器:“不要在堆栈上弄乱任何东西,也不要擦除你没有看到我使用的寄存器”。 您仍然需要编写一个程序集例程,将该调用转发给超类的实现 – 在执行任何子类实现之前的任何工作之前 – 然后返回给您的所有同时注意objc_msgSend()
所做的事情,比如需要_stret
和_fpret
至less在三种架构( armv7
, i386
, x86_64
上的变体和实现 – 并根据您对向后和向前兼容性的需求,也可能ppc
, ppc64
, armv6
和armv7s
。
对于简单的可变参数,编译器使用其对调用的深入了解以及目标的调用约定,以便在创buildva_list
时在幕后执行此操作。 C不能直接访问这些信息。 而objc_msgSend()
是Objective-C编译器,运行时重新执行,所以你可以不使用va_list
编写方法调用。 (另外,在某些体系结构中,能够将parameter passing给已知调用列表比使用可变参数约定更有效)。
所以,不幸的是,如果不付出更多的努力,就不可能做到这一点。 类的实现者,让这成为你的一个教训 – 当你提供一个可变参数的方法时,也提供一个接受va_list
代替...
的相同方法的一个版本 。 NSString
是一个很好的例子,它有initWithFormat:
和initWithFormat:arguments:
我想出了一个答案,任何人都好奇! 长话短说我使用股票的“初始化”初始化,然后通过常规的NSArray和使用超类设置器传递参数。
- (id) initWithCustomInitializer:(NSString *)argument arguments:(NSArray*)moreArguments { self = [super init]; if (self) { self.argument = argument; for (int i = 0; i < [moreArguments count]; i++) { [self addArgument:[moreArguments objectAtIndex:i]]; } } return self; }
这就是所谓的:
NSArray *moreArguments = [NSArray arrayWithObjects:@"argument0", @"argument1", @"argument2", nil]; CustomObject *myObject = [[CustomObject alloc] initWithCustomInitializer:@"argument" arguments:moreArguments];
注意:Carl在下面写出了一些好的观点。 这个解决scheme可能不是一般的,普通的init并不总是执行额外的initWith …方法初始化。 虽然这个解决scheme为我工作。