8
votes

I'm trying to construct "fake" variable arguments list, using the technique described here, but for ARC-enabled project and I can't figure out how to get rid of the error I'm getting.

Here's the code in question:

NSMutableArray* argumentsArray = [NSMutableArray array];

// ... Here I fill argumentsArray with some elements
// And then, I want to construct a "fake" variable argument list

char* fakeArgList = (char*) malloc( sizeof(NSString*) * [argumentsArray count]); 
[argumentsArray getObjects: (id*) fakeArgList];

NSString* content = [[NSString alloc] initWithFormat: formatString arguments:fakeArgList]; 

XCode complains on the (id) fakeArgList* casting, saying:

Cast of non-Objective-C pointer type 'char *' to '_autoreleasing id *' is disallowed with ARC

My initial theory was that I just need to add __unsafe_unretained to (id*) casting to tell ARC that I'm responsible for that block of memory and it shouldn't retain/release it, but that doesn't work and I can't figure out how to fix this problem.

Update: Here's the full function. It should take a printf-style format string and a variable list of field names inside the .plist and output a formatted string with data loaded from .plist. I.e., if I have a .plist file with fields "field1" = "foo" and "field2" = 3 and I call [loadStringFromFixture: @"?param1=%@&param2=%d", @"field1", @field2] then I should get string "?param1=foo&param2=3"

- (NSString*) loadStringFromFixture:(NSString*) format, ...
{
    NSString* path = [[NSBundle mainBundle] bundlePath];
    NSString* finalPath = [path stringByAppendingPathComponent:@"MockAPI-Fixtures.plist"];
    NSDictionary* plistData = [NSDictionary dictionaryWithContentsOfFile:finalPath];

    va_list argumentsList;    
    va_start(argumentsList, format);

    NSString* nextArgument;
    NSMutableArray* argumentsArray = [NSMutableArray array];

    while((nextArgument = va_arg(argumentsList, NSString*)))
    {
        [argumentsArray addObject: [plistData objectForKey:nextArgument]];
    }

    NSRange myRange = NSMakeRange(0, [argumentsArray count]);

    id* fakeArgList = (__bridge id *)malloc(sizeof(NSString *) * [argumentsArray count]); 
    [argumentsArray getObjects:fakeArgList range:myRange];
    NSString * content = [[NSString alloc] initWithFormat:formatString
                                                arguments:(__bridge va_list)fakeArgList];

    free(fakeArgList);

    return content;
}
1
There must be a better way to do this...semisight
Seriously; a quick read of the source article leaves me thinking that it only works by coincidence and is outside the bounds of the ABI or spec.bbum
That's true, but I need it only for my unit-test code, so I'm less concerned with the fact that it's a pretty "dirty hack". Though if someone can point out to a better way to construct a variable arguments list, I'm really interested.Alex Koloskov
Try something like __builtin_va_arg_pack documented in Constructing Calls, maybe? ARC restricts object pointers to be either valid or nil at all times.Jeremy W. Sherman
More generally: You should ask a question about what you are trying to do and how it might be done. This is one approach, but another might avoid needing to do it at all.Jeremy W. Sherman

1 Answers

2
votes

Looking at this code, it seems like a pretty dirty hack, but I suppose if it worked without ARC, it should also work with ARC. The problem here is that you're casting from C-pointers to Objective-C pointers, which you cannot do without bridging:

NSMutableArray * argumentsArray = [NSMutableArray array];

// ... Here I fill argumentsArray with some elements
// And then, I want to construct a "fake" variable argument list

NSRange myRange = NSMakeRange(0, [argumentsArray count]);
id * fakeArgList = (__bridge id *)malloc(sizeof(NSString *) * [argumentsArray count]); 
[argumentsArray getObjects:fakeArgList range:myRange];
NSString * content = [[NSString alloc] initWithFormat:formatString
                                            arguments:(__bridge va_list)fakeArgList];
free(fakeArgList);

This is still pretty ugly, and I really hope that there is a better way of doing this, but since it is for a unit test, I guess it will have to do.

EDIT: It may be that the bridged cast should be done like this instead:

id __autoreleasing * fakeArgList = (__bridge id __autoreleasing *)malloc(sizeof(NSString *) * [argumentsArray count]);