I can't get my head around the syntax for multiple arguments in Objective-C. I have seen this question, but the answer hasn't helped me (yet).
Here is my code (actually I will want to eventually pass to NSString stringWithFormat, but getting an NSLog to work would be good enough for now):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self log:#"blah blah %d", 32];
}
- (void)log:(NSString *)text, ... {
va_list args;
va_start(args, text);
NSLog(text, args);
}
The argument (or some argument) comes through, but it's got some weird value (output is blah blah 1606412704). How should I pass the values that come in via ...?
There's a variant of NSLog that accepts a va_list called NSLogv:
- (void) log:(NSString *)text, ... {
va_list args;
va_start(args, text);
NSLogv(text, args);
va_end(args);
}
The only way to forward the actual ... (not the va_list) is to use a macro. For example:
#define MyLog(f, ...) { \
NSLog(f, ##__VA_ARGS__); \
[someObject doSomething:f, ##__VA_ARGS__]; \
}
However, this should be used very sparingly, since macros can make code really obfuscated.
You could use -[NSString initWithFormat:arguments:]:
- (void)log:(NSString *)text, ...
{
va_list args;
va_start(args, text);
NSString *log_msg = [[[NSString alloc] initWithFormat:text arguments:args] autorelease];
NSLog(#"%#", log_msg);
}
Related
Maybe this will be obviously simple for most of you, but could you please give an example how to create similar methods (in Objective-C) and functions in C to create functions like NSString's stringWithFormat:, or NSLog().
Just to remind:
[NSString stringWithFormat:#"example tekst %i %# %.2f", 122, #"sth", 3.1415"];
NSLog(#"account ID %i email %#", accountID, email);
I'd like to create the similar to NSString's method stringWithFormat:, NSURL - urlWithFormat.
What these are called, generally, is "variadic functions" (or methods, as it were).
To create this, simply end your method declartion with , ..., as in
- (void)logMessage:(NSString *)message, ...;
At this point you probably want to wrap it in a printf-like function, as implementing one of those from scratch is trying, at best.
- (void)logMessage:(NSString *)format, ... {
va_list args;
va_start(args, format);
NSLogv(format, args);
va_end(args);
}
Note the use of NSLogv and not NSLog; consider NSLog(NSString *, ...); vs NSLogv(NSString *, va_list);, or if you want a string; initWithFormat:arguments: on NSString *.
If, on the other hand, you are not working with strings, but rather something like
+ (NSArray *)arrayWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
things get a lot easier.
In that case, instead of a vprintf-style function, use a loop going through args, assuming id as you go, and parse them as you would in any loop.
- (void)logMessage:(NSString *)format, ... {
va_list args;
va_start(args, format);
id arg = nil;
while ((arg = va_arg(args,id))) {
/// Do your thing with arg here
}
va_end(args);
}
This last sample, of course, assumes that the va_args list is nil-terminated.
Note: In order to make this work you might have to include <stdarg.h>; but if memory serves, this gets included in connection with NSLogv, meaning it comes down by way of "Foundation.h", therefore also "AppKit.h" and "Cocoa.h", as well as a number of others; so this should work out of the box.
- (void)methodWithFormat:(NSString*)format, ... {
va_list args;
va_start(args,format);
//loop, get every next arg by calling va_arg(args,<type>)
// e.g. NSString *arg=va_arg(args,NSString*) or int arg=(args,int)
va_end(args);
}
If you want to pass the variable arguments to stringWithFormat:, use something like:
NSString *s=[[[NSString alloc] initWithFormat:format arguments:args] autorelease];
One thing to mention here is that, the first NSString parameter here comes as format, and the other are passed in the variable argument. right? So before entering the for loop, you have one parameter to handle.
- (NSString *) append:(NSString *)list, ...
{
NSMutableString * res = [NSMutableString string];
[res appendString:list];
va_list args;
va_start(args, list);
id arg = nil;
while(( arg = va_arg(args, id))){
[res appendString:arg];
}
va_end(args);
return res;
}
- (void) test_va_arg
{
NSString * t = [self append:#"a", #"b", #"c", nil];
STAssertEqualObjects(#"abc", t, #"");
}
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
You appear to have some misconceptions about variadic functions, exemplified by this approach to counting the variable arguments:
while (va_arg(argList, NSObject *))
{
argCount += 1;
}
That code assumes that the variable arguments have at least one member, that all of them are of type NSObject *, and that the list will be terminated by a null pointer of that type. None of those is guaranteed by the system, and if those assumptions are not satisfied then the behavior of one or more va_arg() invocations will be undefined.
In practice, you can probably get away with actual arguments that are pointers of other types (though formally, the behavior will still be undefined in that case). If the arguments may have non-pointer types, however, then that approach to counting them is completely broken. More importantly, your test cases appear to assume that the system will provide a trailing NULL argument, but that is in no way guaranteed.
If your function relies on the end of the variable argument list being signaled by a NULL argument, then it is relying on the caller to provide one. It is very likely the absence of null termination in your argument lists that gives rise to the behavior you are asking about.
I would like to pass a variable argument list from one method (functionOne) to another (functionTwo). Everything works fine, except that I have not been able to figure out how to setup the va_list in functionTwo in a way where I can access the first parameter in the va_list. Using va_arg advances to the second parameter in the va_list. Thx.
- (void)functionOne:(NSString *)configFiles, ... {
va_list args;
va_start(args, configFiles);
[self functionTwo:args];
va_end(args);
}
- (void)functionTwo:(va_list)files {
NSString *file;
while ((file = va_arg(configFiles, NSString *))) {
...
}
}
The first variadic argument is not the argument passed to va_start – it's the one immediately following it. If you want functionTwo: to have access to the configFiles string, you'll need to pass it in explicitly.
See Technical Q&A QA1405: Variable arguments in Objective-C methods.
Methods that take variable arguments are known as variadic methods.
Keep in mind that the implementation of an Objective-C method is just
a block of code, like a C function. The variadic argument macros
described in the stdarg(3) manual page work the same way in a method
as they do in an ordinary function.
Here's an example of an Objective-C category, containing a variadic
method that appends all the objects in a nil-terminated list of
arguments to an NSMutableArray instance:
#import <Cocoa/Cocoa.h>
#interface NSMutableArray (variadicMethodExample)
// This method takes a nil-terminated list of objects.
- (void)appendObjects:(id)firstObject, ...;
#end
#implementation NSMutableArray (variadicMethodExample)
- (void)appendObjects:(id)firstObject, ... {
id eachObject;
va_list argumentList;
if (firstObject) // The first argument isn't part of the varargs list,
{ // so we'll handle it separately.
[self addObject: firstObject];
// Start scanning for arguments after firstObject.
va_start(argumentList, firstObject);
while (eachObject = va_arg(argumentList, id)) // As many times as we can get an argument of type "id"
[self addObject: eachObject]; // that isn't nil, add it to self's contents.
va_end(argumentList);
}
}
#end
A solution that I use for debugging purposes is like
-(void) debug:(NSString*)format, ... {
if (level < MXMLogLevelDebug) return;
if(format == nil) return;
va_list args, args_copy;
va_start(args, format);
va_copy(args_copy, args);
va_end(args);
NSString *logString = [[NSString alloc] initWithFormat:format
arguments:args_copy];
NSString *funcCaller = #"";
NSArray *syms = [NSThread callStackSymbols];
if ([syms count] > 1) {
funcCaller = [syms objectAtIndex:1];
}
NSString *logMessage = [NSString stringWithFormat:#"%# DEBUG: %#", funcCaller, logString];
NSLog(#"%#",logMessage);
}
The side-effect that this can have is that you have to add a guard on the args to be sure is not NULL.
Hee
Does anybody know how to implement an method in objective c that will take an array of arguments as parameter such as:
[NSArray arrayWithObjects:#"A",#"B",nil];
The method declaration for this method is:
+ (id)arrayWithObjects:(id)firstObj...
I can't seem to make such method on my own. I did the following:
+ (void) doSometing:(id)string manyTimes:(NSInteger)numberOfTimes;
[SomeClass doSometing:#"A",#"B",nil manyTimes:2];
It will give the warningtoo many arguments to function 'doSometing:manyTimes:'
Thanks already.
The ellipsis (...) is inherited from C; you can use it only as the final argument in a call (and you've missed out the relevant comma in your example). So in your case you'd probably want:
+ (void)doSomethingToObjects:(id)firstObject, ...;
or, if you want the count to be explicit and can think of a way of phrasing it well:
+ (void)doManyTimes:(NSInteger)numberOfTimes somethingToObjects:(id)firstObject, ...;
You can then use the normal C methods for dealing with ellipses, which reside in stdarg.h. There's a quick documentation of those here, example usage would be:
+ (void)doSomethingToObjects:(id)firstObject, ...
{
id object;
va_list argumentList;
va_start(argumentList, firstObject);
object = firstObject;
while(1)
{
if(!object) break; // we're using 'nil' as a list terminator
[self doSomethingToObject:object];
object = va_arg(argumentList, id);
}
va_end(argumentList);
}
EDIT: additions, in response to comments. You can't pass the various things handed to you in an ellipsis to another function that takes an ellipsis due to the way that C handles function calling (which is inherited by Objective-C, albeit not obviously so). Instead you tend to pass the va_list. E.g.
+ (NSString *)doThis:(SEL)selector makeStringOfThat:(NSString *)format, ...
{
// do this
[self performSelector:selector];
// make string of that...
// get the argument list
va_list argumentList;
va_start(argumentList, format);
// pass it verbatim to a suitable method provided by NSString
NSString *string = [[NSString alloc] initWithFormat:format arguments:argumentList];
// clean up
va_end(argumentList);
// and return, as per the synthetic example
return [string autorelease];
}
Multiple arguments (also known as an arglist) can only come at the end of a method declaration. Your doSomething method would look something like this:
+ (void)doNumberOfTimes:(NSInteger)numberOfTimes withStrings:(id)firstArg, ...
{
va_list args;
va_start(args, firstArg);
NSString * argString = firstArg;
while (argString != nil)
{
// do something with argString here
argString = va_arg(args, NSString *);
}
va_end(args);
}
To be called as follows:
[SomeClass doNumberOfTimes:2 withStrings:#"A", #"B", nil];
See also: How to create variable argument methods in Objective-C
I think you're after a variadic function. Here's Apple's documentation: http://developer.apple.com/library/mac/qa/qa2005/qa1405.html
Is there any function that does what NSLog does but without the new line at the end?
see this http://borkware.com/quickies/one?topic=NSString
excerpted from that page:
void LogIt (NSString *format, ...)
{
va_list args;
va_start (args, format);
NSString *string;
string = [[NSString alloc] initWithFormat: format arguments: args];
va_end (args);
printf ("%s\n", [string UTF8String]);
[string release];
} // LogIt
just customize the printf to suit your needs
You can use printf(), but the time won't be displayed, and you won't be able to use the "%#" sequence for objects.
That said, you can implement your own logging function, using printf(), and adding support for objects. You will need to know how to deal with C variable arguments.
you can use some trick. use special unicode character like:u00a0
NSLog(#"\n\n\n\u00a0")