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.
Related
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.
I have a method with variable arguments, the argument may be int, NSString or other classes.I want handle each argument rely on one's type.So I am trying to get the type of each argument, but I failed.
Here is my code :
.h file
-(void)handleArgs:(NSString *)firstArg, ...;
.m file
-(void)handleArgs:(NSString *)firstArg, ...
{
va_list argList;
va_start(argList, firstArg);
// get each object and get its type and handle it by type
va_end(argList);
}
I will call it like this :
NSArray *list = [NSArray alloc] initWithObjects:#"item1", #"item2", #"item3", nil];
[self handleArgs:#"test", 100, #"arg2", list, nil];
So how to implement this method?
You can't. Varargs does not preserve type information. You will either need to have the first argument provide type information for the rest (like printf does) or you will have to make all the arguments compatible types. (It is possible to have arguments of different object types, though, since all objects share the static type id.)
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...
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.
I am thinking about some details regarding
stringByReplacingOccurrencesOfString:withString: in NSString class
According to document.
It returns a new string in which all occurrences of a target string in the receiver are replaced by another given string.
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
Parameters
target
The string to replace.
replacement
The string with which to replace target.
my question is that if the replacement string is not found in the target string. If there would be some side effects to call this function.
Thanks
For example
NSString *myString = [NSString stringWithString:#"Hello my string"];
NSString *myReplacementString = [myString stringByReplacingOccurrencesOfString:#"NOTFOUND" withString#"Any side effect?"];
NSLog(#"my replacement string is %#", myReplacementString);
That function has no side effects, and if the search string isn't found in the receiver, the receiver will be returned unaltered.
There are no side effects: if the substring to be replaced doesn't occur in the original string, you end up with an NSString identical to the one you started with.
Indeed, (this is an implementation detail and you shouldn't depend on it, but) you don't even end up with a copy of the original string, but a pointer to the same exact string.
myString, target and replacement aren't modified whether a match if found or not:
The method doesn't change the string myString in place but creates a new string that will contain the result.
target and replacement are parameters of the method and their values don't get changed.
myString, target and replacement are immutable strings.
There are no errors or exceptions if no match is found.