I thought that NSArray/NSDictionary/NSSet and their mutable subclasses just added the pointer to the object, and not the object it self.
So if set my "simple" object to nil after I added it to the container, why isn't the reference nil also in the Array (container)?
Here is the code:
NSMutableArray *array = [[NSMutableArray alloc] init];
Simple *simple = [[Simple alloc] init];
[array addObject:simple];
//Array sends retain, lets release
[simple release], simple = nil;
NSLog(#"Simple = \"<Simple: %p>", simple);
NSLog(#"Array: %#", array);
[array release], array = nil;
Here is the output:
2011-02-16 20:00:03.149 Allocations[5433:207] Simple = <Simple: 0x0>
2011-02-16 20:00:03.150 Allocations[5433:207] Array: (
<Simple: 0x4d3d4e0>
)
NSArray adds a pointer to the object. In order to track changes to variable, the array would have to add a pointer to the variable itself (remember, you're just setting the variable to nil, not the object). There can be many variables all pointing to the same object, and reassigning them won't change any others.
Remember: Pointers aren't magic. They're just ordinary variables whose value is a memory address — in this case, the memory address of an object. Two pointers to the same object aren't "linked" any more than two ints with the value 5. Changing the pointer doesn't affect the object; in order to affect the object, you have to either send it a message that causes it to change (e.g. [object setValue:6]) or dereference the pointer to access the object's members directly (e.g. object->value = 6).
PS: Don't access an object's members directly. It's bad and fragile and very prone to bugs. I just mentioned it here to explain how pointers work.
Setting simple = nil just makes that pointer point to nothing. It doesn't delete the object that the array still has a pointer to. At the point of your NSLog statements, the retainCount of the Simple instance that simple pointed to would be one.
Create simple
simple => (Simple instance: retain count 1)
Add to array
simple => (Simple instance: retain count 2)
[array objectAtIndex:0] => (Simple instance: retain count 2)
Release simple
simple => (Simple instance: retain count 1)
[array objectAtIndex:0] => (Simple instance: retain count 1)
Set simple = nil
simple => nil
[array objectAtIndex:0] => (Simple instance: retain count 1)
Release array
(Simple instance: retain count 0, subsequently destroyed)
NSArray does contain only a pointer to the object that is added, but that's ok -- it's not pointing to the simple pointer itself, but rather to the Simple object that simple pointed to. Thus in your example, after you change what simple points to, the array is still pointing at the original Simple object.
Related
Given the following code:
NSMutableArray *a = [NSMutableArray array];
NSMutableArray *b = [NSMutableArray array];
[a addObject:b];
[b addObject:a];
What are the options to make the array objects deallocate when I set a and b to NIL?
Ive tried few things (Like weak references) but it doesn't seem to work.. (Probably because I don't understand enough - new to objective c ).
would love to get some assistance.
thank you
you can make an entire array not retain its elements with: CFArrayCreate()
this can be problematic in ARC if you no longer use the elements then iterate through the array later.
It's explained here: Non-retaining array for delegates.
You can use
+ (NSValue *)valueWithNonretainedObject:(id)anObject
And put the NSValue in the array.
Or do that:
NSMutableArray* NICreateNonRetainingMutableArray(void) {
return (NSMutableArray *)CFArrayCreateMutable(nil, 0, nil);
}
NSMutableDictionary* NICreateNonRetainingMutableDictionary(void) {
return (NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, nil, nil);
}
NSMutableSet* NICreateNonRetainingMutableSet(void) {
return (NSMutableSet *)CFSetCreateMutable(nil, 0, nil);
}
I'm not sure how you tried to add weak references to an array, but if it was something like this:
NSMutableArray *myArray = [NSMutableArray new]
__weak id weakObject = blah;
[myArray addObject:weakObject]
Then the fact that weakObject is declared weak makes no difference. It only makes the local reference to your object from the stack weak. In your situation, the array needs to hold a weak reference to the object, and NSMutableArray always holds strong references to its members.
There are several ways of doing this. CFArray allows you to specify how it should retain its members when you create it. Or you could 'box' the reference inside an NSValue, which although retained itself by the array, does not retain its contents.
If you're targeting Mac or iOS 6.0+, the best solution is to use a weak NSPointerArray (created using [NSPointerArray weakObjectsPointerArray]).
This is better than using CFArray or an NSValue inside a normal NSMutableArray because when the referenced object is deallocated, the array element will be automatically set to nil.
One solution to this kind of thing is to wrap your objects in a weak proxy object. Here is an example implementation of such an object:
https://github.com/j-h-a/jalib-core/blob/master/JALibCore/Helpers/JAWeakProxy.h
https://github.com/j-h-a/jalib-core/blob/master/JALibCore/Helpers/JAWeakProxy.m
Then you can do this:
NSMutableArray* a = [NSMutableArray array];
NSMutableArray* b = [NSMutableArray array];
[a addObject:[JAWeakProxy weakProxyWithTarget:b]];
[b addObject:[JAWeakProxy weakProxyWithTarget:a]];
Now both a and b are weakly referenced within the array. The good thing about using weak proxy objects is that you don't need to 'unbox' the real object inside them - you can just send messages (call methods) directly on the weak proxy object as if it were the target and it will pass the message along. For example:
[a[0] addObject:#(0)];
Notice how a[0] actually returns the weak proxy object holding b as its target, but I can sill send addObject directly to this weak proxy representing b (which is an NSMutableArray) and the implementation of the weak proxy will ensure that the message is forwarded to b.
However, you do lose some compile-time type-checking, so this technique is best used to help with the internal implementation of some class, which will have well-typed methods to access and enumerate the contents.
When I use such things I usually put in some auto-clean-up code into the array enumerators. That is I hide the arrays inside a utility class and provide block-based enumeration methods and I also keep a toRemove array. When iterating I skip any objects that the target has auto-zeroed to nil and add them to the toRemove array (the weak proxy object still exists even though its target is gone), then afterwards I iterate through the toRemove array and remove the proxy objects. You can also check if the target is nil and add it to the toRemove array in any accessor helpers.
I have a NSMutableArray holding a whole bunch of UITextFields which I have created and allocated memory.
In my viewDidUnload method I need to release this memory. How do I do it?
for(int i = 0; i < [arr count]; i++){
UITextField* txtField = [arr objectAtIndex i];
txtField = nil;
}
arr = nil;
Will this work? or do I just need to set arr = nil;?
I am using ARC so i set to nil not release.
If the array is an instance variable of the view controller, as long as you're using ARC it will automatically be deallocated when the view controller leaves memory.
If you need to manually remove the array, set it to nil.
arr = nil;
If you need to reuse the array later, you will need to reallocate it after setting it to nil.
Assuming your array is an #property of your object, a good trick is to allocate the array in the getter:
-(NSMutableArray*)arr {
if (!_arr) {
_arr = [[NSMutableArray alloc] init];
}
return _arr;
}
This way you always get an array when you try to access it, even if it's previously been set to nil.
Let's see what your code does to understand what happens:
UITextField* txtField = [arr objectAtIndex i];
that makes a copy of the value in the array, and values in an NSMutableArray are references, and stores that value in a variable txtField. As txtField is defined, implicitly, to hold strong references ARC will (subject to any optimisations) register an ownership interest in the reference (aka "retain"). Your next line:
txtField = nil;
stores the nil reference value in txtField. As txtField holds strong references any store causes ARC to relinquish ownership interest (aka "release") in the previous reference value stored in the variable.
The array is never changed. You've iterated over its contents, copied each value, retained that value, released that value. Finally you write:
arr = nil;
which stores the nil reference value in arr. As arr holds strong references any store causes ARC to relinquish ownership interest (aka "release") in the previous reference value stored in the variable - and that previous value was your reference to your NSMutableArray. If there are no other owners of the array it is destroyed, and when an array is destroyed it relinquishes its ownership of any values it contains - which in this case are your UITextField instances, and if there is no other owner of those then they are destroyed...
So at most all you need is:
arr = nil
but you may not even need that. As arr holds strong references when its lifetime ends - at the end of the block or method containing its declaration if a local variable, or when the instance is destroyed if an instance variable - then ARC will relinquish its ownership interest, etc...
HTH
When using ARC, you release a variable by setting it to nil just as you have done with arr = nil. Note that the memory will only be freed when all pointers to the object have been set to nil, so make sure you aren't holding on to them anywhere else.
Secondly, if you are running into memory issues you should be handling this in the didReceiveMemoryWarning method, as viewDidUnload is no longer supported on iOS 6.
i'm a newbie of the world of objective-c.
what i'd like to know is how to check whether a mutable array object exists.
here is an exmaple.
if(![appDelegate.answerList objectAtIndex:3])
{
answer = [[NSMutableArray alloc] init];
}
else
{
answer = [[NSMutableArray alloc] initWithArray:[appDelegate.answerList objectAtIndex:3]];
}
above this code, 'answer' object is local, and 'answerList' object is on appDelegate class.
both are NSMutableArray objects.
i don't know whether answerList's third object is allocated or not.
If it already has an object, i wanna just copy an object from answerList's third object.
But that code doesn't work.
I'm not familiar with objective-c's methods.
please show me the way.
You can check if an array is empty by comparing it's count property to a number (in this case, 4, because arrays are 0 based). And because arrays do not accept objects that are nil and send a -retain message to all of their objects, you would technically only need to compare count instead of object existence. However, you can nest your current if...else... blocks in this as well for much more accurate (if redundant) results.
if([appDelegate.answerList count] <= 4) //object exists, and the array contains a valid index.
I wrote a class, which acts as a filter. I pass three objects:
An NSArray, which holds objects to filter (these objects have a timestamp property)
An NSMutableArray (which will hold the section names for a tableView, the periods based on timestamps). I need this array, because I have to sort the periods.
An NSMutableDictionary, in which the keys will be the section names, the values are NSMutableArrays, which hold the items for a given period.
In the class from which I pass these objects, there is a tableView, in which I display the items.
This class has it own NSMutableArray and NSMutableDictionary, I not initialize them, only retain the corresponding return values of the filter class. In the delloc method I release them. There is a method in the filter class:
+ (void)insertItem:(id)item forPeriod:(NSString *)period toContainer:(NSMutableDictionary *)container {
if ( ![[container allKeys] containsObject:period] ) {
// the period isn't stored, create and store it
NSMutableArray *periodArray = [[NSMutableArray alloc] init];
[container setObject:periodArray forKey:period];
[periodArray release];
periodArray = nil;
}
// store the item
NSMutableArray *arrayForPeriod = [container objectForKey:period];
[arrayForPeriod addObject:item];
arrayForPeriod = nil;
}
The instruments shows me leak when I set the newly allocated array as an object of the dictionary. At this point this is definitely true, because the dictionary retains again the array, so after the release, it retain count remains 1. But I think in the caller class when I release the dictionary, the array will be released too. Am I wrong?
Yes it is considered as a leak because your var is a local variable. Then you still have an object in memory but no reference to it. Remember the init makes a retain + the retain made by the dictionary = 2 retains. Just create your array using
NSMutableArray *periodArray = [[[NSMutableArray alloc] init]
autorelease]
Is it clear ?
You could switch to ARC. Alternatively, check what the static analyser thinks of your code. It is pretty good at finding memory leaks, better than most humans.
Once you have a few hundred objects in your dictionary, you waste an awful lot of time and memory. A dictionary doesn't have an array of all keys stashed away somewhere, it has to create it every time you call your method. That's copying a few hundred pointers (cheap) and retaining them (expensive). containsObject for an array compares the object with every object in the array calling isEqual: That's expensive. It's an NSString compare each time. The array is autoreleased, and when it finally goes away, all the keys in it get released. Again expensive.
NSDictionary uses a hash table, so [objectForKey ] will immediately go to the right object. One operation instead of possibly hundreds.
In my app, the singleton class (SharedData) allocates memory for a NSMutableArray:
[self sharedMutableArray] = [[NSMutableArray alloc] init];
Class A populates the this sharedMutableArray:
NSObject *obj = [NSObject alloc] init];
[sharedMutableArray addObject];
obj = nil;
Class B does this - and that's my question:
NSMutableArray *tmpArray = sharedMutableArray;
... uses the tmpArray locally
[tmpArray removeAllObjects];
tmpArray = nil;
This is an inherited code and my hunch is that this is a NO-NO. Can some one confirm that assigning nil to tmpArray will release memory for sharedMutableArray also.... I guess the author wanted to release tmpArray only...
Assigning nil to tmpArray only sets your pointer to the object to nil. It does not affect the object itself (or its lifecycle) at all. In this case, setting the objects you've created to nil does nothing, since their variable declaration is in local scope - if you want the objects to be deallocated from memory you need to send them release before setting the pointer to the object to nil.
However, sending removeAllObjects is affecting your original sharedArray, because you didn't copy the array, you simply set a new pointer to point to the 'singleton'. You probably want this:
NSMutableArray *tmpArray = [NSMutableArray arrayWithArray:sharedMutableArray];
You won't need to use removeAllObjects in the above case because it will be autorelease'd. I suggest you read this.
tmpArray is a pointer, and it's initialized to point to the same mutable array that sharedMutableArray points to. For that reason, the line:
[tmpArray removeAllObjects];
will empty out the array, and anyone using sharedMutableArray will see that change. In other words, the assignment
NSMutableArray *tmpArray = sharedMutableArray;
doesn't make a copy of the array itself, it only copies the pointer. Any messages you send using that pointer will go to the shared array. Likewise, assigning nil to tmpArray sets the pointer tmpArray, but doesn't do anything to the array itself.
Finally, setting a variable to nil never releases memory. Setting a property to nil, on the other hand, will release memory under some conditions (e.g. when the property is declared to retain its contents). You're setting a variable here, not a property, so there's no chance that the array will be released.