String formatting, varargs and EXEC_BAD_ACCESS - ios

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.

Related

Recursive method Objective-C

I'm checking is first letter of string is 0, if it is remove it and call again method to check is there is still 0. I've debugged this and it seems like when it accomplish number without 0, it goes backwards. Code:
-(NSString *)deleteZerosOnFirst:(NSString *)card
{
NSString *firstLetter = [card substringToIndex:1];
if ([firstLetter isEqualToString:#"0"]) {
card = [card substringFromIndex:1];
[self deleteZerosOnFirst:card];
NSLog(#"CARD: %#", card);
return card;
}
else {
NSLog(#"CARD: %#", card);
return card;
}
}
The main problem is that you're not using the result of the recursion. The line of code where you call yourself should say this:
card = [self deleteZerosOnFirst:card];
Also, you're calling deleteZerosOnFirst before you do the NSLog. Reverse the order of these two lines. That will at least give you your debug output in the right sequence.
Here's your recursive call:
[self deleteZerosOnFirst:card];
That doesn't modify the string that card references. It creates and returns a new string. You're ignoring the returned string. You want this:
card = [self deleteZerosOnFirst:card];
But this is really a lot simpler:
#implementation NSString (withoutLeadingZeroes)
- (NSString *)withoutLeadingZeroes {
NSString *s = self;
while ([s hasPrefix:#"0"]) {
s = [s substringFromIndex:1];
}
return s;
}
#end

how to pass the uncertain parameters form one method to another on Objective-C? [duplicate]

i have googled and came to know that how to use the variable arguments. but i want to pass my variable arguments to another method. i m getting errors. how to do that ?
-(void) aMethod:(NSString *) a, ... {
[self anotherMethod:a];
// i m doing this but getting error. how to pass complete vararg to anotherMethod
}
AFAIK ObjectiveC (just like C and C++) do not provide you with a syntax that allows what you directly have in mind.
The usual workaround is to create two versions of a function. One that may be called directly using ... and another one called by others functions passing the parameters in form of a va_list.
..
[obj aMethod:#"test this %d parameter", 1337);
[obj anotherMethod:#"test that %d parameter", 666);
..
-(void) aMethod:(NSString *)a, ...
{
va_list ap;
va_start(ap, a);
[self anotherMethod:a withParameters:ap];
va_end(ap);
}
-(void) anotherMethod:(NSString *)a, ...
{
va_list ap;
va_start(ap, a);
[self anotherMethod:a withParameters:ap];
va_end(ap);
}
-(void) anotherMethod:(NSString *)a withParameters:(va_list)valist
{
NSLog([[[NSString alloc] initWithFormat:a arguments:valist] autorelease]);
}
You cannot pass variadic arguments directly. But some of these methods provide an alternative that you can pass a va_list argument e.g.
#include <stdarg.h>
-(void)printFormat:(NSString*)format, ... {
// Won't work:
// NSString* str = [NSString stringWithFormat:format];
va_list vl;
va_start(vl, format);
NSString* str = [[[NSString alloc] initWithFormat:format arguments:vl] autorelease];
va_end(vl);
printf("%s", [str UTF8String]);
}
Have you considered setting up your arguments in either an array or dictionary, and coding conditionally?
-(void) aMethodWithArguments:(NSArray *)arguments {
for (id *object in arguments) {
if ([object isKindOfClass:fooClass]) {
//handler for objects that are foo
[self anotherMethod:object];
}
if ([object isKindOfClass:barClass]) {
//and so on...
[self yetAnotherMethod:object];
}
}
}
I think you could use macros to achieve same thing.
Let's say you wanna pass aMethod's variable arguments to another
-(void) aMethod:(NSString *) a, ... {
}
You could define your another 'method' using macro though it is not a real method:
#define anotherMethod(_a_,...) [self aMethod:_a_,##__VA_ARGS__]
This is my solution.

Ways to build NSStrings in Objective-C? [duplicate]

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.

va_args() causing EXC_BAD_ACCESS

I'm getting a EXC_BAD_ACCESS when using va_args (iOS 7, Xcode 5.1.1, ARC on):
// ...
int val = sqlIntQuery(#"format_string", #"arg1"); // <-- does not work
int val = sqlIntQuery(#"format_string", #"arg1", nil); // <-- this works
// ...
- (int)sqlIntQuery:(NSString *)format, ...
{
va_list args;
va_start(args,format);
__unsafe_unretained id eachObject;
NSMutableArray *arguments = [NSMutableArray array];
while ( (eachObject = va_arg(args, id)) != nil ) { // <-- crash on 2nd loop
[arguments addObject:eachObject];
}
va_end(args);
// ... process 'arguments'
return 5; // return a computed intValue
}
If I put a "break;" at the end of the loop (because I only have one argument), or add "nil" as a last argument, there's no crash, but I don't think I should have to add the "nil". I suspect an ARC issue, but I'm using __unsafe_unretained, as suggested elsewhere on SO. (Is there a way I can push "nil" into to args?)
What's causing the failure on the second time through the loop?
EDIT Aug 6: My Solution:
The accepted solution by maddy pushed me in the right direction when he mentioned "the number of format specifiers." My format argument has a '?' placeholder for each argument, so I just count those. So, for the record:
- (int)sqlIntQuery:(NSString *)format, ...
{
int numberOfArgs = [format componentsSeparatedByString:#"?"].count - 1; // <<-- this solved my problem
va_list args;
va_start(args,format);
NSMutableArray *arguments = [NSMutableArray array];
while ( numberOfArgs-- ) {
id eachObject = va_arg(args, id);
[arguments addObject:eachObject];
}
va_end(args);
FMResultSet *rs = [db executeQuery:format withArgumentsInArray:arguments];
[rs next];
int ret = [rs intForColumnIndex:0];
[rs close];
return ret;
}
It's a double-wrapper. My routine is a wrapper around FMDB, which is itself a wrapper for SQLite.
You need the nil or some other way to know how many arguments to grab. va_list has no magic way to know when to stop.
Things like stringWithFormat: don't need a nil because it determines the number of arguments based on the number of format specifiers (which is why they need to match or your code goes boom). Note how methods like NSDictionary dictionaryWithObjectsAndKeys: requires a nil terminator or UIAlertView initWithTitle... needs the nil terminator for the otherButtonTitles parameter.
What you could do is make use of the following NSString method:
- (int)sqlIntQuery:(NSString *)format, ... {
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
// do whatever
va_end(args);
return 5;
}
Of course this solutions assumes you wish to build a string from format and the variable arguments to your method.
If you really do need to populate an array then you will need to pass a nil terminator when you call your sqlIntQuery method.

Warning: Format string is not a string literal [duplicate]

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.

Resources