NSMutableArray Resetting Itself? - objective-c

I am having an issue with NSMutableArray wiping its contents.
Consider my code: (int i; is in my file's .h as is NSMutableArray* newFileControllerArray)
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [[NSMutableArray alloc]init];
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"%lu",(unsigned long)elementsInArray);
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"THERE ARE %lu ELEMENTS IN THE ARRAY",(unsigned long)elementsInArray);
}
The first method called (other than awakeFromNib:) is newFileMenubar: This will add one element to the array. I can confirm that this works because 1 is printed in the console. However, once OKbutton is called and I print out the number of elements in my array it says that no elements are in the array. Why is that?
Am I missing something very obvious here? Why does my array reset itself?
EDIT:
The comments have gotten long and unwieldy so here is the code w/NSLogs and outputs:
-(void)awakeFromNib{
i = 0;
newFileWindowControllerArray = [NSMutableArray array];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
[[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
i++;
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
}
-(IBAction)OKButtonClicked:(id)sender{
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
[[newFileWindowControllerArray objectAtIndex:i]close];
}
When the program launches, this is the output: self=0x100141480, array=0x100140f30
This should be coming from awakeFromNib:
The next method called is newFileMenubar:
The output from this is
self=0x1001ac990, array=0x1005228a0 and immediately after self=0x100141480, array=0x100140f30
The last method called is OKButtonClicked:
The output from the last method (OKButtonClicked:) is self=0x1001ac990, array=0x1005228a0
As you can see from the code, the name of the array doesn't change, but my outputs beg to differ? What could cause this?

There are good clues in your log output. There are multiple instances of the view controller (see the different values for 'self'?). They each have their own array. See this code...
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:[[NewFileWindowController alloc]initWithWindowNibName:#"NewFileWindowController"]];
When you press the button associated with that action, your app builds another view controller and places it in the array. That view controller gets the awake from nib message and allocates another array, and so on.
To confirm this, change the code as follows:
-(IBAction)newFileMenubar:(id)sender{
[newFileWindowControllerArray addObject:#"Hello world"];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i] showWindow:nil];
In the other methods, comment out your expectations that the array has anything other than strings in it, and see what you get. e.g. ...
- (IBAction)OKButtonClicked:(id)sender {
NSUInteger elementsInArray = [newFileWindowControllerArray count];
NSLog(#"self=%p, array=%p", self, newFileWindowControllerArray);
[documentController newDocument:sender];
// and comment this out, for now:
// [[newFileWindowControllerArray objectAtIndex:i]close];
// instead...
NSLog(#"danh thinks my array will be ok: %#", newFileWindowControllerArray);
}
You probably do not mean to create another view controller on every button press, but I'm not sure what function you do want. Maybe you want an array of views? (To create many view controllers under the control of another, you'll want to read up on container view controllers, here).

Related

Outlets not working after showing "old" view again

I have several UIViews in my Storyboard and, of course, I can switch between them using a segue. Initially this works just fine:
notenKurse is a NSMutableArray, and kurse1Outlets is an outlet collection with my UITextFields.
int counter = 0;
for (UITextField *tf in kurse1Outlets) {
NSMutableString *t = [NSMutableString stringWithFormat:#"%#", [notenKurse objectAtIndex:counter]];
NSLog(#"Object at index %i is %#", counter, [notenKurse objectAtIndex:counter]);
if ([t isEqualToString:#"42"]) {
[t setString:#""];
}
[tf setText:t];
NSLog(#"UITextField in slot %i should now display %#", counter, t);
counter++;
}
All of my UITextFields are displaying the value stored in the array. But if I go to another view (let's assume I have a Button for it ;) ) Change something, and then go back to the original UIView the above code still gets executed, and there are different values in the array (this is supposed to be). I can see that in the log. But the stupid UITextField just doesn't display anything. Neither what was in there before, nor the new text. But why? The log clearly shows that t is what it's supposed to be, so the error must be in writing it into the textfield, and therefore I guess it's an outlet issue...
There is no guarantee of the order of your outlet collection. It's treated very much like an NSDictionary as opposed to an NSArray - where order is guaranteed. Iterating over this sort of collection will yield different results for different devices/people/phase of the moon.
When I use a collection like this I tend to set the 'tag' and then reorder the outlet collection when viewDidLoad by sorting off of the tag.
self.calendarDayImageViews = [_calendarDayImageViews sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if ([(UIView *)obj1 tag] < [(UIView *)obj2 tag]) {
return NSOrderedAscending;
}
else if([(UIView *)obj1 tag] > [(UIView *)obj2 tag]){
return NSOrderedDescending;
}
else{
return NSOrderedSame;
}
}];
You can just output the tf,by
NSLog(#"%#",tf);
To check if the tf is null
Ok, i found it. I forgot to release some stuff, and so my UITextFields did get set before the array was sorted. My mistake!

NSMutableArray of nsstrings (in one of the UISearchBar delegates) possible with persistent storage?

I would like to make a history list in my app. I have surfed and looked at many questions here, but so far I haven´t found the answer to my question.
My codes:
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
NSLog (#"search bar text did end editing!");
NSString *bookmarked = mySearchBar.text;
NSMutableArray *bookmarkl = [[NSMutableArray alloc] init];
int i;
for (i = 0; i < [bookmarkl count]; i ++)
{
[bookmarkl addObject:bookmarked];
}
for (NSString *sa in bookmarkl);
NSLog (#"whole array: %#", bookmarkl);
}
But all I get is one string at a time. What is wrong?
I want to add a new string to the mutable array, how should I do this?
You are creating a new list every time the function is called. You should add bookmarkl to your class as a property and only create it once during initialization. If you want the history to persist when your app is closed you will also want to write the data to a file. You can use [NSMutableArray arrayWithContentsOfFile] to load the history and writeToFile to save it.

How to manage memory/usage of a NSMutableArray of NSMutableArrays

I am currently trying to write a class to create faux grid system to keep track of a NSMutableArray of game entities using a NSMutableArray of NSMutableArrays. Given my limited experience with Objective-C programming, I am unsure of how certain things work.
Here is the init method:
#define MAX_BALL_ROWCOUNT 6
#define MAX_BALL_COLCOUNT 4
- (id) initWithMutableArray:(NSMutableArray *)aList {
self = [super init];
if (self != nil) {
ballList = [[NSMutableArray alloc] initWithCapacity: MAX_BALL_ROWCOUNT];
for (int i=0; i<MAX_BALL_ROWCOUNT; i++) {
NSMutableArray *balls = [[NSMutableArray alloc] initWithCapacity:MAX_BALL_COLCOUNT];
[ballList addObject:balls];
[balls release];
}
int x = 0;
for (NSMutableArray *array in ballList) {
for (int i = 0; i<MAX_BALL_COLCOUNT; i++) {
[array addObject:[aList objectAtIndex:x]];
x++;
}
}
}
return self;
}
ballList is the class's NSMutableArray that will store NSMutableArrays.
aList is the NSMutableArray containing the GameEntities I wish to keep track of that is passed into this class.
All the sizes and amount of entities to store are fixed, which is why there is no checks on the sizes of the arrays nor the number of entities to store.
So the first question I have involves freeing memory. This is the dealloc function I currently have:
- (void) dealloc {
[ballList release];
[super dealloc];
}
Does calling a release on ballList cause the release to be called on the NSMutableArrays that it contains (which will subsequently call the release on the objects those NSMutableArrays contain) or do I have to write something like:
for (NSMutableArray *array in ballList) {
[array release];
}
[ballList release];
My second question involves the usage of this array of arrays. Is this the proper way to traverse through ballList?
- (void) update {
for (NSMutableArray *array in ballList) {
for (GameEntity *balls in array) {
(CGPoint) location = [balls getLocation];
[balls setLocation: CGPointMake(location.x+1, location.y+1)];
}
}
}
Lastly, in the code above where it sets the balls location, does it only affect the contents within ballList or does the original aList that is passed into ballList change as well? If the contents in the original aList do not change, how would I write it so that they do?
If people have suggestions for a better way to keep track of the entities in a grid system, I'd be open to those too. Thanks in advance.
First : One release is enough for the NSMutableArray instance to release all it's object.
[ballList release];
Second : Your code for updating GameEntity instance is fine and will also effect to the original aList (which you called) .
When the dealloc of a NSArray or NSMutableArray is called, all its contents gets a release message. So when you release ballList, if there there is no other owner (I guess in this case there is none) then its dealloc is called and you don't need to release the other arrays here.
Your loop traversal is fine. Though for 2D arrays instead of NSArray of NSArray I personally prefer pure C 2D array, at least in most of the cases.
When you are adding object in this way you are adding a reference in the array. So any change via the array's reference will be reflected in all references of the object. If you don't want that then add a copy of the object in the array.

why the tableview doesn't show binding data?

Here's my code of generating data
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[array initWithCapacity:20];
}
- (IBAction) readlog:(id)sender {
for (int i = 0; i < 20; i++) {
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingFormat:#"/%d.log",i]];
[array addObject:d];
}
}
- (IBAction) writelog:(id)sender {
for (int i = 0; i < 20; i++) {
NSMutableDictionary *d = [NSMutableDictionary dictionary];
NSString *name = [NSString stringWithFormat:#"testfile%d", i];
[d setObject:[NSDate date] forKey:#"date"];
[d setObject:[path stringByAppendingFormat:#"/%d.log", i] forKey:#"path"];
[d setObject:name forKey:#"name"];
[d writeToFile:[path stringByAppendingFormat:#"/%d.log", i] atomically:YES];
}
and I bind my tableview column with appdelegate.array with keypath name/path/date
but it doesn't show any data in the array.. is there anything wrong here?
Thanks!
You haven't created an array.
init methods, including NSMutableArray's initWithCapacity:, initialize an existing (freshly-created) instance. You haven't created one, so you're sending that initWithCapacity: message to nil, which means it has no effect.
You need to create the array, then initialize it, then assign it to your array variable, preferably all in the same line.
There's also the issue that your table view will have already asked for the array by the time you receive the applicationDidFinishLaunching: message. You don't have one yet, so it gets nothing; by the time you create one, it has already asked you for it and gotten its answer, and does not know that it should ask again.
Create your array in init or initWithCoder: (I believe you will need the latter if your app delegate is in a nib), and implement and use Key-Value-Coding-compatible accessor methods to fill the array with values. When you send yourself accessor messages, you'll cause KVO notifications that will tip off the table view that it needs to ask for the array again. Assigning directly to the instance variable will not cause this effect.
A couple of other things…
You have three [path stringByAppendingFormat:#"/%d.log", i] expressions in two different methods. Don't repeat yourself. Move that to a method named something like logFileNameWithLogFileNumber: and send yourself that message to generate the filename. This will make the code both clearer and easier to maintain.
Finally, as a matter of style, you should not use stringByAppendingFormat: or stringWithFormat: to construct paths. Use stringByAppendingPathComponent: (in this case, together with stringWithFormat: to generate the filename). Clarity and pathname-separator-independence are virtues.

Reusing NSMutableArray

I'm getting some leaks (obvserved by Instruments) when trying to reuse an existing NSMutableArray (in order to save memory).
Basically I'm creating an NSMutableArray, filling it with objects (UIImages) and passing it onto another object which retains it. However, I now need to use an NSMutableArray again. I figured I would release all its objects, empty it, and everything would be fine, but Instruments reports a CALayer leaked object (??) from that very method which looks something as follows:
NSString *fileName;
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] init];
// fill the array with images
for(int i = 0; i <= 7; i++) {
fileName = [NSString stringWithFormat:#"myImage_%d.png", i];
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
}
// create a button with the array
aButton = [[CustomButtonClass buttonWithType:UIButtonTypeCustom]
initWithFrame:someFrame
imageArray:arrayOfImages];
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
// empty array
[arrayOfImages removeAllObjects];
// fill it with other images
for(int i = 0; i <= 7; i++) {
fileName = [NSString stringWithFormat:#"myOtherImage_%d.png", i];
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
}
// create another button with other images (same array)
aSecondButton = [[CustomButtonClass buttonWithType:UIButtonTypeCustom]
initWithFrame:someFrame
imageArray:arrayOfImages];
[arrayOfImages release];
For the sake of clarity, my button init method looks as follows:
- (id)initWithFrame:(CGRect)frame
images:(NSArray *)imageArray
{
if(self = [super initWithFrame:frame]) {
myImageArray = [[NSArray arrayWithArray:imageArray] retain];
}
return self;
}
I know I could just create a new NSMutableArray and be over with this issue but it annoys me not to be able to just reuse the old array. What could be the problem?
I'm getting some leaks (obvserved by
Instruments) when trying to reuse an
existing NSMutableArray (in order to
save memory).
An array takes a really small amount of memory; 4 bytes per pointer stored (on a 32 bit system) + a tiny bit of overhead. Reusing an array to attempt to save memory is a waste of time in all but the most extraordinary circumstances.
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
// empty array
[arrayOfImages removeAllObjects];
You didn't retain the objects and, thus, you shouldn't be releasing them! That your app didn't crash after the above indicates that you are likely over-retaining the objects somewhere else.
I know I could just create a new
NSMutableArray and be over with this
issue but it annoys me not to be able
to just reuse the old array. What
could be the problem?
There isn't anything in that code that springs out as a memory leak. Just the opposite; you are over-releasing objects.
And the above indicates that you really need to revisit the memory management guidelines as re-using an array versus releasing the array and creating a new one really doesn't have anything to do with this problem.
You don't need this part:
// release its objects
for(int i = 0; i < [arrayOfImages count]; i++) {
[[arrayOfImages objectAtIndex:i] release];
}
This is against the ownership rule. You didn't retain the images at
[arrayOfImages addObject:[UIImage imageNamed:fileName]];
so it's not your responsibility to release them. It's NSMutableArray that retains them when -addObject is called, and so it's NSMutableArray's responsibility to release them when -removeObject or others of that ilk is called. The leak you found might be because the system got confused by this over-releasing...
I would also recommend to perform "Build and Analyze" in XCode.
The Leaks instrument tells you where the object that leaked was first allocated. The fact one of the images was leaked means that you used the image somewhere else, and did not release it there - Leaks cannot tell you where since it cannot show you code that does not exist.
Indeed as others have pointed out this is somewhat surprising since the code as-is is over-releasing objects and should have crashed. But the fact it did not crash is a good sign somewhere else you are using the images from the array and over-retaining them.