Using va_list and getting EXC_BAD_ACCESS - iphone

Similar to how NSLog takes variable argument list, I want to create my own method.
I have my method declared like this but when I try to access the 'args' variable, I get an EXEC_BAD_ACCESS. What is that I'm not doing correctly here?
- (void)info:(NSString *)formatString, ...
{
va_list args;
va_start(args, formatString);
NSLog(#"formatString value: %#", formatString);
// The following line causes the EXEC_BAD_ACCESS
NSLog(#"args value: %#", args);
// This is what I'm trying to do:
NSLog(formatString, args);
va_end(args);
}
I was following the 'va_list in Cocoa' section from this blog:
http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html

There are a couple of mistakes in your code. Firstly, args cannot be printed directly as is. It is a reference to several arguments, and trying to print it using NSLog("%#") will not work. What you can do is use NSLogv() to print it (e.g. NSLogv(format, args);) instead.
Or you can do what I do, and use this function:
void print (NSString *format, ...) {
va_list args;
va_start(args, format);
fputs([[[[NSString alloc] initWithFormat:format arguments:args] autorelease] UTF8String], stdout);
va_end(args);
}

The "%#" format directive takes an argument, interprets it as an Objective-C object and sends it the "description" selector. That needs to return a NSString which is printed.
So you code is trying to execute ‘[args description]‘, but args is not a Objective-C object, it's of type ‘va_list‘. Hence the exception.
See your link, the implementation of "setContentByAppendingStrings:" shows how do get the arguments out of your va_list.

Morning lads,
I just have been confronted with the very similar issue. Here is what I was doing:
+ (void) l:(D3LogLevel)p_logLevel s:(NSString *)p_format, ...
{
if (p_logLevel >= logLevel) {
va_list v_args;
va_start(v_args, p_format);
NSLog(#"[%d] %#", p_logLevel, [NSString stringWithFormat:p_format, v_args]);
va_end(v_args);
}
}
Which, as DarkDust here has described accurately, uses v_args as an Objective-C object when it is not. Here is the culprit call:
[NSString stringWithFormat:p_format, v_args]
Hence the modification to take a va_list:
[[NSString alloc] initWithFormat:p_format arguments:v_args]
Making use of the appropriate method initWithFormat which signature is:
- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
We can see that the type is right and everything becomes crystal clear. The full rewritten method is then:
+ (void) l:(D3LogLevel)p_logLevel s:(NSString *)p_format, ...
{
if (p_logLevel >= logLevel) {
va_list v_args;
va_start(v_args, p_format);
NSLog(#"[%d] %#", p_logLevel, [[NSString alloc] initWithFormat:p_format arguments:v_args]);
va_end(v_args);
}
}
It works like a charm!

Related

NSString (with method call : stringWithContentsOfFile)

NSBundle *bundle = [NSBundle bundleForClass : [self class]];
NSString *f_path = nil;
if ((f_path = [bundle pathForResource : #"about_screen" ofType:#"html" inDirectory:#"html"]) != nil)
{
NSLog(#" f_path found" );
NSString *ns_string = [NSString stringWithContentsOfFile : f_path
encoding : NSUTF8StringEncoding
error : NULL
];
NSLog(#" string = %#", ns_string);
}
else
{
NSLog(#" f_path not found" );
}
// *** if the following assignment is commented off, there will be an error. ***
ns_string =
#"<!DOCTYPE html PUBLIC \"-//IETF//DTD HTML 2.0//EN\"><HTML><HEAD><TITLE>minimal test </TITLE></HEAD><BODY bgcolor = \"silver\"><H1>Hi</H1><P>This is very minimal \"hello world\" test document.</P> </BODY></HTML>";
[web_view loadHTMLString : ns_string baseURL : nil];
Consider the above code segment.
For testing purpose I have set the content of the file "about_screen.html" to be the same as the string assigned to ns_string in the code above. So if "NSString stringWithContentsOfFile" works as expected, the ns_string "in-line" assignment can be commented off without making any difference.
My problem is : the in-line assignment works as expected but without it, there will be a run time error.
The error is :
-[About_Screen dataUsingEncoding:]: unrecognized selector sent to instance 0x6eddf70'
Also note that the statement :
NSLog(#" string = %#", ns_string);
always outputs the correct string, so the embedded html file is found and is being read correctly.
Hope that somebody familiar with this could help.
This answer actually comes from #bbamhart's comment. I thought the problem was due to the NSUTF8StringEncoding parameter since it shows up frequently from googling on related topics.
The real cause of the problem is about variable scope. ns_string should not be declared in any conditional blocks. (since the variable would then normally not be accessible outside the block.)
The bottom-line solution is simply move the declaration out of the block.

Restricting what is passed into a method

Right lets say I have a method something like :
- (void)doStuff:(NSString *)doStuffWith;
Can I make it so that doStuffWith will only accept certain words like lets say "DoSomething1" and "DoSomething2", so when i call it like :
[self doStuff:#"DoSomething1"];
it will run but if I call it like :
[self doStuff:#"HelloWorld"];
it will give a warning or something?
You should use an enum, like:
typedef enum {
MyStuffOne,
MyStuffTwo,
MyStuffThree
} MyStuff;
- (void)doStuff:(MyStuff)stuff;
thus you will be able to pass only "MyStuff" (MyStuffOne, MyStuffTwo, MyStuffThree)... these are integers and if you want to play with strings, in your method you have to do something like:
NSString *string;
switch (stuff)
{
case MyStuffOne:
string = #"StuffOneString";
break;
default:
...
}
If you need to limit amount of possible values, you should use enumeration data type instead of NSString
Why not just add an if statement into the method like this
- (void)doStuff:(NSString *)doStuffWith{
if([doStuffWith isEqualToString:#"DoSomething1"]){
//do whatever you want here
}else{
//add your warning here
}
}
That should work fine
You could create a method that checks if a word is valid and then assert that method returns true. That would then crash the app if a programmer ever called the method with a bad string, but wouldn't really help if users are able to enter strings themselves. Also, if you use the default project settings, assertions only happen when building with the Debug configuration.
For example:
static NSSet* __validStrings = nil;
- (BOOL)checkString:(NSString*)string
{
if( [string length] == 0 ) return NO;
static dispatch_once_t token;
dispatch_once(&token, ^{
// build the list of valid words once, or load from a plist or something
// if they are very large or change often
NSArray* validWords = [NSArray arrayWithObjects:#"valid", #"doSomething", #"etc.", nil];
__validStrings = [[NSSet alloc] initWithArray:validWords];
});
return [__validStrings containsObject:string];
}
// your doStuff implementation
- (void)doStuff:(NSString*)doStuffWith
{
// This will crash the program and give you debugging information if doStuffWith
// is not in your string list
NSAssert1( [self checkString:doStuffWith], #"invalid string: %#", doStuffWith );
// continue on with your method implementation...
}

Write stderr on iPhone to both file and console

I'm following the suggestion in the answer here for redirecting NSLog output on an iOS device to a file, which works great. The problem is that it no longer shows up in the console on the device. What I'd really like is a way to tee the stderr stream to both the console and the file. Does anyone have an idea how to do that?
I found an acceptable answer on another thread (NSLog() to both console and file).
The solution provided there is to only redirect to a file if a debugger is not detected, like this:
if (!isatty(STDERR_FILENO))
{
// Redirection code
}
Thanks to Sailesh for that answer.
Once you freopen() the file descriptor, you can read from it and do as you please with the data. Some ideas from this will be useful to you.
You could either write it back out to stdout, or try to write directly to /dev/console. I've never tried to open /dev/console on an iPhone, but I'm guessing it's possible despite being outside of the sandbox. I'm not sure how the app review process will treat it.
Or you can redirect to a TCP socket and view on a remote telnet client. No need for XCode this way!
Basically:
Create a standard C function which calls an Obj-C static method:
void tcpLogg_log(NSString* fmt, ...)
{
va_list args;
va_start(args, fmt);
[TCPLogger tcpLog:fmt :args];
va_end(args);
}
The static Obj-C method:
(void)tcpLog:(NSString*)fmt :(va_list)args
{
NSLogv(fmt, args);
if(sharedSingleton != nil && sharedSingleton.socket != nil)
{
NSString *time = [sharedSingleton.dateFormat stringFromDate:[NSDate date]];
NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args];
mach_port_t tid = pthread_mach_thread_np(pthread_self());
NSString *str = [NSString stringWithFormat:#"%#[%X]: %#\r\n", time, tid, msg];
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
[sharedSingleton.socket writeData:data
withTimeout:NETWORK_CLIENT_TIMEOUT_PERIOD
tag:0];
}
}
Then in your .pch file, add the following lines to override NSLog()
define NSLog(...) tcpLogg_log(__VA_ARGS__);
void tcpLogg_log(NSString* fmt, ...);
Of course more details are required to handle the TCP Socket. Working source code is available here:
https://github.com/driedler/iOS-TCP-Logger/wiki/About

NSString stringWithFormat swizzled to allow missing format numbered args

Based on this SO question asked a few hours ago, I have decided to implement a swizzled method that will allow me to take a formatted NSString as the format arg into stringWithFormat, and have it not break when omitting one of the numbered arg references (%1$#, %2$#)
I have it working, but this is the first copy, and seeing as this method is going to be potentially called hundreds of thousands of times per app run, I need to bounce this off of some experts to see if this method has any red flags, major performance hits, or optimizations
#define NUMARGS(...) (sizeof((int[]){__VA_ARGS__})/sizeof(int))
#implementation NSString (UAFormatOmissions)
+ (id)uaStringWithFormat:(NSString *)format, ... {
if (format != nil) {
va_list args;
va_start(args, format);
// $# is an ordered variable (%1$#, %2$#...)
if ([format rangeOfString:#"$#"].location == NSNotFound) {
//call apples method
NSString *s = [[[NSString alloc] initWithFormat:format arguments:args] autorelease];
va_end(args);
return s;
}
NSMutableArray *newArgs = [NSMutableArray arrayWithCapacity:NUMARGS(args)];
id arg = nil;
int i = 1;
while (arg = va_arg(args, id)) {
NSString *f = [NSString stringWithFormat:#"%%%d\$\#", i];
i++;
if ([format rangeOfString:f].location == NSNotFound) continue;
else [newArgs addObject:arg];
}
va_end(args);
char *newArgList = (char *)malloc(sizeof(id) * [newArgs count]);
[newArgs getObjects:(id *)newArgList];
NSString* result = [[[NSString alloc] initWithFormat:format arguments:newArgList] autorelease];
free(newArgList);
return result;
}
return nil;
}
The basic algorithm is:
search the format string for the %1$#, %2$# variables by searching for %#
if not found, call the normal stringWithFormat and return
else, loop over the args
if the format has a position variable (%i$#) for position i, add the arg to the new arg array
else, don't add the arg
take the new arg array, convert it back into a va_list, and call initWithFormat:arguments: to get the correct string.
The idea is that I would run all [NSString stringWithFormat:] calls through this method instead.
This might seem unnecessary to many, but click on to the referenced SO question (first line) to see examples of why I need to do this.
Ideas? Thoughts? Better implementations? Better Solutions?
Whoa there!
Instead of screwing with a core method that you very probably will introduce subtle bugs into, instead just turn on "Static Analyzer" in your project options, and it will run every build - if you get the arguments wrong it will issue a compiler warning for you.
I appreciate your desire to make the application more robust but I think it very likely that re-writing this method will more likely break your application than save it.
How about defining your own interim method instead of using format specifiers and stringWithFormat:? For example, you could define your own method replaceIndexPoints: to look for ($1) instead of %1$#. You would then format your string and insert translated replacements independently. This method could also take an array of strings, with NSNull or empty strings at the indexes that don't exist in the “untranslated” string.
Your method could look like this (if it were a category method for NSMutableString):
- (void) replaceIndexPointsWithStrings:(NSArray *) replacements
{
// 1. look for largest index in "self".
// 2. loop from the beginning to the largest index, replacing each
// index with corresponding string from replacements array.
}
Here's a few issues that I see with your current implementation (at a glance):
The __VA_ARGS__ thingy explained in the comments.
When you use while (arg = va_arg(args, id)), you are assuming that the arguments are nil terminated (such as for arrayWithObjects:), but with stringWithFormat: this is not a requirement.
I don't think you're required to escape the $ and # in your string format in your arg-loop.
I'm not sure this would work well if uaStringWithFormat: was passed something larger than a pointer (i.e. long long if pointers are 32-bit). This may only be an issue if your translations also require inserting unlocalised numbers of long long magnitude.

How to get rid of all this garbage in NSLog?

When I use
NSLog(#"fooBar")
it prints out a lot of stuff I don't want:
2009-09-03 13:46:34.531 MyApp[3703:20b] fooBar
Is there a way to print something to the console without this big prefix?
I want to draw a table and some other things in the console so that space is crucial...
This is from Mark Dalrymple at borkware.com
http://borkware.com/quickies/single?id=261
A Quieter NSLog (General->Hacks) [permalink]
// NSLog() writes out entirely too much stuff. Most of the time I'm
// not interested in the program name, process ID, and current time
// down to the subsecond level.
// This takes an NSString with printf-style format, and outputs it.
// regular old printf can't be used instead because it doesn't
// support the '%#' format option.
void QuietLog (NSString *format, ...)
{
va_list argList;
va_start (argList, format);
NSString *message = [[[NSString alloc] initWithFormat: format
arguments: argList] autorelease];
printf ("%s", [message UTF8String]);
va_end (argList);
} // QuietLog
This is a variation on the borkware quickies: http://cocoaheads.byu.edu/wiki/a-different-nslog . It prints the file and line number of where the log takes place. I use it all the time.
NSLog just prints to strerr. Use fprintf instead.
fprintf(stderr, "foobar");