This question already has answers here:
Warning: "format not a string literal and no format arguments"
(11 answers)
Closed 9 years ago.
I am getting "Format string is not string literal" warning from following line
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:valist];
I using this in following function
- (void)logMessage:(NSString *)format
level:(LoggingLevel)level
withParameters:(va_list)valist {
if (level >= self.loggingLevel) {
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:valist];
}
Any idea how to fix this ? I am using Xcode 4.6.3
Suppress it using:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
- (void)logMessage:(NSString *)format
level:(LoggingLevel)level
withParameters:(va_list)valist {
if (level >= self.loggingLevel) {
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:valist];
}
#pragma clang diagnostic pop
If you tell the compiler that your method has a format-like argument, using the
NS_FORMAT_FUNCTION macro:
- (void)logMessage:(NSString *)format
level:(LoggingLevel)level
withParameters:(va_list)valist NS_FORMAT_FUNCTION(1,0) {
if (level >= self.loggingLevel) {
NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:valist];
}
}
then
the compiler warning in your method goes away, but
you get a warning if you call your method with a format string which is not a string literal.
Example:
NSString *abc = #"foo %# bar";
[self logMessage:abc level:7 withParameters:NULL];
warning: format string is not a string literal [-Wformat-nonliteral]
[self logMessage:abc level:7 withParameters:NULL];
^~~
ADDED: The same applies to the functions mentioned in your comments.
They should also be "tagged" with NS_FORMAT_FUNCTION:
+ (void)logVeryFineWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2)
{
va_list ap;
va_start(ap, format);
[[self sharedInstance] logMessage:format level:VERY_FINE withParameters:ap];
va_end(ap);
}
+ (void)say:(NSString *)formatstring, ... NS_FORMAT_FUNCTION(1,2)
{
va_list arglist;
va_start(arglist, formatstring);
// This is not needed:
// NSString *litralString = [NSString stringWithFormat:#"%#",formatstring];
NSString *statement = [[NSString alloc] initWithFormat:formatstring arguments:arglist];
va_end(arglist);
[ModalAlert ask:statement withCancel:#"Okay" withButtons:nil];
}
The format string should be #"something with some format specifiers to be replaced by the varargs" just like in stringWithFormat: or in NSLog
Remember that # has a special meaning everywhere in Objective-C. It is a sigil indicating a compiler directive. The compiler will know what to do with the following token ( expanding it or converting contents, sometimes looking for a matching paired directive like #end
It may be the right solutio for you just to suppress the warning, as #trojanfoe suggests. It is just that the warning is there for a reason.
In the event that your format string does not match the number and/or type of your arguments, you application may break on runtime. When you provide the format as string literal such as #"My output is: %#" ... then you enable the compiler to check upon types and numer of arguments.
There may be good reasons for using a string varaible as format. However, in that case you are on your own when it comes to avoiding this type of error.
Anyway, I'd suggest re-thinking your algorithm in favor of using string literals.
Related
When giving a NSString a value using = #"", what exactly is that shorthand for? I noticed that, all other things being equal in an application, global NSStrings made with #"" don't need retain to be used outside of the methods that gave them that value, whereas NSStrings given a value in any other way do.
I have found this question utterly unsearchable, so I thank you for your time.
A related answer: Difference between NSString literals
In short, when writing #"" in the global scope of your program, it does the same as writing "": The literal string you specified will be embedded in your binary at compile time, and can thus not be deallocated during runtime. This is why you don't need release or retain.
The # just tells the compiler to construct a NSString object from the C string literal. Mind you that this construction is very cheap and probably heavily optimized. You can follow the link and see this example:
NSString *str = #"My String";
NSLog(#"%# (%p)", str, str);
NSString *str2 = [[NSString alloc] initWithString:#"My String"];
NSLog(#"%# (%p)", str2, str2);
Producing this output:
2011-11-07 07:11:26.172 Craplet[5433:707] My String (0x100002268)
2011-11-07 07:11:26.174 Craplet[5433:707] My String (0x100002268)
Note the same memory addresses
EDIT:
I made some test myself, see this code:
static NSString *str0 = #"My String";
int main(int argc, const char * argv[]) {
NSLog(#"%# (%p)", str0, str0);
NSString *str = #"My String";
NSLog(#"%# (%p)", str, str);
NSString *str2 = [[NSString alloc] initWithString:#"My String"];
NSLog(#"%# (%p)", str2, str2);
return 0;
}
Will produce this output:
2015-12-13 21:20:00.771 Test[6064:1195176] My String (0x100001030)
2015-12-13 21:20:00.772 Test[6064:1195176] My String (0x100001030)
2015-12-13 21:20:00.772 Test[6064:1195176] My String (0x100001030)
Also, when using the debugger you can see that the actual object being created when using literals are in fact __NSCFConstantString objects.
I think the related concept to that is called Class Clusters
Objective-C is a super-set of C, which means you should be able to write C code in there and have it work. As a result, the compiler needs a way to distinguish between a C string (an old-school series of bytes) and an Objective-C NSString (Unicode support, etc). This is done using the # symbol, such as #"Hello".
#"Hello" can't be released because it's a literal string – it's been written into the program when it was built, versus being assigned at run-time.
String allocated using the literal syntax i.e # reside as c strings in the data segment of the executable. They are allocated only once at the time of program launch and are never released until the program quits.
As an exercise you can try this:
NSString *str1 = #"Hello";
NSString *str2 = #"Hello";
NSLog("Memory Address of str1 : %p", str1);
NSLog("Memory Address of str2 : %p", str2);
both the log statements will print the same address which means literals are constant strings with lifetimes same as that of the program.
C# has syntax that allows you to specify the argument index in a string format specifier, e.g.:
string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age);
You can use arguments more than once and also omit arguments that are provided from being used. Another question mentions the same formatting for C/C++ in the form of %[index]$[format], e.g. %1$i. I have not been able to get NSString to fully respect this syntax, because it does behave well when omitting arguments from the format. The following does not work as expected (EXC_BAD_ACCESS because it attempts to dereference the age parameter as a NSObject*):
int age = 23;
NSString * name = #"Joe";
NSString * message = [NSString stringWithFormat:#"Age: %2$i", name, age];
The positional arguments are respected only if there are no missing arguments from the format (which seems to be an odd requirement):
NSString * message = [NSString stringWithFormat:#"Age: %2$i; Name: %1$#", name, age];
All these calls work properly in OS X:
printf("Age: %2$i", [name UTF8String], age);
printf("Age: %2$i %1$s", [name UTF8String], age);
Is there a way of accomplishing this using NSString in Objective-C / Cocoa? It would be useful for localization purposes.
NSString and CFString support reorderable/positional arguments.
NSString *string = [NSString stringWithFormat: #"Second arg: %2$#, First arg %1$#", #"<1111>", #"<22222>"];
NSLog(#"String = %#", string);
Also, see the documentation at Apple: String Resources
The following code fixes the bug specified in this issue. It is a workaround and renumbers the placeholders to fill gaps.
+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments
{
NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count];
NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format];
NSString *placeHolderFormat = #"%%%d$";
int actualPlaceholderIndex = 1;
for (int i = 1; i <= arguments.count; ++i) {
NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i];
if ([format rangeOfString:placeHolder].location != NSNotFound) {
[filteredArguments addObject:[arguments objectAtIndex:i - 1]];
if (actualPlaceholderIndex != i) {
NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex];
[correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];
[replacementPlaceHolder release];
}
actualPlaceholderIndex++;
}
[placeHolder release];
}
if (filteredArguments.count == 0) {
//No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported.
[filteredArguments setArray:arguments];
}
NSString* result;
if (filteredArguments.count == 0) {
//Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string
result = [NSString stringWithString:format];
} else {
char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]);
[filteredArguments getObjects:(id *)argList];
result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease];
free(argList);
}
[filteredArguments release];
[correctedFormat release];
return result;
}
After doing more research, it appears Cocoa respects positional syntax in printf. Therefore an alternate pattern would be:
char msg[512] = {0};
NSString * format = #"Age %2$i, Name: %1$s"; // loaded from resource in practice
sprintf(msg, [format UTF8String], [name UTF8String], age);
NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];
However, it would be nice if there was an implementation of this on NSString.
I have two methods in my class called TestClient
-(void)log:(NSString *)logMessage, ...
{
va_list ap;
va_start(ap, logMessage);
[self log:logMessage withParameters:ap];
va_end(ap);
}
-(void)log:(NSString *)logMessage withParameters:(va_list)valist
{
NSString *formattedString = [[NSString alloc] initWithFormat:logMessage arguments:valist]; //Crashes here
[self callMethod:#"log" withParams:formattedString, nil]; //Calls my method.
}
Here is my unit test:
-(void)testWtfCondition
{
int test = 1;
NSString *test2 = #"wtf";
[proxy log:#"This is a test: %# %#",test, test2];
}
My unit test crashes at the NSString formattedString line with EXEC_BAD_ACCESS. Am I doing something wrong with string formatting or varargs here? Is it because I'm trying to do a format with an int?
%i (or %d) - if you want to print integers
[proxy log:#"This is a test: %i %#",test, test2];
%# - will call [description] on class you want to print. For build-in variable types like float, int you need to cannot use it, since they are not objects.
For more string format, you can check out String Programming Guide.
I'm using a macro to simplify returning localised strings, like so:
#define GetLocalStr(key, ...) \
[NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:#"" table:nil], ##__VA_ARGS__]
Basically, if you have an entry in a localisation Strings file like "name" = "My name is %#";, calling
GetLocalStr( #"name", #"Foo" );
will return the NSString #"My name is Foo"
When I run it however, like:
NSString * str = GetLocalStr( #"name", #"Foo" );
I get the "format string is not a string literal" warning. Even following the advice of the other answers on SO about this warning and replacing it with:
NSString * str = [NSString stringWithFormat:#"%#", GetLocalStr( #"name", #"Foo" )];
I still get the warning, and besides, it kind of defeats the point of the macro making life easier.
How can I get rid of the warning short of wrapping all the GetLocalStr calls in #pragma suppressors?
Edit 27/08
After running through CRD's answer and doing some more tests, it seems like I made a bad assumption on the error. To clarify:
Localisation Strings file:
"TestNoArgs" = "Hello world";
"TestArgs" = "Hello world %#";
Code:
NSString * str1 = GetLocalStr( #"TestNoArgs" ); // gives warning
NSString * str2 = GetLocalStr( #"TestArgs", #"Foo" ); // doesn't give warning
The majority of my translations take no arguments, and those were the ones giving the warning, but I didn't make the connection until I read through CRD's answer.
I changed my single macro to two, like so:
#define GetLocalStrNoArgs(key) \
[[NSBundle mainBundle] localizedStringForKey:key value:#"" table:nil]
#define GetLocalStrArgs(key, ...) \
[NSString stringWithFormat:[[NSBundle mainBundle] localizedStringForKey:key value:#"" table:nil], ##__VA_ARGS__]
And if I call each one separately, there's no warnings.
I'd like GetLocalStr to expand to either GetLocalStrNoArgs or GetLocalStrArgs depending on if any arguments were passed or not, but so far I've been having no luck (macros are not my strong suit :D).
I'm using sizeof(#__VA_ARGS__) to determine if there's any arguments passed - it stringifys the arguments, and if the size is 1, it's empty (i.e. `\0'). Perhaps it's not the most ideal method, but it seems to work.
If I rewrite my GetLocalStr macro to:
#define GetLocalStr(key,...) (sizeof(#__VA_ARGS__) == 1) ? GetLocalStrNoArgs(key) : GetLocalStrArgs(key,##__VA_ARGS__)
I can use it, but I still get warnings everywhere it's used and there's no arguments passed, while something like
#define GetLocalStr( key,...) \
#if ( sizeof(#__VA_ARGS__) == 1 ) \
GetLocalStrNoArgs(key) \
#else \
GetLocalStrArgs(key,##__VA_ARGS__)
won't compile. How can I get my GetLocalStr macro to expand properly?
The Clang & GCC compilers check that format strings and the supplied arguments conform, they cannot do this if the format string is not a literal - hence the error message you see as you are obtaining the format string from the bundle.
To address this issue there is an attribute, format_arg(n) (docs), to mark functions which take a format string; alter it in some way without changing the actual format specifiers, e.g translate it; and then return it. Cocoa provides the convenient macro NS_FORMAT_ARG(n) for this attribute.
To fix your problem you need to do two things:
Wrap up the call to NSBundle in a function with this attribute specified; and
Change your "key" to include the format specifiers.
Second first, your strings file should contain:
"name %#" = "My name is %#"
so the key has the same format specifiers as the result (if you need to reorder the specifiers for a particular language you use positional format specifiers).
Now define a simple function to do the lookup, attributing it as a format translation function. Note we mark it as static inline, using the macro NS_INLINE as a hint to the compiler to both inline it into your macro expansion; the static allows you to include it in multiple files without symbol clashes:
NS_INLINE NSString *localize(NSString *string) NS_FORMAT_ARGUMENT(1);
NSString *localize(NSString *string)
{
return [[NSBundle mainBundle] localizedStringForKey:string value:#"" table:nil];
}
And your macro becomes:
#define GetLocalStr(key, ...) [NSString stringWithFormat:localize(key), ##__VA_ARGS__]
Now when you:
GetLocalStr(#"name %#", #"Foo")
You will get both the localised format string and format checking.
Update
After Greg's comment I went back and checked - I had reproduced your error and so assumed it was down to a missing attribute. However as Greg points out localizedStringForKey:value:table: already has the attribute, so why the error? What I had absentmindedly done in reproducing your error was:
NSLog( GetLocalStr( #"name %#", #"Foo" ) );
and the compiler pointed at the macro definition and not that line - I should have spotted the compiler was misleading me.
So where does that leave you? Maybe you've done something similar? The key is that a format string must either be a literal or the result of a function/method attributed as a format translating function. And don't forget, you must also had the format specifier to your key as above.
Update 2
After your additional comments what you need to use is function, rather than a macro, along with the format attribute, for which Cocoa provides the convenient NS_FORMAT_FUNCTION(f,a) macro. This attribute informs the compiler that the function is a formatting one, the value of f is the number of the format string and a is the number of the first argument to the format. This gives the function declaration:
NSString *GetLocalStr(NSString *key, ...) NS_FORMAT_FUNCTION(1,2);
and the definition (assuming ARC):
NSString *GetLocalStr(NSString *key, ...)
{
va_list args;
va_start(args, key);
NSString *format = [[NSBundle mainBundle] localizedStringForKey:key value:#"" table:nil];
NSString *result = [[NSString alloc] initWithFormat:format arguments:args];
va_end (args);
return result;
}
(which is essentially the same as #A-Live's).
Uses of this will be checked appropriately, for example:
int x;
...
NSString *s1 = GetLocalStr(#"name = %d", x); // OK
NSString *s2 = GetLocalStr(#"name = %d"); // compile warning - More '%" conversions than data arguments
NSString *s3 = GetLocalStr(#"name", x); // compile warning - Data argument not used by format string
NSString *s4 = GetLocalStr(#"name"); // OK
This variant produces no warnings (as there's always a va_list):
NSString* GetLocalStr1(NSString *formatKey, ...) {
va_list ap;
va_start(ap, formatKey);
NSString * format = [[NSBundle mainBundle] localizedStringForKey:formatKey value:#"" table:nil];
NSString *result = [[NSString alloc] initWithFormat:format arguments:ap];
va_end (ap);
return [result autorelease];
}
...
__unused NSString * str = GetLocalStr1( #"name", #"Foo" );
__unused NSString * str1 = GetLocalStr1( #"TestNoArgs" );
__unused NSString * str2 = GetLocalStr1( #"TestArgs", #"Foo" );
NSLog(#"%#", str);
NSLog(#"%#", str1);
NSLog(#"%#", str2);
Result:
my name is Foo
TestNoArgs
Hello world Foo
It doesn't answer the question exactly but might help you to avoid warnings until the solution is found.
This question already has answers here:
Get last 2 characters of a string?
(2 answers)
Closed 9 years ago.
This seems to be what I'm looking for but in reverse. I would like the string to extract from the right not from the left.
The example extracting from the left is given:
NSString *source = #"0123456789";
NSString *firstFour = [source substringToIndex:4];
Output: "0123"
I'm looking for a version of the below that works from the right (what is below doesn't work)
NSString *source = #"0123456789";
NSString *lastFour = [source substringToIndex:-4];
Output: "6789"
the [source substringFromIndex:6]; won't work because sometimes I will get an answer that is 000123456789 or 456789 or 6789. In all cases I just need the last 4 characters from the string so that I can convert it to a number.
there must be a better way than a bunch of if else statements?
As you are not sure, about the length of the string, so you must check it before extracting like this:
NSString *source = #"0123456789";
NSNumber *number;
if (source.length>=4) {
NSString *lastFour=[source substringFromIndex:source.length-4];
number=#([lastFour integerValue]); //and save it in a number, it can be int or NSInteger as per your need
}
NSLog(#"%#",number);
Also if you want a quick method that you need to call several times, create a category :
#implementation NSString (SubstringFromRight)
-(NSString *)substringFromRight:(NSUInteger)from{
if (self.length<from) {
return nil;
}
return [self substringFromIndex:self.length-from];
}
#end
And use it as :NSLog(#"%#",[source1 substringFromRight:4]);
NSString *source = #"0123456789";
NSString *newString = [source substringFromIndex:[source length] - 4];
NSLog(#"%#",newString);
replace
NSString *lastFour = [source substringToIndex:-4];
with
NSString *lastFour = [source substringFromIndex:[source length] - 4];
which returns you the last 4 characters of your original string string in lastFour string.
You can use the following code to get last 4 characters from your string.
NSString *last4Characters = [source substringFromIndex:(source.length - 4)];
NSLog(#"Last 4 Characters:%#",last4Characters);
last4Characters=nil;
Please let me know if any issue.