I am trying to duplicate NSLog but without all of the unnecessary dates at the beginning. I have tried the following c function (I made myself), but it wont log any other values that are not NSStrings. Please could you tell me how I could do it so that it would log any value?
static void echo(NSString *fmt, ...) {
printf("<<<<<<<%s>>>>>>>", [fmt UTF8String]);
}
To use variadic argument lists in C you need to use a few macros that are defined in the stdarg.h header file that comes with your compiler.
here is a detailed explanation of how to write your own printf
If you just want to pass the arguments to the real printf without further manipulation you can use the vfprintf variant of printf instead but you need to extend the fmt parameter separately:
static void echo(NSString *fmt, ...)
{
va_list args;
NSString *logfmt = [NSString stringWithFormat: #"<<<<<<<%s>>>>>>>", [fmt UTF8String]];
va_start (args, fmt);
vfprintf( stdout, [logfmt UTF8String], args );
va_end (args);
[logfmt release];
}
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.
It is a rather silly question! but in the following function how do you use the remaining arguments passed in:
void NSLog(NSString *format, ...)
{
//here I can use "format" but how can I use the remaining arguments?
}
It is hard to find the answer to this question because I cant search for "..." ?!
By the way that is how NSLog works but I put it here just as an example my question has nothing to do with NSLog.
Use a variable argument list:
void NSLog(NSString *format, ...)
{
va_list ap;
va_start(ap, format);
while( (value = va_arg(args, NSString *) ){
// Do something with value. This is assuming they are all strings
}
// or pass entire list to another function
NSLog_VA( format, ap );
va_end(ap);
}
void NSLog_VA( NSString * format, va_list args )
{
// do something with va_list here
}
Edit:
Since you want a debug only log:
#ifdef DEBUG
#define DebugOnly_Log(format, args...) NSLog(format, ##args)
#else
#define DebugOnly_Log(format, args...) // defined to nothing
#endif
Have a look at stdarg.h
It might look a bit like -
void NSLog(NSString *format, ...)
{
va_list args;
va_start(args,format);
vprintf([format cString], args);
va_end(args);
}
That's called a variadic function, and you can access its arguments by using the macros in stdarg.h.
OK, thanks all, I followed the direction you gave me and this is the solution:
As I understand it, there is no general/portable solution to the problem of receiving variable number of arguments and passing them to another function that accepts variable number of arguments (as mentioned her http://c-faq.com/varargs/handoff.html ).
But I wanted to implement an NSLog alternative (I call it AliLog ) which during debugging just behaves like NSLog but for the release version does nothing (or does something other than writing to consol).
here is my solution:
void AliLog(NSString *format, ...)
{
#ifdef DEBUG
va_list args;
va_start(args, format);
NSLogv(format, args);
va_end(args);
#else
// do nothing! or something that makes sense in a release version
#endif
}
The magic is in NSLogv here.
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")
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);
}