I wrote a Helper class with c functions for an iOS Library with the following pattern. There are 2 wrapping (variadic) functions, which finally call the same function, with slightly different parameter. Idea is to have "default" properties being set.
__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...);
__attribute__((overloadable)) void func1(int param1, NSString* _Nonnull format, ...);
Both will then call the following function:
void prefixAndArguments(int param1, NSString* _Nonnull format, va_list arguments);
Implementation as followed:
__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...)
{
va_list argList;
va_start(argList, format);
prefixAndArguments(0, format, argList);
va_end(argList);
}
__attribute__((overloadable)) void func1(int param1, NSString* _Nonnull format, ...)
{
va_list argList;
va_start(argList, format);
prefixAndArguments(param1, format, argList);
va_end(argList);
}
void prefixAndArguments(NMXLogLevelType logLevel, NSString* _Nullable logPrefix, __strong NSString* _Nonnull format, va_list arguments)
{
// Evaluate input parameters
if (format != nil && [format isKindOfClass:[NSString class]])
{
// Get a reference to the arguments that follow the format parameter
va_list argList;
va_copy(argList, arguments);
int argCount = 0;
NSLog(@"%d",argCount);
while (va_arg(argList, NSObject *))
{
argCount += 1;
}
NSLog(@"%d",argCount);
va_end(argList);
NSMutableString *s;
if (numSpecifiers > argCount)
{
// Perform format string argument substitution, reinstate %% escapes, then print
NSString *debugOutput = [[NSString alloc] initWithFormat:@"Error occured when logging: amount of arguments does not for to the defined format. Callstack:\n%@\n", [NSThread callStackSymbols]];
printf("%s\n", [debugOutput UTF8String]);
s = [[NSMutableString alloc] initWithString:format];
}
else
{
// Perform format string argument substitution, reinstate %% escapes, then print
va_copy(argList, arguments);
// This is were the EXC_BAD_ACCESS will occur!
// Error: Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
s = [[NSMutableString alloc] initWithFormat:format arguments:argList];
[s replaceOccurrencesOfString:@"%%"
withString:@"%%%%"
options:0
range:NSMakeRange(0, [s length])];
NSLog(@"%@",s);
va_end(argList);
}
...
}
My Unit Tests for the function look the following (order is important).
// .. some previous cases, I commented out
XCTAssertNoThrow(NMXLog(@"Simple string output"));
XCTAssertNoThrow(NMXLog(@"2 Placeholders. 0 Vars %@ --- %@"));
The crash happens when I want to use the arguments and the format (making format strong did not solve the problem, and does not seem being part of the problem, see below):
s = [[NSMutableString alloc] initWithFormat:format arguments:argList];
Here is the Log:
xctest[28082:1424378] 0
xctest[28082:1424378] --> 1
xctest[28082:1424378] Simple string output
xctest[28082:1424378] 0
xctest[28082:1424378] --> 4
Of course we won't see the desired string "2 Placeholders. 0 Vars %@ --- %@"
as the crash happened before.
So, the question is now: Why is the amount of arguments now being 4 instead of 0? As none being passed in the second call, are the arguments being collected when the function is being called immediately again?
So, I started to call the function "again" to make sure the argument's list is being cleared, although va_end
was being called:
__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...)
{
va_list argList;
va_start(argList, format);
prefixAndArguments(none, nil, format, argList);
va_end(argList);
NSString *obj = nil;
prefixAndArguments(none, nil, obj, nil);
}
This does work now like a charm (argument's list is being cleared and the desired output is being received):
xctest[28411:1453508] 0
xctest[28411:1453508] --> 1
xctest[28411:1453508] Simple string output
xctest[28411:1453508] 0
xctest[28411:1453508] --> 1
Error occured when logging: amount of arguments does not for to the defined format. Callstack: ....
xctest[28411:1453508] 2 Placeholders. 0 Vars %@ --- %@
Here is finally my question:
What is the reason for this behavior and how can I avoid it? Is there a better way to solve the issue than "stupidly" calling the function a second time with "no" arguments to clear the them? P.s. I tried not to use macros, because I consider them as more error prone than c functions. See this thread: Macro vs Function in C