Subclass method with ellipsis array argument?

I would like to subclass an object with ellipsis syntax in the init header. i.e.

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

I am not sure how to pass an array of arguments in this case. I suspect it will be something like:

 - (void) initObjectWithCustomInitializer:(NSString*)argument additionalArgument:(NSString*)additionalArgument argument:(NSString*) someArgument,... { self = [super initObjectWith:argument arguments:someArgument,...]; if (self) { //custom init code here } return self } 

This compiler, but the nil-terminated argument array receives only the first argument. How to go through zero-terminated array objects?

+4
source share
3 answers

A superclass that declares that a variable initializer must also declare an invariant that accepts va_list (similar to printf having vprintf , for example). Assuming the case where the superclass has both:

 -(void)init:(id)a arguments:(id)b, ...; 

and

 -(void)init:(id)a arguments:(id)b variadicArgs:(va_list)args; 

You would do something like:

 - (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; } 

Of course, you must also have a non-rotatable version of your new initializer!

+9
source

Due to the fact that varargs and C language restrictions are actually implemented, it is impossible to pass ... args down the call chain without the va_list -taking function to call if you:

  • Use the assembly language appropriate for each platform your code can run on.
  • Know the intimate details of how the compiler implements va_list et al., Or
  • Try writing a function that somehow computes all possible combinations of argument types and passes them manually.

Of these options, (3) is obviously impractical in any realistic case, and (2) is subject to change without notice at any time. This leaves us with (1), the assembly language for each platform on which your code runs.

Internally, varargs are implemented using ABI for each architecture. It is clear that ... says: "I am going to pass all the arguments that I want, as if I were calling a function that took these arguments, and you decide where to get each argument." Take an example of an architecture that passes all of its arguments on the stack, such as i386 on OS X and iOS Simulator.

Given the following function prototype and call:

 void f(const char * const format, ...); /* ... */ f("lUf", 0L, 1ULL, 1.0); 

The compiler will generate the following assembly (as I wrote, a real compiler will probably produce a slightly different sequence of calls with the same effect):

 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 

The effect of this is to push each parameter onto the stack in reverse order. Here's a secret trick: the compiler would do the same if f() was declared as follows:

 void f(const char * const format, long arg1, unsigned long long arg2, float arg3); 

This means that if your function can copy the stack parameter area and call the vararg accept function, the arguments will simply pass. Problem: There is no general way to find out how large this parameter area is! In i386 , in a function that has a frame pointer, which is also called from a function that has a frame pointer, you can spoof and copy rbp - *rbp bytes, but this is inefficient and will not work for all cases (especially functions that take struct parameters or return struct s).

Then you have architectures such as armv6 and armv7 , where most parameters are passed in registers, which must be carefully stored, x86_64 , where parameters are passed in registers, and the number of registers xmm is passed in %al and ppc , where the locations and registers of the stack are displayed like parameters!

The only bulletproof way to redirect arguments without using va_list is to reimplement the entire ABI logic architecture in your code using the assembly for each architecture, just like the compiler does.

This is also essentially the same problem that objc_msgSend() solves.

"So wait!" you are talking now. "Why can't I just call objc_msgSend instead of entering the assembly this way ?!

Answer. Because you have no way to tell the compiler: "Do not touch anything on the stack and do not destroy any registers that you do not see me." You still have to write a build procedure that redirects the call to the implementation of the superclass before doing any work in your implementation of the subclass, and then return to yours, all with the same things in mind as objc_msgSend() , for example, the need for _stret and _fpret options and implementations for at least three architectures ( armv7 , i386 , x86_64 ), and depending on your need for compatibility with the reverse and previous capabilities, ppc , ppc64 , armv6 and armv7s also armv7s ).

For simple varargs, the compiler uses its intimate knowledge of your calls and calling conventions of targets to do this work behind the scenes when creating a va_list . C does not provide direct access to any of this information. And objc_msgSend() is an Objective-C compiler and a runtime that repeats it again so you can write method calls without using va_list all the time. (In addition, on some architectures it is more efficient to pass parameters to a known call list than to use the varargs conventions).

So, unfortunately, you cannot do this without putting more work into the work than it may cost. Class creators, let this be a lesson for you - when you provide a method that takes variable arguments, also provide a version of the same method that takes va_list instead ... NSString is a great example: initWithFormat: and initWithFormat:arguments:

+7
source

I understood the answer, for someone curious! In short, I used the init initializer, and then passed the arguments through a regular NSArray and used superclasses.

 - (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; } 

It is called like this:

 NSArray *moreArguments = [NSArray arrayWithObjects:@"argument0", @"argument1", @"argument2", nil]; CustomObject *myObject = [[CustomObject alloc] initWithCustomInitializer:@"argument" arguments:moreArguments]; 

Note: Carl makes a few good points below. This solution may not be general, and simple init does not always perform the additional initialization that the initWith method can use .... However, this solution worked for me.

0
source

All Articles