Can anyone explain the difference between NSLog and NSLogv? I know NSLog is used to print data in the console. But what is NSLogv?
Suppose you want to write a function similar to NSLog, but which also saves the message to an array in addition to logging it. How would you implement this?
If you write a variadic function void MySpecialLog(NSString *format, ...), someone can call your function just like NSLog — MySpecialLog(#"Hello %#!", name); — but the only way to access the extra arguments beyond format is with a va_list. There's no splat operator in C or Obj-C allowing you to pass them directly to NSLog inside the function.
NSLogv solves this problem by accepting all the additional arguments at once via a va_list. Its signature is void NSLogv(NSString *format, va_list args). You can use it to build your own NSLog wrappers.
Obj-C
void MySpecialLog(NSString *format, ...)
NS_FORMAT_FUNCTION(1, 2)
// The NS_FORMAT_FUNCTION attribute tells the compiler to treat the 1st argument like
// a format string, with values starting from the 2nd argument. This way, you'll
// get the proper warnings if format specifiers and arguments don't match.
{
va_list args;
va_start(args, format);
// Do something slightly more interesting than just passing format & args through...
NSString *newFormat = [#"You've called MySpecialLog()! " stringByAppendingString:format];
NSLogv(newFormat, args);
va_end(args);
}
You can even use the same technique to wrap NSLog with an Obj-C method. (And since -[NSString initWithFormat:] has a similar variant called -initWithFormat:arguments:, you can wrap it too.)
- (void)log:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2)
{
// Similarly to the above, we can pass all the arguments to -initWithFormat:arguments:.
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Why not both?
va_start(args, format);
NSLogv(format, args);
va_end(args);
}
Swift
In Swift, you can do this with a variadic function accepting CVarArg...:
func mySpecialLog(_ format: String, _ args: CVarArg...) {
withVaList(args) {
NSLogv("You've called mySpecialLog()! " + format, $0)
}
}
Generally speaking, a suffix of v means that a function takes a va_list as an argument, instead of a variadic argument list.
This is the case for NSLog and NSLogv:
void NSLog(NSString *format, ...);
void NSLogv(NSString *format, va_list args);
This is useful in certain very specific situations where you need to "wrap" a function that takes variadic arguments. If you need it, you'll know. Otherwise, you can safely ignore it.
NSLog is a varadic function, which means it takes a variable number of arguments. But sometimes, programmers will want to implement their own varadic wrapper function which does something else before calling NSLog.
If NSLog was the only function, that wouldn't be possible because you can't pass a set of varadic arguments (aka a va_list) to another varadic function.
That's why NSLogv exists separately from NSLog, which is just a wrapper that accepts a variable number of arguments and passes them to NSLogv.
Related
I'm developing an app that needs to perform some swizzling.
I'm swizzling a method -(void)m1:(CMAcceleration)a; with another one that I provide.
-(void)newM(id self, SEL _cmd, ...){
va_list args;
va_start(args, _cmd);
//...
NSInteger returnValue=((NSInteger(*)(id,SEL,...))origImp)(self,_cmd,args);
va_end(args);
}
To swizzle it I use:
origImp=method_setImplementation(method, newImp);
I then call it normally like [ClassInstance m1:a];
The thing is, args seems to be filled with garbage when I expected a structure like {name=type...} as described in here.
I need to pass the arguments to the original implementation after doing some operation like NSLog.
Searching the Internet it seems this is a Simulator problem related but I'm not sure and I have no access to a device to confirm this.
Am I doing something wrong or is there a way to fix this?
You are doing it very wrong.
The method signature should match i.e. -(void)newM:(CMAcceleration)a;
and
Method method = class_getInstanceMethod([SomeClass class],#selector(newM:));
IMP newImp = method_getImplementation(method);
origImp=method_setImplementation(method, newImp);
A different way is make C function
void newM(id self, SEL _cmd, CMAcceleration a) {
}
origImp=method_setImplementation(method, (IMP)newM);
I have a variadic function that runs some code on the first argument and then runs NSString initWithFormat:arguments: afterwards, if arguments have been passed in.
+ (NSString *)updatedString:(NSString *)mainString, ...
{
// Some code run on mainString here, outputs finalString
// add format arguments to the final string
va_list args;
va_start(args, mainString);
NSString *formattedString = [[NSString alloc] initWithFormat:finalString arguments:args];
va_end(args);
return formattedString;
}
EDIT: The idea is that the code run on mainString uses a regular expression to find and replace variables within the text. So say the output (finalString) equals "Hello World %# blah blah %#", then it would use the arguments to replace the %# symbols. The issue is that I don't know if the resolved string includes %# symbols or not until the string is resolved.
So, I need to check to see if there are actually any extra arguments, and if there aren't, I don't want to run the initiWithFormat part.
Is there any way to check if args exists first?
No. In C/Objective-C a called variadic function has absolutely no idea about the number and types of arguments passed by the caller. It must figure it out somehow based on other information (e.g. for format arguments, that they match the format specifiers in the format string; or for objects to initialize a Cocoa collection, that the list is terminated with nil) and trust that the caller correctly followed the convention.
I'm trying to write a logging function and have tried several different attempts at dealing with the variadic arguments, but am having problems with all of them. Here's the latest:
- (void) log:(NSString *)format, ...
{
if (self.loggingEnabled)
{
va_list vl;
va_start(vl, format);
NSString* str = [[NSString alloc] initWithFormat:format arguments:vl];
va_end(vl);
NSLog(format);
}
}
If I call this like this:
[self log:#"I like: %#", #"sausages"];
Then I get an EXC_BAD_ACCESS at the NSLog line (there's also a compiler warning that the format string is not a string literal).
However if in XCode's console I do "po str" it displays "I like: sausages" so str seems ok.
Well you are logging format, not str. I wonder if thats the issue...
Assume that we have methods:
-(instancetype) initWithElements:(id)firstElement, ... NS_REQUIRES_NIL_TERMINATION;
+(instancetype) objWithElements:(id)firstElement, ... NS_REQUIRES_NIL_TERMINATION;
I understand, how to work with variable number of arguments in -initWithElements:, but I don't understand how to pass variables from -objWithElements: to -initWithElements:.
I mean, I want to write something like:
+(instancetype) objWithElements:(id)firstElement, ... NS_REQUIRES_NIL_TERMINATION {
return [[[self] initWithElements:ELEMENTS] autorelease];
}
Is it even possible?
The only solution for my problem I see is to store arguments in array and use helper method that will init object with given array.
No, in C (and Objective-C), it is not possible to pass down variadic arguments.
The idiomatic solution is to get yourself an initializer that takes a va_list, make that as the designated initializer, and then call it from every other method. From within a variadic method, this would look like:
- (instancetype)initWithVarargs:(id)first, ...
{
va_list args;
va_start(args, first);
id obj = [self initWithFirst:first VAList:args];
va_end(args);
return obj;
}
and here's a designated initializer that takes a va_list argument:
- (id)initWithFirst:(id)first VAList:(va_list)args
{
id obj;
while ((obj = va_arg(args, id)) != nil) {
// do actual stuff
}
// the return self, etc.
}
j
I would create two versions of each method; one which takes variable arguments (...) and another (where the actual implementation is) using va_list:
-(instancetype) initWithElements:(id)firstElement, ... NS_REQUIRES_NIL_TERMINATION;
-(instancetype) initWithElementsImpl:(va_list)va;
+(instancetype) objWithElements:(id)firstElement, ... NS_REQUIRES_NIL_TERMINATION;
+(instancetype) objWithElementsImpl:(va_list)va;
This will allow the va_list version to simply pass that parameter onto the other va_list method with no work at all.
The var args version (...) will use va_start() et al to create the va_list object to pass to the va_list version of the method.
This question already has answers here:
C Programming: Forward variable argument list
(3 answers)
Closed 9 years ago.
I am confusing about one question.See an example:
-(void)DIYLog:(NSString *)format, ...
{
NSLog(...);
}
It's just an example for fun. We all know that we can't pass "..." as parameters for NSLog. So I'm Curious about passing one "variable parameters" to another.
I already know that params are passed by register or stack, But, The key point is , how can I implement it in Objective-C or C.
I think I make you misunderstand. NSLog is just an example to be explained. Let me make another.
It's about passing params to id objc_msgSend(id self, SEL op, ...).
-(void)DIY_msgSend:(id)target selector:(SEL)op params:(id)param, ...
{
objc_msgSend(target, op, ...);
}
So, the key is, how I can pass those variable parameters to another function which also need variable parameters.
The C va_list type can be created from variadic arguments and passed to functions/methods that accept a va_list parameter. Eg:
- (void)logFormat:(NSString *)format, ...
{
va_list args;
va_start(args, format);
NSLogv(format, args);
va_end(args);
}
However, there is no portable way of passing a va_list to a standard variadic function as you want to do.