Issues using function with variadic arguments - objective-c

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...

Related

Difference between NSLog and NSLogv

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.

Variadic function: Checking number of arguments

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.

what happens when stringWithFormat format has no type placeholders but has arguments

Suppose I have a method like this
+ (NSString *)stringWithObject:(id)object format:(NSString *)format
{
NSString *string = [NSString stringWithFormat:format, object];
NSLog(#"%#", string);
return string;
}
The object parameter is never nil but the format argument passed into the method might either be
NSString *formatWithPlaceholder = #"object: %#"
or
NSString *formatWithoutPlaceholder = #"No object";
so if formatWithoutPlaceholder is passed into the method as the format argument, the console output is correct and there are no warnings or errors, but will this cause other problems? I feel like there is something wrong about the usage of stringWithFormat: like this.
Nothing bad happens when the format string has fewer format specifiers than the number of parameters supplied: your code is valid for both format strings.
However, when the opposite situation happens (more format specifiers than the parameters) you get undefined behavior.

Print the name of the calling function to the debug log

Objective-C's runtime seems to be rather robust, so I was wondering if there's a way to log the name of the function that called the current function (for debugging purposes).
My situation is that a bunch of things assign to a property, and rather than set a breakpoint and examine the call stack each time, I'd like to just NSLog the name of the function that is setting the property, along with the new value.
So is it possible to get access to the call stack at runtime?
Try this:
#include <execinfo.h>
void *addr[2];
int nframes = backtrace(addr, sizeof(addr)/sizeof(*addr));
if (nframes > 1) {
char **syms = backtrace_symbols(addr, nframes);
NSLog(#"%s: caller: %s", __func__, syms[1]);
free(syms);
} else {
NSLog(#"%s: *** Failed to generate backtrace.", __func__);
}
Great Question. Combining Jeremy's Answer above and what we always use for our debugging, you'll get a nice string like this:
NSArray *syms = [NSThread callStackSymbols];
if ([syms count] > 1) {
NSLog(#"<%# %p> %# - caller: %# ", [self class], self, NSStringFromSelector(_cmd),[syms objectAtIndex:1]);
} else {
NSLog(#"<%# %p> %#", [self class], self, NSStringFromSelector(_cmd));
}
There is no facility for getting the sender. Or, at least, nothing centric to Objective-C.
There are a couple of alternatives, though.
First, you could use GDB commands. Go to the GDB console and do something like:
comm 1
bt
po argName
cont
Alternatively, you could use dtrace. This is hard. Or you could use Instruments which makes dtrace somewhat easier.
Or you could use the backtrace() function. See the backtrace man page (x-man-page://backtrace).
There is a C macro called __PRETTY_FUNCTION__ that will return a C-String with the name of the current function. If you would like to convert that to an NSString for easily printing to NSLog, you can create a macro with
#define NSSTRING_PRETTY_FUNCTION [NSString stringWithCString:__PRETTY_FUNCTION__ encoding:NSASCIIStringEncoding]
I use it all the time in my projects.

Objective C question: method won't return a string

This is my first question so please forgive me if it's obvious. I learned to program in Pascal a few years ago, so my terminology may be off. I've looked at a bunch of postings, but nothing seems to address my basic problem.
I have a lookup table that I use to convert decimals back into fractions. I am calling this method...
-(void) convertToFractions:(float *)float2 aString:(NSMutableString *) myString;
...with this..
[self convertToFractions:&float1 aString:outputFraction];
The idea is that float2 is the decimal that I pass to the method, and myString is the fraction returning.
This runs after the lookup:
myString = [NSString stringWithString:[[decimalInchArray objectAtIndex:x]objectAtIndex:1]];
NSLog(#"myString = %#",myString);
The log shows myString is the correct fraction i.e. myString is correctly displaying the fraction I want to return, but outputFraction is null.
I think it's a pointer issue. I tried *myString, but the compiler throws an error (incompatible types).
Any suggestions are really appreciated.
You want to change the output of your convertToFractions method from void to NSString.
It's returning null because the return type of your method, is void, so it returns nothing.
The return type of an Objective-C method is in the parenthesis, at the beginning of the method name.
Here,s an example, but I don't see where you define convertToString so, I'll use pseudocode.
- (NSString *) convertToFractions:(float *)float2{
NSString *fraction = *some code to lookup fraction from table;*
return fraction;
}
myString = [self convertToFractions:(float *)float2];
EDIT:
As others have suggested, you should give Objective-C a fresh look. I suggest you read this Objective-C Primer written by Apple.
Where do you define your outputFraction? Nowhere in the code above you mention it.
At a guess your conversion method is declared as (void) meaning it will not return anything. If you need it to return the result as a NSString declare it like
-(NSString*) convertToFractions:(float *)float2 aString:(NSMutableString *) myString;
And make sure you return an NSString before reaching the end of the method with
return MyNSStringVariable;
[EDIT]
I can see you are hoping that outputFraction will be returned by your method but that is not the case with Objective-C (not sure about Pascal). You are simply passing outputFraction as a second variable in your method.
So the "right" way of doing it would be to have a method
-(NSString*)convertToFraction:(float*)float2 {
...// Do your float to fraction calculation here
...// Make sure fraction is formatted as NSString
return YourFractionVariable;
}
Then you can assign the return value to a variable of your choice, for instance:
NSString *fraction = [self converToFraction:aFloatNumber];
NSLog (#"fraction is %#", fraction);
Cheers,
Rog
Why not just return the string?
- (NSString*)convertToFractions:(float)float {
//Bla bla do everything
return [myString autorelease]; //Autorelease is for memory-management
}
Btw: You seriously need to read into ObjC. Please don't try to use your old pascal-knowledge on ObjC. It's different, and your knowledge isn't really applicable.
I would recommend buying a book about ObjC or reading some good tutorials for it. (Apple itself has some very good ones)
If you don't want to return NSString from your method as others suggested you can pass a pointer to NSString pointer to your function (the same way you pass NSError** to some standard api callsm e.g. in NSFileManager methods). Your code will look something like:
NSString *outputFraction;
[self convertToFractions:&float1 aString:&outputFraction];
-(void) convertToFractions:(float *)float2 aString:(NSMutableString **) myString{
...
if (myString)
*myString = [NSString stringWithString:[[decimalInchArray objectAtIndex:x]objectAtIndex:1]];
}