Several Objective-C objects become Invalid for no reason, sometimes - objective-c

- (void)loadLocations {
NSString *url = #"<URL to a text file>";
NSStringEncoding enc = NSUTF8StringEncoding;
NSString *locationString = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:url] usedEncoding:&enc error:nil];
NSArray *lines = [locationString componentsSeparatedByString:#"\n"];
for (int i=0; i<[lines count]; i++) {
NSString *line = [lines objectAtIndex:i];
NSArray *components = [line componentsSeparatedByString:#", "];
Restaurant *res = [byID objectForKey:[components objectAtIndex:0]];
if (res) {
NSString *resAddress = [components objectAtIndex:3];
NSArray *loc = [NSArray arrayWithObjects:[components objectAtIndex:1], [components objectAtIndex:2]];
[res.locationCoords setObject:loc forKey:resAddress];
}
else {
NSLog([[components objectAtIndex:0] stringByAppendingString:#" res id not found."]);
}
}
}
There are a few weird things happening here. First, at the two lines where the NSArray lines is used, this message is printed to the console-
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFDictionary count]: method sent to an uninitialized mutable dictionary object'
which is strange since lines is definitely not an NSMutableDictionary, definitely is initialized, and because the app doesn't crash.
Also, at random points in the loop, all of the variables that the debugger can see will become Invalid. Local variables, property variables, everything. Then after a couple lines they will go back to their original values. setObject:forKey never has an effect on res.locationCoords, which is an NSMutableDictionary. I'm sure that res, res.locationCoords, and byId are initialized.
I also tried adding a retain or copy to lines, same thing. I'm sure there's a basic memory management principle I'm missing here but I'm at a loss.

Your -[NSArray arrayWithObjects:] call must end with a nil (because a C function needs it to determine how many arguments you gave it).

I don't see anything obviously wrong with memory management here. Maybe add some error checking:
check the error: parameter of initWithContentsOfURL:
check locationString != nil
check lines != nil

Related

[__NSCFString count]: Unrecognized selector

I know this has been asked before, but there is no answer that I have found useful.
First off here is my code
// load the .csv file with all information about the track
NSError *error;
NSString *filepath = [[NSBundle mainBundle] pathForResource:#"file" ofType:#"csv" inDirectory:nil];
NSString *datastring1 = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:&error];
NSArray *datarow = [datastring1 componentsSeparatedByString:#"\r"];
//fill arrays with the values from .csv file
NSArray *data_seg = [datarow objectAtIndex:0]; //segment number
NSArray *data_slength = [datarow objectAtIndex:1]; //strait length
NSArray *data_slope = [datarow objectAtIndex:2]; //slope
NSArray *data_cradius = [datarow objectAtIndex:3]; //circle radius
NSArray *data_cangle = [datarow objectAtIndex:4]; //circle angle
NSLog(#"%i", [data_seg count]);
Okay, so there is the code, and I read that is has something to do with autorelease, but I was not able to add a retain like NSArray *data_seg = [[datarow objectAtIndex:0] retain]
When I run the code, I get [__NSCFString count]: unrecognized selector sent to instance 0x9d1ad50
Any help is appreciated, I'm not good at programming, and I am very new.
componentsSeparatedByString method returns an NSArray of NSString. Every item that you extract from datarow array is an NSString and an NSString doesn't respond to 'count'. Your code starting at //fill arrays is incorrect. Every objectAtIndex call will return an NSString*.
This is another way of saying that the datatype for data_seg is NSString* (not NSArray*).
With the corrected code snippet, the problem is because data_seg is a string, and -count is not a method of NSString. It seems you think data_seg is an NSArray.
Look at the documentation for -[NSString componentsSeparatedByString:] and see what it returns -- strings! So you get back an array of strings. So what you want is:
NSString *data_seg = [datarow objectAtIndex:0]; //segment number
NSLog(#"my segment number is: %#", data_seg);

NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array when checking NSPasteboard

When I do a check to validate to make sure copiedItems array has something it in, I am receiving the error: NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array
NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil]; NSDictionary *options = [NSDictionary dictionary];
NSArray *copiedItems = [pasteboard readObjectsForClasses:classes options:options];
if ([copiedItems objectAtIndex:0]!= nil){
NSString *copiedString = [copiedItems objectAtIndex:0];
Turns out I was causing the error with my check. By calling [copiedItems objectAtIndex:0], I was accessing the array right then, instead of seeing if it was valid first (my original intention). By changing [copiedItems objectAtIndex:0] to if ([copiedItems count]> 0)
I was able to not access copiedItems until I could validate.
A good expansion from #H2CO3: [array objectAtIndex:index] != nil doesn't make sense - NSArrays cannot contain nil

Objective-C: copying a returned NSMutableArray

I am calling a method getHoleScore that populates a NSMutableArray and returns it. I'm trying to copy the array so that the calling method can access the items but I get this exception every time:
exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
I've tried multiple different ways of copying an array that I've found on this site but nothing seems to work. Here's the code:
Score *theScore = self.roundScore;
NSMutableArray *scores = [[[self delegate]getHoleScore:self score:theScore] mutableCopy];
NSInteger total = 0;
if (theScore) {
self.playerName.text = theScore.name;
self.courseName.text = theScore.course;
self.hole1Field.text = [NSString stringWithFormat:#"%d", [[scores objectAtIndex:0] integerValue]];
self.hole2Field.text = [NSString stringWithFormat:#"%d", [[scores objectAtIndex:1] integerValue]];
self.hole3Field.text = [NSString stringWithFormat:#"%d", [[scores objectAtIndex:2] integerValue]];
self.hole4Field.text = [NSString stringWithFormat:#"%d", [[scores objectAtIndex:3] integerValue]];
self.hole5Field.text = [NSString stringWithFormat:#"%d", [[scores objectAtIndex:4] integerValue]];
etc.
Any idea on how to populate the scores array?
You are reaching 0 for your mutable array because it hasn't been alloc/init yet. you either have to place this NSMutableArray *myscores = [NSMutableArray array]; above you [[self delegate] ] call.
Or a better way to do this would be creating your myscores and pass the method in with a this built in nsarray super class method, which takes care of alloc/init your array.
NSMutableArray *myscores =
[NSMutableArray arrayWithArray:[[self delegate] getHoleScore:self
score:theScore];
also make sure that
[[self delegate]getHoleScore:self score:theScore]is not sending you a empty array, calling a -objectAtIndex:a on a empty array will crash your code. A good way to check if your array is empty or not is calling the method -count method on your array, calling count on a array won't crash your code, even if it's zero.
Hey thanks for the help everyone. You were right that getHoleScore returned something nil. It was working off an sqlite database but once I cleared it and then re-added the data, everything worked fine.

NSString.length gives EXC_BAD_ACCESS

I have the following code:
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
NSString *temp;
for (int i = 0; i < [array count]; i++)
{
temp = [array objectAtIndex:i];
NSLog(#"temp length = %#", [temp length]);
}
I get an EXC_BAD_ACCESS error at the NSLog line. I assume it's erring out at the [temp length] bit. The weird thing is, I can do other methods of NSString on temp and they work fine, like [temp characterAtIndex:0].
I've also tried doing [[array objectAtIndex:i] retain];, but that doesn't seem to help.
Does anyone know why I'm getting this error?
EDIT: Turns out it was crashing at the NSLog because it was %# instead of %lu. The real problem was with other code that I had omitted from this post. After playing around with it some more, I got it working.
From my understanding, the "%#" placeholder is for object pointers, "length" returns "NSUInteger" which is not a pointer. Try "%lu" instead of "%#".
This (slightly cleaned up) version works for me:
NSError *error = nil;
NSString *path = [#"~/Desktop" stringByExpandingTildeInPath];
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:
path error:&error];
if (error) NSLog(#"%#", error);
for (NSString *path in array) {
NSLog(#"Path length = %lu", path.length);
}
As thg435 mentioned, "%#" is for object pointers, so if you pass it an arbitrary number it will throw a memory access error.

stringWithFormat vs. initWithFormat on NSString

I am wondering what differences such as disadvantages and/or advantages there are to declaring an NSString this way:
NSString *noInit = [NSString stringWithFormat:#"lolcatz %d", i];
as opposed to:
NSString *withInit = [[NSString alloc] initWithFormat:#"Hai %d", i];
What was the motivation of putting stringWithFormat instead of just having the initWithFormat way of initializing the string?
stringWithFormat: returns an autoreleased string; initWithFormat: returns a string that must be released by the caller. The former is a so-called "convenience" method that is useful for short-lived strings, so the caller doesn't have to remember to call release.
I actually came across this blog entry on memory optimizations just yesterday. In it, the author gives specific reasons why he chooses to use [[NSString alloc] initWithFormat:#"..."] instead of [NSString stringWithFormat:#"..."]. Specifically, iOS devices may not auto-release the memory pool as soon as you would prefer if you create an autorelease object.
The former version requires that you manually release it, in a construct such as this:
NSString *remainingStr = nil;
if (remaining > 1)
remainingStr = [[NSString alloc] initWithFormat:#"You have %d left to go!", remaining];
else if (remaining == 1)
remainingStr = [[NSString alloc] initWithString:#"You have 1 left to go!"];
else
remainingStr = [[NSString alloc] initWithString:#"You have them all!"];
NSString *msg = [NSString stringWithFormat:#"Level complete! %#", remainingStr];
[remainingStr release];
[self displayMessage:msg];
Here, remainingStr was only needed temporarily, and so to avoid the autorelease (which may happen MUCH later in the program), I explicitly handle the memory as I need it.