Is if (variable) the same as if (variable != nil) in Objective-C - objective-c

I am getting a EXC_BAD_ACCESS (SIGBUS) on this line in my iPhone project:
if (timeoutTimer) [timeoutTimer invalidate];
The thing that has me stumped is that I don't understand how that line could crash, since the if statement is meant to be checking for nil. Am I misunderstanding the way Objective-C works, or do line numbers in crash statements sometime have the wrong line in them?

Just because a variable is set to a value other than nil doesn't mean it's pointing to a valid object. For example:
id object = [[NSObject alloc] init];
[object release];
NSLog(#"%#", object); // Not nil, but a deallocated object,
// meaning a likely crash
Your timer has probably already been gotten rid of (or possibly hasn't been created at all?) but the variable wasn't set to nil.

I just ran into a similar issue, so here's another example of what might cause a check such as yours to fail.
In my case, I was getting the value from a dictionary like this:
NSString *text = [dict objectForKey:#"text"];
Later on, I was using the variable like this:
if (text) {
// do something with "text"
}
This resulted in a EXC_BAD_ACCESS error and program crash.
The problem was that my dictionary used NSNull values in cases where an object had an empty value (it had been deserialized from JSON), since NSDictionary cannot hold nil values. I ended up working around it like this:
NSString *text = [dict objectForKey:#"text"];
if ([[NSNull null] isEqual:text]) {
text = nil;
}

They should be the same. Perhaps the line number is in fact incorrect.
Look for other possible errors near that in your code and see if you find anything.

Related

Block based enumeration getting BAD_ACCESS

I have a block based enumeration setup to go through an array of NSDictionaries like this:
__block NSURL *contentURL;
//This method of enumerating over the array gives the bad_access error
[documents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *aName = [(NSDictionary *)obj objectForKey:#"Name"];
if([aName isEqualToString:name]) {
contentURL = [NSURL URLWithString:[(NSDictionary *)obj objectForKey:#"Content"]];
*stop=YES;
}
}];
NSLog(#"Content URL for issue with name %# is %#", name, contentURL);
Which if I use this method I get a EXC_BAD_ACCESS error on contentURL when I try to print it out in the NSLog statement.
If however, I enumerate through the array like this:
NSURL *contentURL;
//This method of enumerating over the array works fine
for (NSDictionary *obj in documents) {
NSString *aName = [obj objectForKey:#"Name"];
if([aName isEqualToString:name]) {
contentURL = [NSURL URLWithString:[obj objectForKey:#"Content"]];
}
}
NSLog(#"Content URL for issue with name %# is %#", name, contentURL);
All works fine. Why is this?
I ran into a similar problem a while ago.
Turns out that some of the block-based enumeration methods wrap the enumeration in an autorelease pool. Since you're assigning an autoreleased object, it gets deallocated before -enumerateObjectsUsingBlock: returns.
(I ran into the problem with -[NSDictionary enumerateKeysAndObjectsUsingBlock:, but the same principle applies here)
Try this instead:
contentURL = [[NSURL alloc] initWithString:[(NSDictionary *)obj objectForKey:#"Content"];
Out of interest, are you using ARC? If you are, I'd have expected it to add in a -retain on assignment.
Edit:
You are using ARC, so this isn't the answer to your question. Assigning to a __block variable will retain the object (unless you've encountered a bug in ARC, which is unlikely. The code you've provided doesn't have this issue when compiled using Apple LLVM 5.0).
It is likely that your problem is elsewhere and changing from using the convenience constructor is just masking the problem.
Likewise, the autorelease pool set up for the duration of the enumeration is likely revealing a problem that is caused elsewhere in your code. It explains why switching to using fast enumeration seems to fix the problem, but as before it is likely just masking a problem caused elsewhere in your code.
I'll leave the answer here because the information about autorelease pools may still be relevant to people who stumble across this.
If you're using ARC, as you say you are, then the code you show cannot produce the problem you describe. You must have some problem somewhere else, or your code is not as you describe.

objectAtIndex returns SIGABRT error

I have this line:
NSString *objectkey=[NSString stringWithFormat:#"%#",[line objectAtIndex:1]];
If objectAtIndex:0, it works fine. But if 1, it produces SIGABRT error at runtime.
However, I have another line to confirm that the array "line" has an object at index 1:
NSLog(#"%d",[line count]);
This returns 2 to the console.
Why would SIGABRT occur even though an index should exist there?
As for line, it is created like this:
for (int i=1;i<=[components count];i++){
NSArray*line=[[components objectAtIndex:(i-1)] componentsSeparatedByString:#"|"];
"line" is recreated during each loop iteration (I assume this is okay? no release is necessary, from what i understand using the "separated by string" method).
the array "components" contains lines such as:
Recipe Books|BOOKS
Recipe Photos|PHOTOS
I have created this little loop to verify that all are strings in "line":
for( NSObject* obj in line )
{
NSLog(#"%#",obj);
if ([obj isKindOfClass:[NSString class]]==YES) { NSLog(#"string"); }
}
Most likely the object contained at [line objectAtIndex:1] is not an NSString*. Why don't you try iterating over the set of objects in line and outputting them with NSLog. My guess is that the second one is going to print an address (of the form <Classname: 0x0>, not a string.
for( NSObject* obj in line )
{
NSLog(#"%#",obj);
}
Add an NSLog to the code where you create line:
for (int i=1;i<=[components count];i++){
NSLog(#"%#",[components objectAtIndex:(i-1)]);
NSArray*line=[[components objectAtIndex:(i-1)] componentsSeparatedByString:#"|"];
I have a strong feeling that your input data is bad, as you've excluded our prior answers as the solution with your responses. Your input data might have a <cr> somewhere it doesn't belong or missing data. How big is the input file for components?
Second answer based on gdb results
Try using [NSString stringWithString:[line objectAtIndex:1]] instead of stringWithFormat. Based on your use of gdb its likely that stringWithFormat is breaking on the unexpected control character (and trying to format it). stringWithString should copy the string character by character. Removing the control character(s) is another problem. :)
Assuming there is a valid object at index 1 of your line NSArray, the likely answer is that it's not a NSString, it's some other class that can't be translated using %# in your stringWithFormat: call. Look at the stack where it aborts and you could also check the type of the object before calling the stringWithFormat: call.

Why this strange behavior is occurring with this code? objective-c

I have a method (the code below is a simplified version) that parses small text files:
- (void)parseFile:(NSString *)aFile
{
NSDate *date;
NSNumber *number;
NSString *desc;
NSString *txt = [NSString stringWithContentsOfFile:aFile encoding:NSUTF8StringEncoding error:nil];
for (NSString *line in [txt componentsSeparatedByString:#"\n"]) {
if ([linesubstring isEqual:#"mydate"]) {
date = [dateFormat dateFromString:strDate];
}
if ([linesubstring isEqual:#"mynumber"]) {
number = [numberFormat numberFromString:strValue];
}
if ([linesubstring isEqual:#"mydesc"]) {
desc = [line substringWithRange:NSMakeRange(0, 10)];
}
if (!date && !number && !desc) {
...do something...
}
}
}
The first problem is that date variable is being filled with the content of aFile parameter. It only assumes it's correct value, when the passes through the fist if/check.
So why? I though that date could be a reserved word and exchanged it, but with the same behavior.
The second problem is with the last if (with the nested ones). Debuging the code, i can see that xcode shows it as "out of scope", but !number fails (xcode thinks that it's valid)...
I tried other combinations, like [number isNotEqualTo:[NSNull null]] (this one throws an error EXC_BAD_ACCESS), without success.
Please, could anybody give some hints? I'm newbie with cocoa/objective-c. I'm coming from java...
TIA,
Bob
There's quite a few things wrong with the code you've provided. I'm using the answer box because there isn't enough room for this to be a comment:
With regards to your variable declarations:
NSDate *date;
NSNumber *number;
NSString *desc;
You have correctly declared them, but you have not initialised them. As they are, they could be pointing to any random garbage. This means that your test at the end of the loop…
if (!date && !number && !desc) {
...do something...
}
…may in fact always execute because date, number and desc may always be non-zero (I say may because it is actually undefined whether they are zero or non-zero). Initialise each of them to nil if you plan to determine whether they are set or not:
NSDate *date = nil;
NSNumber *number = nil;
NSString *desc = nil;
It is not always necessary to initialise variables (for example, as long as you write to it before you read from it, it is not necessary to initialise it), however some people promote the idea of initialising all variables to prevent this undefined behaviour from surfacing (I typically initialise all variables even if I overwrite the initialised value anyway).
Also, there is a variable called linesubstring but it is not declared anywhere in the code, similarly strDate, strValue are not declared anywhere either. It is important to know how these are declared and how these are used as they may similarly be pointing to garbage.

Why do I get EXC_BAD_ACCESS?

I have following code, which executes on button press. At first it works as expected but second time onwards application hangs and I get EXC_BAD_ACCESS signal.
- (IBAction) comicDetailsPressed:(id)sender {
static IssueProperties *props = nil;
if (props == nil) {
props = [ComicDataParser
parseComicForUrl:#"http://dummy.com/Jan.xml"];
}
NSLog(#"%d", [props totalPages]);
totalPages.text = [NSString stringWithFormat:#"%d", [props totalPages]];
}
You didn't say what line it's crashing on, which will mean answers will have to be speculative.
You have a static pointer to a IssueProperties object, but when you assign to it, you aren't using retain. You probably should.
This is assuming that the return value from parseComicForUrl: is a IssueProperties object or a subclass.
I'm assuming that the text property is an NSString set to copy and not retain. If not, it should be.
You need to retain the object you get back from +parseComicForUrl:. Also, why don't you use an instance variable for props?
Without a lot more context, it is going to be impossible to answer for sure, but my first thought would be this:
your static IssueProperties *props would not be nil the second time around. Instead, it would have the value that [ComicDataParser parseComicForUrl] returned.
My guess is that the ComicDataParser is autoreleaseing the response, and so the second time around you have a pointer that is not nil, but is now pointing to an already released object, which is invalid.
If I am right, you need a retain somewhere.

Objective-C NSString Assignment Problem

In my Cocoa application, in the header file, I declare a NSString ivar:
NSString *gSdkPath;
Then, in awakeFromNib, I assign it to a value:
gSdkPath = #"hello";
Later, it's value is changed in the code:
gSdkPath = [NSString stringWithString:[folderNames objectAtIndex:0]];
(the object returned from objectAtIndex is an NSString)
However, after this point, in another method when I try to NSLog() (or do anything with) the gSdkPath variable, the app crashes. I'm sure this has something to do with memory management, but I'm beginning with Cocoa and not sure exactly how this all works.
Thanks for any help in advance.
EDIT: This was solved by retaining the string [gSdkPath retain].
(the object returned from
objectAtIndex is an NSString)
Are you sure? I suggest putting this in it's own temporary variable and double checking that it's not nil or invalid in some way.
Edit: If that is OK so far, do note that stringWithString returns an autoreleased object. You need to retain it if you want to use it "later".
gSdkPath = [NSString stringWithString:[folderNames objectAtIndex:0]];
[gSdkPath retain];