20

Having some issues with the ... in ObjectiveC.

I'm basically wrapping a method and want to accept a nil terminated list and directly pass that same list to the method I am wrapping.

Here's what I have but it causes an EXC_BAD_ACCESS crash. Inspecting the local vars, it appears when otherButtonTitles is simply a NSString when it is passed in with otherButtonTitles:@"Foo", nil]

+ (void)showWithTitle:(NSString *)title
              message:(NSString *)message
             delegate:(id)delegate
    cancelButtonTitle:(NSString *)cancelButtonTitle
    otherButtonTitles:(NSString *)otherButtonTitles, ...
{
    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
                                                     message:message
                                                    delegate:delegate
                                           cancelButtonTitle:cancelButtonTitle
                                           otherButtonTitles:otherButtonTitles] autorelease];
    [alert show];
}

How do I simply siphon from the argument incoming to the argument outgoing, preserving the exact same nil terminated list?

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • 1
    The first object in a variadic method list is not part of the va_list itself, which is why you see otherButtonTitles as an NSString. That is, the va_list only comprises the objects in the "..." part. – Don Feb 26 '10 at 22:51
  • 1
    Since Objective-C is a superset of C, cf. http://stackoverflow.com/questions/150543/forward-an-invocation-of-a-variadic-function-in-c. – Don Feb 26 '10 at 23:05

3 Answers3

39

You can't do this, at least not in the way you're wanting to do it. What you want to do (pass on the variable arguments) requires having an initializer on UIAlertView that accepts a va_list. There isn't one. However, you can use the addButtonWithTitle: method:

+ (void)showWithTitle:(NSString *)title
              message:(NSString *)message
             delegate:(id)delegate
    cancelButtonTitle:(NSString *)cancelButtonTitle
    otherButtonTitles:(NSString *)otherButtonTitles, ...
{
    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
                                                     message:message
                                                    delegate:delegate
                                           cancelButtonTitle:cancelButtonTitle
                                           otherButtonTitles:nil] autorelease];
    if (otherButtonTitles != nil) {
      [alert addButtonWithTitle:otherButtonTitles];
      va_list args;
      va_start(args, otherButtonTitles);
      NSString * title = nil;
      while(title = va_arg(args,NSString*)) {
          [alert addButtonWithTitle:title];
      }
      va_end(args);
    }

    [alert show];
}

This is, of course, very problem-specific. The real answer is "you can't implicitly pass on a variable argument list to a method/function that does not have a va_list parameter". You must therefore find a way around the problem. In the example you gave, you wanted to make an alertView with the titles you passed in. Fortunately for you, the UIAlertView class has a method that you can iteratively call to add buttons, and thereby achieve the same overall effect. If it did not have this method, you'd be out of luck.

The other really messy option would be to make it a variadic macro. A variadic macro looks like this:

#define SHOW_ALERT(title,msg,del,cancel,other,...) { \
  UIAlertView *_alert = [[[UIAlertView alloc] initWithTitle:title message:msg delegate:del cancelButtonTitle:cancel otherButtonTitles:other, ##__VA_ARGS__] autorelease]; \
  [_alert show]; \
}

However, even with the variadic macro approach, you'd still need a custom macro for each time you wanted to do this. It's not a very solid alternative.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 1
    A cool approach, but I was hoping to use this technique in other places where a method like this may not exist to help out with the argument list. Really no way to create a nil terminated list dynamically? – Alex Wayne Feb 26 '10 at 22:46
  • @Squeegy you can maybe fake a `va_list` (see cocoawithlove link in @Don's comment), but unless the method you want to invoke takes a `va_list` (as opposed to `...`), you can't use it. – Dave DeLong Feb 26 '10 at 22:50
  • Fortunately, Dave's solution works for your specific situation. – Don Feb 26 '10 at 22:53
  • 3
    If you need to accept a list of variable arguments (as Dave answers quite nicely above) and then construct a call to a non-variadic API, use NSInvocation. – bbum Feb 27 '10 at 02:36
  • 1
    If anyone wants to see this solution in working code, I used it to create a class through which I'll be funneling all my alert-showing needs from now on. Help yourself and/or add improvements at https://github.com/clozach/CLAlertViewManager – clozach Sep 18 '11 at 05:55
0

This is specific to the OP's UIAlertView-wrapping case, and tested only on iOS7: It appears that once a UIAlertView has been initialised with otherButtons:nil, and then has its style set to UIAlertViewStylePlainTextInput it doesn't call its delegate's alertViewShouldEnableFirstOtherButton: to validate input. I'm not sure if this is a bug or intended behaviour but it broke my principle of least astonishment. This is reproducible with the following (I'll assume the delegate's alertViewShouldEnableFirstOtherButton: is implemented):

UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Title" 
                                             message:@"message" 
                                            delegate:self         
                                   cancelButtonTitle:@"Cancel" 
                                   otherButtonTitles:nil];
[av setAlertViewStyle:UIAlertViewStylePlainTextInput];
[av addButtonWithTitle:@"OK"];
[av show];

The solution, since UIAlertView happily accepts otherButtons:nil, is to initialise UIAlertView with otherButtonTitles (which can be nil), and iterate over the variadic arguments, as above:

+ (void)showWithTitle:(NSString *)title
              message:(NSString *)message
             delegate:(id)delegate
    cancelButtonTitle:(NSString *)cancelButtonTitle
    otherButtonTitles:(NSString *)otherButtonTitles, ...
{
    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:title
                                                     message:message
                                                    delegate:delegate
                                           cancelButtonTitle:cancelButtonTitle
                                           otherButtonTitles:otherButtonTitles] autorelease];

    // add your [alert setAlertViewStyle:UIAlertViewStylePlainTextInput] etc. as required here

    if (otherButtonTitles != nil) {
        va_list args;
        va_start(args, otherButtonTitles);
        NSString * title = nil;
        while(title = va_arg(args,NSString*)) {
            [alert addButtonWithTitle:title];
        }
        va_end(args);
    }

    [alert show];
}
Robin Macharg
  • 1,468
  • 14
  • 22
  • While this is interesting information and should definitely be reported at Apple's [bug reporter](http://bugreport.apple.com/) it is not an answer to this post. As I understand it it does not provide additional information to question asked. – Nikolai Ruhe Oct 29 '13 at 09:56
  • Already reported. I agree, in part; it's a correction to the accepted answer in the place Google brought me to when I was struggling with variadic UIAlertView wrapping of my own. Maybe it will help someone else... – Robin Macharg Oct 29 '13 at 10:45
0

How about constructing an NSInvocation object? Since arguments must be passed by pointer, you could pass the pointer to the nil-terminated list.

You could also iterate over the parameters using marg_list() and construct a nil-terminated list yourself.

These are just simple suggestions; I haven't tried them out.

Don
  • 3,654
  • 1
  • 26
  • 47
  • "`NSInvocation` does not support invocations of methods with either variable numbers of arguments or `union` arguments." - http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSInvocation_Class/Reference/Reference.html#//apple_ref/doc/uid/20000212-1804 – Dave DeLong Feb 26 '10 at 22:36
  • Well, that kills that suggestion! – Don Feb 26 '10 at 22:37
  • I think I can something like this to iterate through the list: http://en.wikipedia.org/wiki/Variadic_function#Variadic_functions_in_C.2C_Objective-C.2C_C.2B.2B.2C_and_D but how do I use `marg_list()` to generate one dynamically I can pipe back in. – Alex Wayne Feb 26 '10 at 22:38
  • 5
    Matt Gallagher has a way to create a fake variadic list: http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html Again, I haven't tried this. – Don Feb 26 '10 at 22:42