This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
index 0 beyond bounds for empty array error
I am a hobbyist in iOS programming. And I always have the same problem while programming.
I think that I don't understand the memory management issue.
This is my actual project:
h-File:
a NSMutableArray containing NSStrings
NSMutableArray *pictureStrings;
#property (nonatomic,retain) NSMutableArray *pictureStrings;
in the m-File:
#synthesize pictureStrings;
and in the viewDidLoad Method
pictureStrings = [[NSMutableArray alloc] init];
in the dealloc Method
[pictureStrings release];
okay. I am working with an asynchronous URL Request which gets a number of strings and in the connectionDidFinishLoading Delegate Method the values get stored to pictureStrings
[pictureStrings addObject:aString];
BUT
when I read values in the cellForRowAtIndexPath Method like
if (pictureStrings != nil) {
cell.textLabel.text = [NSString stringWithFormat:#"%#", [pictureStrings objectAtIndex:indexPath.row]];
}
my App crashes with the message:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array'
Where is my fault?
Please help me!
Yours, Raphael.
Greetings from Austria
Your crash is not a memory management issue. Your crash is that you're reading past the end of an array. We know that pictureStrings is non-nil at that point, because if it was nil then the objectAtIndex: method would have silently just returned nil (because messaging nil returns nil [1]). Oh and not to mention you have the if around the call to objectAtIndex: anyway - but you can safely remove that really as you shouldn't need it.
So, the only thing that can really be happening here is that your [pictureStrings addObject:aString] are not being called before the table view is reloaded. However, that would be quite odd because I assume you are doing something like return pictureStrings.count in the table view's tableView:numberOfRowsInSection: data source method. If there really were zero elements in the array then the table view wouldn't be asking for any rows and you wouldn't get this crash.
I think your problem is likely to be that you're not adding the strings to the array. You could check by breakpointing that code and seeing what is happening.
Also, consider changing your code to set the cell's text to this:
cell.textLabel.text = [pictureStrings objectAtIndex:indexPath.row];
You said they are already strings so why bother going through a stringWithFormat: call?
[1] Caveat: Doesn't always return nil - read up about it for more information.
Related
What can cause an assignment or change to an NSMutableArray to crash?
I have a mutable array containing custom objects, and I consistently keep no more than the 3 latest objects in it, the rest are removed. I started calling
[myArray insertObject:newObject atIndex:0];
if (myArray.count > 3)
[myArray removeLastObject]; // Crash
But whenever I do this too fast, the last line causes an exception-less crash.
I know that you are not allowed to add or remove objects of an array while it is being enumerated, but I do not enumerate myArray anywhere unless calling count on it performs an implicit enumeration. I also tried doing this:
NSMutableArray *tmp = [myArray mutableCopy];
[tmp removeLastObject];
myArray = tmp; // Crash
But the same thing happens, it crashes on the last line. Again, this works perfectly fine when doing it slowly. The action itself is being called when a user taps a button, and when tapping it too fast, it crashes every time.
EDIT:
I should add that all of this is being run inside the cellForItemAtIndexPath method of a UICollectionView.
First, could you please post the crash message. It would be nice to know what error you are actually seeing.
I wonder what would happen if you switched to immutable arrays. Switch myArray to being an NSArray * and use the following.
myArray = [self updatedArray:myArray withObject:newObject];
Where -updatedArray:withObject: is
- (NSArray *)updatedArray:(NSArray *)array withObject:(id)object {
switch (array.count) {
case 0: return #[object];
case 1: return #[object, array[0]];
default: return #[object, array[0], array[1]];
}
}
Or better for testing
NSArray *temp = [self updatedArray:myArray withObject:newObject];
myArray = temp; // I assume the crash will be here!
If the code crashed at my comment, then deallocating myArray is causing the crash. My guess is that one of the items in the array is pointing to bad memory (a zombie or some such thing).
I can think of two possible reasons for the crash:
1) The array is being accessed/mutated from multiple threads
2) An object inside the array was over-released somewhere, causing it to be deallocated while still living inside the array.
Short answer: try profiling your app in Instruments, and select the "Zombies" instrument. When the crash happens, you just might get a zombie alert, in which case you can get a back-log of everything that led up to your memory getting stomped over.
I think the root cause is this variable is accessed from multiple threads at the same time.
You can use this when access the array
#synchronized(self) {
//Your accessed code here
}
I am getting this error when trying to add the NSString object named object to my array :
erminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x7fc48ae52ba0'
Not sure why this is so. I was originally using NSArray to changed it to NSMutableArray but I am still having problems with it. Code is below:
-(NSMutableArray *)getReplyArrayForMessage: (NSString *)message {
//get the array
NSMutableArray *replies = [self.messageDictionary objectForKey:message];
NSLog(#"%#",replies);
//if no replies we init the base set
if([replies count]==0) {
//get the base array
//this also works if a key just isn't in the dictonary
return replies=[self getBaseArray];
}
else {
//add the other message
NSString *object = #"Send a different message";
[replies addObject:object];
return replies;
}
}
If anyone could give me a pointer to why this is happening I would appreciate it. Noob here.
The array is immutable (NSArray), not mutable (NSMutableArray). You can create a mutable array from an immutable array using mutableCopy:
NSMutableArray *replies = [[self.messageDictionary objectForKey:message] mutableCopy];
I know this from the exception text:
-[__NSArrayI addObject:]: unrecognized selector sent to instance ...
^^^^^^^^^^
EDIT I've missed off some important information from my original answer.
Having used mutableCopy you now have a copy of the array and the original array (from self.messageDictionary objectForKey:message) will remain unchanged after you add your element. This is almost certainly not what you intended.
As I've mentioned in the comments below, you probably want to create a custom object to hold these details (or an array of custom objects) that should be created from the message dictionary as soon as you receive it. The effort required to create a custom object will pay for itself tenfold in the long term.
You are trying to add an NSString to an NSArray (which looks like an NSMutableArray) since you initialized it as one. This means that the object that you store in your messageDictionary is actually of type NSArray, and not NSMutabelArray. So, assigning it to an object of type NSMutableArray won't actually change its type and make it mutable.
It's an easy fix:
NSMutableArray *replies = [[NSMutableArray alloc] initWithArray:(NSArray *) [self.messageDictionary objectForKey:message]];
Or you can go with mutableCopy (as suggested by #trojanfoe) which is a shortcut but will lead to the same result.
I can tell because the error message says -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x7fc48ae52ba0'. Notice the I at the end of __NSArrayI, this means that this array is actually immutable, so you can't add objects to it.
Scenario = I have a PFQueryTableViewController that needs different sized cells at each indexPath depending upon values returned from the query. If the image is "Landscape size" that would be one size, and if its "Portrait size" than that is another.
1) I create a NSMutableArray in .h file to hold the sizes.
#property (strong, nonatomic) NSMutableArray *rowHeightArray;
2) For each objects I check for the value in cellForRowAtIndexPath and add it to the array.
if ([object[#"orientation"] isEqual:#"left"] || [object[#"orientation"] isEqual:#"right"]) {
[rowHeightArray addObject:[NSNumber numberWithFloat:378]];
}
else {
[rowHeightArray addObject:[NSNumber numberWithFloat:488]];
}
(there are more than just these two possibilities but I think that is enough to get the point across)
ISSUES
3) Now I need to place the correct height to the correct cell. Problem is when I try...
return self.rowHeightArray[indexPath.row];
In heightForRowAtIndexPath it "yells" at me with:
returning 'id' from a function with an incompatible result type 'CGFloat' (aka 'float')
and alternatively I also tried
return [self.rowHeightArray[indexPath.row]floatValue]
this stops "yelling" at me but when i try to run this it errors out:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
Question = How do I accomplish what I'm trying to do here and have it 'work'?
Two problems:
heightForRowAtIndexPath: is called before cellForRowAtIndexPath:. So your current approach is calculating the height too late. You need to calculate it in heightForRowAtIndexPath: or earlier (in viewDidLoad, or when your PFQuery finishes, for example).
You're returning an NSNumber object not a CGFloat. You need to unbox it:
NSNumber *heightNumber = self.rowHeightArray[indexPath.row];
return [heightNumber floatValue];
Of course this won't work with your current implementation, since (as rmaddy points out) self.rowHeightArray is empty. So if you want to use this array, you need to make sure it's populated before heightForRowAtIndexPath: gets called.
To elaborate on #AaronBrager's correct answer, load, then build the float array before the datasource gets called. This can be done in the hook the PFTable vc gives you called. To answer your question in comments, there's no need for the indexPath, since we know that the self.objects array indexes, the indexes you build here, and the indexes passed to the datasource method will all correspond (0..self.objects.count-1):
- (void)objectsDidLoad:(NSError *)error {
self.rowHeightArray = [NSMutableArray array];
for (PFObject *object in self.objects) {
NSString *orientation = object[#"orientation"];
NSNumber *height;
if ([orientation isEqualToString:#"left"] || [orientation isEqualToString:#"right"]) {
height = #378.0;
} else {
height = #488.0;
}
[self.rowHeightArray addObject: height];
}
[super objectsDidLoad:error];
}
I want to clear my array, what i did is,
I have tableview view in my app, first i am fetching data from server and loading it in tableView.
-(void)viewDidLoad{
//fetching data from server using background thread and storing it in array called (msg_array)
[table reloadData];
}
when last row comes on screen i want to fetch new data from server and i want to display it,
-(void)LoadMoreData{ //this method gets fire when last cell is on screen
if ([msg_array count]>0)
{
[msg_array removeAllObjects]; //crashes here
}
}
This gives the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[__NSArrayI removeAllObjects]: unrecognized selector sent to instance
Why does it cause a crash:
The array is allocated like this:
msg_array = [dictShow copy];
dictshow contains the data and copying it to msg_array and dictshow is mutabledictionary
(Taken from comments)
'-[__NSArrayI removeAllObjects]: unrecognized selector sent to instance
This means that the array doens't have the method you're trying to call. That's because it is an immutable array (NSArray), not mutable (NSMutableArray).
Either make it mutable if you want to mutate it. Or, replace:
[msg_array removeAllObjects];
with:
msg_array = #[];
Based on your comment, the array should be mutable. That means that you have a mutable property / instance variable, but that you're creating an immutable instance to store into it. Find that location and update it (to create / return a mutableCopy at least).
Its because you are trying to modify a immutable array, you have two options here:
msg_array = #[];
OR
NSMutableArray *mutableMessageArray = [msg_array mutableCopy];
[mutableMessageArray removeAllObjects];
msg_array = [mutableMessageArray copy];
I prefer the first option as its neater, but if you need to do any other modifications of the array the latter option might be best for you.
NB:
Check how you declare msg_array, can you post that code?
msg_array could be immutable thats why it is crashing. removeAllObjects is only for NSMutableArray
__NSArrayI, looking carefully at this bit, we can see it's suffixed with an i. This i means the array is immutable and can't be changed.
You possibly want to use an NSMutableArray
msg_array = [dictShow copy]; dictshow contains the data and copying it to msg_array and dictshow is mutabledictionary
This is very strange! I expect that calling copy on a dictionary would always return a dictionary. Unless you are mistaken there, I can only imagine that either the dictionary's keys or its values are being returned.
I think you might have meant mutable array; assuming you did, the call to copy returns an immutable object, try [dictShow mutableCopy] instead
msg_array = [NSMutableArray arrayWithArray:_MoodsArray].mutableCopy;
Some information about the way I am saving my data: I have an array of View Controllers that are added and deleted by the user (this is basically a note taking app and the View Controllers are folders). The View Controllers have several dynamic properties that the app needs to save as well as the notes array within them, and then the Note objects themselves have a few properties that need to be saved. The View Controllers and the Notes both of course have the proper NSCoding stuff, this is the one on the View Controller for example:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.folderName forKey:#"lvcTitle"];
[encoder encodeObject:[NSNumber numberWithInt:self.myPosition] forKey:#"myPosition"];
[encoder encodeObject:self.notes forKey:#"notes"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.folderName = [decoder decodeObjectForKey:#"lvcTitle"];
NSNumber *gottenPosition = [decoder decodeObjectForKey:#"myPosition"];
int gottenPositionInt = [gottenPosition intValue];
self.myPosition = gottenPositionInt;
self.notes = [decoder decodeObjectForKey:#"notes"];
return self; }
The array of Controllers belongs to a Singleton class. NSCoding is pretty confusing to me even though it's considered to be simple stuff, but so far I've had success with only telling the Singleton to save the Controllers array - which then (successfully) saves all of the contained properties of the View Controllers, their properties and all of the Notes' properties as well. Here is the code in the Singleton:
- (void) saveDataToDisk:(id)object key:(NSString *)key {
NSString *path = [self pathForDataFile];
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:object forKey:key];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path]; }
- (void) loadDataFromDisk {
NSString *path = [self pathForDataFile];
NSDictionary *rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if ([rootObject valueForKey:#"controllers"] != nil) {
self.controllers = [NSMutableArray arrayWithArray:[rootObject valueForKey:#"controllers"]];
firstRun = false;
LabeledViewController *lastOneThere = [self.controllers objectAtIndex:self.controllers.count-1];
lastOneThere.isFolderAddView = TRUE;
}else{
firstRun = true;
}
}
I then call the save method several times in the Folder View Controllers:
[singleton saveDataToDisk];
And this will work well several times, until I randomly get a crash right when the app is loading up. The culprit is heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Note *currentNote = [self.notes objectAtIndex:indexPath.row];
if (currentNote.associatedCellIsSelected) {
return currentNote.myHeight + NOTE_BUTTON_VIEW_HEIGHT;
}
return NORMAL_CELL_FINISHING_HEIGHT; }
I get the following error:
2012-06-07 08:28:33.694 ViewTry[1415:207] -[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710
2012-06-07 08:28:33.696 ViewTry[1415:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710'
*** First throw call stack:
I understand that "__NSCFString" and "unrecognized selector sent to instance" means that there is a string somewhere there shouldn't be, as associatedCellIsSelected is a bool. However, if I only return "currentNote.myHeight" in heightForRow, I also get the same __NSCF error with myHeight, which is a float. If I take out heightForRow all together, everything works except for the appropriate height definitions.
BTW, the table view that heightForRowAtIndexPath is referencing is made in loadView AFTER the notes array is made and populated. I just don't understand why this error would only pop up every once in a while (like 5-10 opens, savings, closings and reopenings of app), seemingly random - I cannot find the pattern that causes this behavior. Any pointers?
Sorry for the mess, I'm new to iOS programming and I'm sure I'm doing a lot of things wrong here.
Edit - Also, once the app has crashed, it stays crashed every time I reopen it (unless I disable heightForRow) until I uninstall and reinstall it.
When you see an "unrecognized selector" error and the receiver type is not the kind of object that you coded (in this case __NSCFString instead of Note), the odds are that you have a problem where the object you intended to use has been prematurely released and its address space is being reused to allocate the new object.
The fix depends on tracking down where the extra release is happening (or retain is not happening). If you can show the #property declaration for notes it might shed more light on the situation.
One quick thing to do is choose Product->Analyze from the menu and fix anything it flags. It won't catch everything but it's a good sanity check to start.