views:

119

answers:

7

So, let's say you have a local variable NSArray *myArray declared in your class header file.

You then write @property (nonatomic, retain) NSArray *myArray also in your header file.

In your .m file, you write @synthesize myArray.

All very standard so far. You now have a variable myArray, which can be accessed through setters and getters synthesized by Apple.

A little bit later, you initialise your variable.

NSArray *anArray = [[NSArray alloc] initWithObjects etc etc...];
self.myArray = anArray;
[anArray release];

So now myArray is pointing to an array in memory, which has a release count of one (if I'm not mistaken).

My question is, why can't we write

@property (nonatomic, assign) NSArray *myArray;
@synthesize myArray;

..and then by initialisation write

self.myArray = [[NSArray alloc] initWithObjects etc etc...];

This has TOTALLY confused me ever since the first time I saw it. Is there a technical reason for this? Or moral? ;-) Or theoretical?

Any help would be MUCH appreciated...

Cheers

Karl...

A: 

There is no reason why you can't do that. You just have to pay some extra attention to your memory.

Because what happens when you later assign to the property again?

Using your example:

@property (nonatomic, assign) NSArray *myArray;
@synthesize myArray;

...

self.myArray = [[NSArray alloc] initWithObjects: @"foo", nil];
self.myArray = [[NSArray alloc] initWithObjects: @"bar", nil]; // MEMORY LEAK!

In this case you would have to manually release your ivar by calling release on it. If you do not, you will have leaked the memory.

Another smart thing about having it retained (or copied, less bug prone) it that you can say:

self.myArray = nil;

This will release the variable AND set the reference to nil, so you avoid getting yourself into trouble.

I absolutely see your point though. It is alot more verbose to have to write 3 lines instead of one. You can as @willcodejavaforfood suggests use autorelease when you are assigning to retained properties, as he seems to have missed). But Apple suggests that on the iPhone you do as little autoreleasing as you can, and we always listen to apple like good little children.

Update:

When you specify a property as (nonatomic, assign) an synthesize it the setter code that is generated looks something like this:

- (void)setMyArray:(NSArray *)newValue {
    myArray = newValue;
}

If you on the other hand define it as (nonatomic, retain) you get:

- (void)setMyArray:(NSArray *)newValue {
    if (myArray != newValue) {
        [myArray release];
        myArray = [newValue retain];
    }
}

Hope it clears things up.

Thomas Børlum
So what's the difference then, if I had done as Apple suggested with retain etc, and then later reallocated the variable as follows NSArray *aSecondArray = [[NSArray alloc] initWithObjects: @"bar", nil]; self.myArray = aSecondArray; [aSecondArray release]; I'm not calling release here on self.myArray either, so why isn't this a memory leak?
Karl Griffiths
Because when you are declaring a property as `retain` the generated property's code will call release on the previous instance (if it is not nil) before assigning the new value.
Thomas Børlum
Yes, but I'm NOT declaring it as retain. I'm declaring it as assign...
Karl Griffiths
The example setter method shown above is incorrect. First, there's no need to check for `nil`, but there is a need to check that the argument is not identical to the instance variable to avoid a potential crasher bug. Second, the assignment should not take place if the test fails. Third, it never calls `-retain`.
jlehr
@jlehr, you are completly right, my bad. But In my defence I did write "something like this". @Karl I know, but you asked what the difference between the two assignments were, so I specified the difference. And why It can be dangerous using assign for object ivar, iff you are not careful.
Thomas Børlum
+1  A: 

You can.

I mean, it's what I'm doing in my program because I don't like using retain property ^^

It doesn't work ? what is the error ?

By the way you can just write

myArray = [[NSArray alloc] initWithObjects etc etc...];
Vinzius
+4  A: 

One of the points of properties is to ease us from having to think about memory management ourselves. Making the property assign and then assigning a retained object into it kind of defeats the purpose of using the property.

It's really simple to do:

@property (nonatomic, retain) NSArray * myArray;
@synthesize myArray;

self.myArray = [NSArray arrayWithObjects:etc, etc1, etc2, nil];

And then all the memory management is taken care of for you.

Dave DeLong
True, but you are using a factory method returning an autoreleased instance. This is suggested by apple that we use autoreleased variables as little as possible. Granted that is is mostly a problem in situations where you have large amounts of autoreleased instances like when parsing an XML document, but it is a good rule of thumb. You can of course also use autorelease pool if it becomes a problem but that is overkill in most cases.
Thomas Børlum
No no, I meant instead of writing @property (nonatomic, retain) write @property (nonatomic, assign)
Karl Griffiths
@Thomas red herring argument, since the object will be retained by the property anyway, which means that it being autoreleased doesn't matter. It will still continue to exist.
Dave DeLong
@Karl your question was "why not use `assign` and then retain manually?", and my answer was "because it's more work"
Dave DeLong
Not completely fishy since the main autorelease pool still has to handle the instance and call release on it. This is of course micro-optimization, I do realize that.
Thomas Børlum
@Thomas don't optimize unless you've observed and profiled it being a bottleneck. And the documentation says that you should avoid using autorelease for the purpose of "never be[ing] lazy about releasing memory". Since that's not an issue here (the memory *will* be retained), it is completely "fishy". :)
Dave DeLong
Documentation on the "avoid autoreleased objects" stuff: http://developer.apple.com/library/ios/documentation/Performance/Conceptual/ManagingMemory/Articles/MemoryAlloc.html#//apple_ref/doc/uid/20001881-102725
Dave DeLong
@DaveDeLong, no actually my question was simply why not just use assign. I didn't mention anything about using retain manually in my second example.
Karl Griffiths
@Thomas Børlum: The idea that we should avoid autorelease at all costs is taking a bit of good advice way too far. Apple just means not to be profligate with system resources. Autorelease is *fundamental* to Apple's frameworks, and avoiding it where it's the best solution just leads to worse code. In a case like this (where we know the object will stick around anyway), the cost of using autorelease vs. release is hardly even measurable.
Chuck
@Karl it's the same thing; if you have an ivar that you want to hold an owned object, you can either 1) use a `retain` property or 2) use an `assign` property and "own" the object yourself (either by `alloc`/`init` or `retain` or `copy`, etc). #2 is more work and therefore more prone to errors. But there's nothing *wrong* with it. Just unconventional.
Dave DeLong
@Chuck, @Dave, hey I know. That's why I called it micro-optimization which you should avoid. But the Karl did ask what the difference was.
Thomas Børlum
+1  A: 

You can write:

self.myArray = [[[NSArray alloc] initWithObjects etc etc...] autorelease];

(note the addition of the autorelease)

Though it would be simpler to write:

self.myArray = [NSArray arrayWithObjects etc etc...];

Purists might argue that you shouldn't put things into an autorelease pool unless you really need to, however if it makes your code simpler I say go for it, the performance overhead is negligible in many/most cases.

If you use an assign property instead, you need to make sure you release the old contents of myArray yourself, which negates much of the advantage and simplicity.

JosephH
A: 

You definitely can.

Using "assign" properties instead of "retain" properties is actually a common practice (see some core object header files from Apple for examples). The issue here is your code being aware of this memory relationship (if the property has something in it at any given time).

Some programmers prefer this pattern, in fact. Complete personal control of memory.

I would add, however, that it is a very difficult pattern to protect when there are multiple developers on a project unless they are all the types that like manually managing memory. It's much easier to leak memory in this pattern from a simple oversight and compilers have a tougher time interrogating such problems.

bladnman
Thank you, that's what I thought. It would seem that the only reasons against doing this are those which you mentioned (difficulty of management etc), but TECHNICALLY the two are identical. Finally I can sleep at night :-)
Karl Griffiths
This really glosses over the technical issues with the technique. It is not just a case of "when there are multiple developers". There are always at least two parties at work in your code — you and Apple. And Apple has made its memory management guidelines very explicit.
Chuck
Guidelines are just guidelines, not programming errors.
hotpaw2
@hotpaw2: Bad design is bad design, not a valid lifestyle choice. If a design decision has nothing but downsides, answering a question as to whether it's inadvisable simply by noting that it is possible glosses over some *very important* information — it's a very shallow answer, and not very helpful. And perhaps I was too diplomatic — Apple calls them the "memory management rules".
Chuck
+1  A: 

The short answer is that using assign will probably result in memory leaks. Unless you're very careful.

By declaring the array property as retain, you are indicating that the object should take ownership of the array by sending it a retain message and, more importantly, that it should send it a release message when it is no longer interested in keeping the array around. When you use assign, the object won't send the array any retain or release messages. So, in the example you give, there isn't a problem YET. You've created an array with a retain count of one (conceptually) and given it to your object. In this case, the array hangs around in memory with a retain count of 1 just as it would have if you'd used the retain attribute when declaring the property.

The problem comes when you want to change the value of myArray. If your property is declared with retain, an assignment will do something like this:

- (void)setMyArray:(NSArray *)newArray {
    if (myArray != newArray) {
        [myArray release];    // Old value gets released
        myArray = [newValue retain];
    }
}

The old myArray gets sent a release message indicating that the object is done with it. If the retain count of myArray drops to zero, it will get deallocated and its memory reclaimed. If the property is declared with assign, this basically happens:

- (void)setMyArray:(NSArray *)newArray {
    myArray = newArray;
}

The object forgets about the array at myArray without sending it a release message. Therefore, the array previously referred to by myArray probably won't get deallocated.

So, it's not the assignment that's a problem. It is the failure to release the array during reassignment that will cause the memory leak. This might not be a problem if another object owns the array.

If another object owns the array, and the array is just being referenced by myArray, that other object is in charge of making sure the array stays around as long as myArray needs it and of releasing the array when it's no longer needed. This is the pattern typically used for delegates. You then have to be careful that you don't access myArray after that other object has released the array it references.

Essentially, this comes down to the question of who owns the array referenced by myArray. If another object owns it and will handle retaining and releasing it as needed, it's perfectly okay for your object to simply reference it. However, if your object is the owner of myArray (and will be releasing it in dealloc), it makes more sense to use the retain attribute. Otherwise, in order to avoid leaks, you'll require other objects to release the contents of myArray prior to calling your object's setter, since your assign setter won't do it for you.

James Huddleston
Hi James, surely when I'm using self.myArray the setters and getters from Apple will take care of all that. I agree that had I said simply myArray = [[NSArray alloc] init etc..] THEN I would have had to manage releases myself...
Karl Griffiths
Actually, the memory management techniques used by synthesized setters and getters *does* depend on the memory management attributes (`assign`, `retain`, `copy`) you specify in your `@property` declaration. That's precisely what they're there for. It's the way you tell the compiler how you want your setters and getters to handle memory. If you specify `assign`, no memory management is done for you. That's great for ints and floats, but usually not what you want for objects. If you specify `retain`, `retain` will get sent to objects on assignment and `release` sent to previous values.
James Huddleston
Oh I see. Well I didn't know that, thanks for pointing that out. So basically then when a property is declared using (assign) there's absolutely NO difference (as far as the setters and getters go) between self.myArray = .. and myArray = ..?
Karl Griffiths
You are correct! When a property is declared using `assign`, there is NO difference (in terms of memory management) between `self.myArray = ...` and `myArray = ...`. Using `self.myArray = ...` on an `assign` property will not send any `retain` or `release` messages to anything. There is a difference between using `self.myArray = ...` and `myArray = ...` though -- `self.myArray = ...` will send out change notifications whereas `myArray = ...` will not. Check out the "Declared Properties" section of Apple's "The Objective-C Programming Language" guide for more information.
James Huddleston
+1  A: 

Memory management in Cocoa (and Cocoa Touch) is very strongly based on conventions. One of those conventions is that objects take ownership of other objects they need to keep around, which means that they must properly retain (to claim ownership) and release (to relinquish ownership) those objects. If you make it an assign property and require every caller to handle the memory for you, this violates the memory management conventions.

It's also poor program design, because rather than have one place (the setter) that is concerned with managing that property, instead you spread the responsibility to every place that accesses the property. Clear separation of concerns is one of the most important aspects of design.

In short: You can do it the way you're asking about. It's just worse in every respect. It violates the assumptions Cocoa makes, it makes bugs more likely, it complicates your design and it bloats your code.

However, in cases where you're setting properties of self, you can do something like what you want. Instead of writing self.someProperty = [[NSString alloc] initWithString:@"Foo"], you can just write someProperty = [[NSString alloc] initWithString:@"Foo"] (assuming someProperty is the underlying instance variable). This is, in fact, the normal way to do it in an initializer method or a dealloc method. This allows you to simply assign the variable in the internal implementation of your class without requiring everybody who uses the class to do the class's memory management for it.

Chuck
Hi Chuck. How does making the property assign require that every caller handles the memory for me? It's STILL being accessed through a setter and getter generated by Apple.
Karl Griffiths
@Karl Griffiths: An `assign` property *by definition* does not do any kind of memory management. It just assigns the value to the instance variable, no retain or release involved — that's what `assign` means. The setter `@synthesize` creates for an `assign` property follows this contract. So if `someProperty` is declared `assign` and you write `object.someProperty = [[NSString alloc] initWithString:@"Foo"]`, then whatever value used to be in `someProperty` is leaked. The caller would have to release the old value first. Thus, the caller is required to handle the object's memory for it.
Chuck
Excellent! :-) Now I understand this better. So what would happen then if the property were declared assign, and then you said this... self.myArray = nil;? Would that ALSO be a memory leak?
Karl Griffiths
@Karl Griffiths: If the instance variable is already nil, then there's no leak. If the instance variable pointed to some object before you set it to nil, there would be a leak.
Chuck