使用省略号数组参数的子类方法?

时间:2013-01-25 22:07:34

标签: ios objective-c

我想在init标头中继承一个具有省略号语法的对象。即。

-(void) initObjectWith:(NSString*)argument arguments:(NSString*)someArgument,...;

在这种情况下,我不确定如何传递arguments数组。我怀疑它会是这样的:

- (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终止的'arguments'数组只获得第一个参数。如何传递nil-terminated数组的对象?

3 个答案:

答案 0 :(得分:9)

声明variadic初始值设定项 的超类也声明了一个非变量的类型,它采用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;
}

当然,您也应该确保新的初始化程序的非可变版本!

答案 1 :(得分:7)

由于实际实现了varargs的方式以及C语言的限制,如果没有...,则无法将va_list args传递给调用链 - 除非您执行以下操作:

  1. 使用适合您的代码可运行的每个平台的汇编语言
  2. 了解编译器如何实现va_list等或
  3. 的详细信息
  4. 尝试编写一个函数,以某种方式计算每种可能的参数类型组合并手动传递它们。
  5. 在这些选项中,(3)在任何现实情况下显然都是不切实际的,并且(2)在任何时候都可能随时更改,恕不另行通知。这给我们留下了(1)汇编语言,用于代码运行的每个平台。

    在内部,varargs以针对每种架构的ABI特定方式实现。从概念上讲,...说“我将传递我想要的所有参数,就像我调用一个接受这些参数的函数一样,并且由你决定从哪里获取每个参数。”让我们举一个架构的例子,它在栈上传递它的所有参数,例如OS X上的i386和iOS模拟器。

    给出以下函数原型并调用:

    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);
    

    这意味着如果你的函数可以复制堆栈的参数区域并调用vararg-taking函数,那么args将简单地通过。问题:没有通用的方法来确定这个参数区域的大小!在i386上,在一个具有帧指针的函数中,该指针也是从具有帧指针的函数调用的,您可以欺骗并复制rbp - *rbp个字节,但这样做效率低,并不适用于所有字节案例(特别是采用struct参数或返回struct s的函数。)

    然后你有armv6armv7这样的架构,其中大多数参数都在寄存器中传递,必须仔细保存x86_64,其中参数在寄存器中传递和< / em> xmm寄存器计数在%alppc中传递,其中堆栈位置和寄存器映射到参数!

    在不使用va_list的情况下转发参数的唯一防弹方法是使用每个体系结构的程序集重新实现代码中的整个体系结构ABI逻辑,与编译器的方式相同。

    这也与objc_msgSend()解决的问题基本相同。

    “等等!”你现在说。 “为什么我不能只调用objc_msgSend而不是用这种方式搞乱汇编?!”

    答案:因为你无法告诉编译器,“不要破坏堆栈中的任何内容,也不要删除任何你看不到我使用的寄存器”。您仍然必须编写一个汇编例程,将调用转发给超类实现 - ,然后在您的子类实现中进行任何工作 - 然后返回给您,同时注意相同的事情{{1 }},例如,至少需要三个架构(objc_msgSend()_stret_fpret上的armv7i386变体和实现 - 并且取决于您需要向前和向后兼容,也可能是x86_64ppcppc64armv6)。

    对于普通的varargs,编译器在创建armv7s时使用其对调用的深入了解以及目标的调用约定来在幕后完成这项工作。 C不直接访问任何此类信息。并且va_list是Objective-C编译器和运行时再次重做它,因此您可以一直编写方法调用而不使用objc_msgSend()。 (另外,在某些体系结构中,将参数传递给已知调用列表比使用varargs约定更有效。)

    所以,不幸的是,如果不把更多的工作投入到努力中,你就不可能做到这一点。类实现者,这给你一个教训 - 每当你提供一个采用可变参数的方法时,也提供一个接受va_list代替va_list的方法的版本...是一个很好的例子,NSStringinitWithFormat:

答案 2 :(得分:0)

我找到了答案,对于任何好奇的人!长话短说我使用了股票'init'初始化程序,然后通过常规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];

注意:卡尔在下面提出了一些要点。此解决方案可能不是通用的,并且普通init并不总是执行initWith ...方法可能的额外初始化。不过,这个解决方案对我有用。