Welcome to the MacNN Forums.

If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

You are here: MacNN Forums > Software - Troubleshooting and Discussion > Developer Center > [Obj-C 1.0] Setter Methods and Memory Management

[Obj-C 1.0] Setter Methods and Memory Management
Thread Tools
subego
Clinically Insane
Join Date: Jun 2001
Location: Chicago, Bang! Bang!
Status: Offline
Reply With Quote
Jul 27, 2010, 12:49 PM
 
The standard setter method is kind of blowing my mind here.

Code:
-(void)setValue:(NSString *)newValue { [retain newValue]; [release value]; value = newValue; }
Let's say you invoke the setter with:

Code:
[anObject setValue:foo]

What's confusing me is how the retain/release balances out. Does the balance occur because "value" and "newValue" both end up pointing to the same object that "foo" points to? IOW, does [retain newValue] increment the release count of "foo" (or more accurately, what "foo" is pointing too) just like [retain foo] would if it were called in a different method?

If this is the case, am I correct in assuming that once the setValue method has been invoked in this example, if you later on change the value of the object that "foo" points to you will also change the value of the object "value" points to without needing to reinvoke the setter?

Is all this even half right?
( Last edited by subego; Jul 27, 2010 at 01:52 PM. )
     
kido331
Junior Member
Join Date: May 2007
Status: Offline
Reply With Quote
Jul 27, 2010, 02:02 PM
 
Well, yes and no. The setter method is doing pretty much what you think it is doing in that it is setting an ivar (in this case "value") to whatever NSString object you pass into the method (in this case 'foo'). I think the main confusion is that more memory management is done in the -init and -dealloc methods. So,typically you'd have the following:
Code:
@interface MyObject : NSObject { NSString *value; } - (NSString *)value; - (void)setValue:(NSString *)newValue; @end @implementation MyObject - (id)init { if(self = [super init]) { value = nil; //or whatever you want to initial value to be. //should be from either an init, copy, or retain } return self; } - (void)dealloc { [value release]; value = nil; [super dealloc]; } - (NSString *)value { return value; } - (void)setValue:(NSString *)newValue { [newValue retain]; [value release]; value = newValue; } @end
So, if in your code you create a MyObject object

Code:
MyObject *obj = [[MyObject alloc] init]; //value is nil from the init method. //obj's retain count is 1 from init [obj setValue:@"hello"]; //value is @"hello" which is an autoreleased NSString. //@"hello" has retain count 1 from setter. [obj setValue:@"there"]; //value is @"there" which is an autoreleased NSString. //@"there" has retain count 1 from setter //@"hello" has been released from setter, //so it's retain count is now 0 and can be dealloc'd NSString *foo = @"dude"; [obj setValue:foo]; //value is now 'foo' which is @"dude" an autoreleased NSString. // foo has retain count 1 from setter //@"there" has been released from the setter // so it's retain count is now 0 and can be dealloc'd [obj release]; // obj retain count is 0, so it is dealloc's which sends a release message to // value which is foo which is @"dude", so @"dude" has retain count 0 and can also be // dealloc'd
So, the balance comes from the dealloc method of MyObject doing the final release for whatever is in value when it is dealloc'd.

One other small point, NSString objects are immutable once they are created, so you cannot change it and have value reflect that change

So,
Code:
NSString *s = @"hello"; [obj setValue:s]; //value is @"hello" s= @"there"; [obj value]; // returns @"hello"
But, if you were to use the NSMutableString class, then it would work.
Code:
NSMutableString *ms = [[@"hello" mutableCopy] autorelease]; [obj setValue:ms]; //value is @"hello" [ms setString:@"there"]; [obj value] // returns @"there"
     
Atheist
Mac Elite
Join Date: Sep 2006
Location: Back in the Good Ole US of A
Status: Offline
Reply With Quote
Jul 27, 2010, 03:11 PM
 
There's a reason I like garbage collection
     
BLAZE_MkIV
Professional Poster
Join Date: Feb 2000
Location: Nashua NH, USA
Status: Offline
Reply With Quote
Jul 27, 2010, 03:23 PM
 
There's a reason I like garbage collection
In C# we get to deal with finalizers and IDisposable instead. With one I have no idea when or if it happens (and no way to stop it from interfering with time critical operations.) And with the other I have to check the type definition to see if I need to manually call the "destructor".
     
subego  (op)
Clinically Insane
Join Date: Jun 2001
Location: Chicago, Bang! Bang!
Status: Offline
Reply With Quote
Jul 27, 2010, 04:15 PM
 
Originally Posted by kido331 View Post
Well, yes and no. The setter method is doing pretty much what you think it is doing in that it is setting an ivar (in this case "value") to whatever NSString object you pass into the method (in this case 'foo'). I think the main confusion is that more memory management is done in the -init and -dealloc methods.
Remarkably, I get that part. It was definitely the balance inside the setter method that I've had to puzzle through. When I see that setter method I go "wait, wait, wait... 'newValue' never gets released, and 'value' will get dealloced with the second call to the method."

Obviously that isn't what happens. Thank you for walking me through it step by step.


Originally Posted by kido331 View Post
One other small point, NSString objects are immutable once they are created, so you cannot change it and have value reflect that change
Whoops! More n00bery. I just grabbed the first object I could think of
     
gperks
Dedicated MacNNer
Join Date: Oct 2003
Location: Round Rock, TX
Status: Offline
Reply With Quote
Jul 30, 2010, 04:05 PM
 
The setter method is going to change which object 'value' points to.

1. It needs to call 'release' on any object it used to point to, since we're not referencing it any more.
2. It needs to call 'retain' on the new object, so nothing will free the object while we're using it.
3. It needs to update 'value'.

Pretty easy, makes sense. But why does the method switch the order of 1 and 2? Because some caller might be calling the setter with the object it already refers to. If that were the case, step 1 would release the object, and it might get totally freed if the retain count goes to zero. Then we retain it in step 2 - too late, it was already freed. Pretty soon, kaboom.

So we retain first. Then release. Just for safety.


-(void)setValueNSString *)newValue
{
[retain newValue];
[release value];
value = newValue;
}
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 2, 2010, 04:14 AM
 
The thing I don't like about that particular form is that often you want to use -copy instead of -retain for a setter such as this, for the simple reason that as a subclass of NSString, an NSMutableString would be a perfectly valid thing to send to that setter, which would then get stored in your object, leaving the door wide open for something else to modify the string without your object expecting it, resulting in who knows what kind of weird behavior. Using -copy instead prevents that, and as a bonus, if the string is immutable, -copy just retains and returns the same object, so there's no loss of efficiency over using -retain. The trouble is, since -copy can return a different object than you started with, to use it with the "retain first" form, you'd have to throw another temporary variable into the mix, which is relatively messy.

Here's the form I usually use:

Code:
-(void)setValue:(NSString *)newValue { if(value != newValue) { [value release]; value = [newValue copy /*or retain*/]; } }
FWIW, this form is what Apple seems to use in its developer documentation and in its synthesized properties.

Of course, if you are able to require Leopard or better, synthesized properties are much less of a pain in the rear-end any way you look at it.

Originally Posted by Atheist View Post
There's a reason I like garbage collection
Meh, I don't. I much prefer to follow a standard, predictable set of rules and know what to expect. With GC on you have to constantly anticipate every little place where the runtime might decide to dispose of something before you're done with it and cause a crash, especially since the only way to be 100% sure about it would be to thoroughly read the source code to the garbage collector (and then it could change). Of course, perhaps I'm just being an old fogey.
( Last edited by CharlesS; Aug 2, 2010 at 04:30 AM. )

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
Atheist
Mac Elite
Join Date: Sep 2006
Location: Back in the Good Ole US of A
Status: Offline
Reply With Quote
Aug 2, 2010, 07:55 AM
 
Originally Posted by CharlesS View Post
With GC on you have to constantly anticipate every little place where the runtime might decide to dispose of something before you're done with it and cause a crash, especially since the only way to be 100% sure about it would be to thoroughly read the source code to the garbage collector (and then it could change).
I've never once experienced this. What would be the point of GC if you couldn't trust it's accuracy?

I've written a download manager program that I have running on my system 24/7. I rely 100% on GC. Not a single memory leak or crash.
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 2, 2010, 06:17 PM
 
Well, here's one example that I picked up from the cocoa-dev list:

Code:
NSData *data = [self getSomeData]; NSUInteger length = [data length]; const unsigned char *bytes = [data bytes]; // this is the last usage of data in this method; after this it can be deallocated for(NSUInteger i = 0; i < length; i++) { unsigned char someByte = bytes[i]; // it is entirely possible that data, and therefore, bytes, could be deallocated at any time during this loop, causing the above variable to contain garbage data, or causing a crash }
To really be safe while using GC in Cocoa, you have to follow up things like this by sending something to data after you're done with bytes, even if it's something that doesn't do anything like [data self]; — else, data can get reaped. This is one example, but the point is that when you are using GC, you have to constantly be thinking about what the collector might do and code defensively against it. I personally find plain old retain and release to be simpler. To each his own, of course.
( Last edited by CharlesS; Aug 2, 2010 at 06:25 PM. )

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
subego  (op)
Clinically Insane
Join Date: Jun 2001
Location: Chicago, Bang! Bang!
Status: Offline
Reply With Quote
Aug 7, 2010, 01:00 PM
 
I'm going to threadjack myself and ask a different setter question.

Let's say I have a class "Shirt", which has "size" (an int) and "sizeInText" (an NSString *). I only want to interact with "size". If I change "size" I want "sizeInText" to be programmatically changed to match with the new value.

Is there a generally accepted philosophy for what is the best way to do this? What makes most sense to me is to have a (pseudo-private) setter method for "sizeInText", which gets called by the setter method for "size". A different option would be to put all the setter code for "sizeInText" inside the setter code for "size". That seems safer, but could get messy if you have a bunch of things which get changed when "size" gets changed. Is there an even better third option?
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 7, 2010, 01:16 PM
 
Best way to do that is probably not to store sizeInText as an instance variable. Have the getter for "sizeInText" generate the return value from the "size" variable. Then implement this method so that the bindings system knows that sizeInText has been updated when size is changed, like so:

Code:
+ (NSSet *)keyPathsForValuesAffectingSizeInText { return [NSSet setWithObject:@"size"]; }
Of course, if sizeInText is just a formatted version of the file size, for example displaying the size 123,456,789 as "117.7 GiB" or something, then you probably don't need the sizeInText property at all. Just make an NSFormatter subclass that converts a raw byte size to the formatted version, and vice-versa. It's not too difficult to write, and you can then reuse the class in other projects. If you do it this way, you can just attach the formatter to the text field or cell that you're viewing the size in in Interface Builder, and your actual code doesn't need to know about the formatted size at all. This way you can just have the 'size' property and everything's nice and simple. This is what I do with my file sizes in Pacifist, and the class has proven to be very handy to have around for other (unreleased) projects.
( Last edited by CharlesS; Aug 7, 2010 at 01:24 PM. )

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
subego  (op)
Clinically Insane
Join Date: Jun 2001
Location: Chicago, Bang! Bang!
Status: Offline
Reply With Quote
Aug 7, 2010, 01:26 PM
 
Thank you! I am now trying to figure out what the hell you just told me.

I'll probably have another question, but I'll work to make it a good one.
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 7, 2010, 02:22 PM
 
Here's the documentation for NSFormatter, which should help.

NSFormatter Class Reference

And here's Apple's quite brief guide to making one:

Data Formatting Guide: Creating a Custom Formatter

It's fairly simple, there are only three methods you need to override - one to convert from the actual value (in this case, the size) to an NSString, the other one to parse an NSString and return the value, and a third method to return a formatted version of the size (if you don't need special formatting, you can just return [[[NSAttributedString alloc] initWithString:[self stringForObjectValue:obj] attributes:attrs] autorelease];&#41;. It's pretty straightforward to write a formatter for file sizes (which is what I suspect you're doing). To use it, just instantiate your formatter in Interface Builder, and then control-drag the text field, cell, or whatever will be displaying the value over to your formatter instance and connect the "formatter" outlet.

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
subego  (op)
Clinically Insane
Join Date: Jun 2001
Location: Chicago, Bang! Bang!
Status: Offline
Reply With Quote
Aug 7, 2010, 02:27 PM
 
^^ Oooh... The edit makes things extra helpful. I was just going to write the formatter as a method of Shirt. Excellent. Many thanks!

Edit: and the next post! Wow! Fantastic!
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 7, 2010, 02:30 PM
 
Yup, and the next time you need the same kind of formatter in some other project, all you have to do is drop in your ready-made source files for this one et voilà.

Using an NSFormatter keeps the MVC separation nice and clear, saves you the hassle of keeping two properties synced, and keeps your Shirt object from having to know (or care) about how its properties are being displayed. Win-win all around.
( Last edited by CharlesS; Aug 7, 2010 at 02:37 PM. )

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 7, 2010, 02:56 PM
 
Oh, one other thing: If your needs for displaying are fairly simple, like appending some string to the end of the number or something, then you can do this even simpler than an NSFormatter, by using the "Display Pattern Value" binding in Interface Builder. This all depends on how complex your needs are — my original assumption was that you were doing something like a file size, where you'd have to distinguish between KiB, MiB, GiB, etc., and which would be a more complex problem. If you're just doing something like tacking "inches" on the end of it, you can do simple things like this just via bindings. Another thing worth looking at is the built-in NSNumberFormatter class included with Cocoa.

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
Catfish_Man
Mac Elite
Join Date: Aug 2001
Status: Offline
Reply With Quote
Aug 13, 2010, 04:47 PM
 
Originally Posted by CharlesS View Post
Well, here's one example that I picked up from the cocoa-dev list:

Code:
NSData *data = [self getSomeData]; NSUInteger length = [data length]; const unsigned char *bytes = [data bytes]; // this is the last usage of data in this method; after this it can be deallocated for(NSUInteger i = 0; i < length; i++) { unsigned char someByte = bytes[i]; // it is entirely possible that data, and therefore, bytes, could be deallocated at any time during this loop, causing the above variable to contain garbage data, or causing a crash }
To really be safe while using GC in Cocoa, you have to follow up things like this by sending something to data after you're done with bytes, even if it's something that doesn't do anything like [data self]; — else, data can get reaped. This is one example, but the point is that when you are using GC, you have to constantly be thinking about what the collector might do and code defensively against it. I personally find plain old retain and release to be simpler. To each his own, of course.

This is incorrect. Stack references to an object root the object, so the NSData would not be collected until after that function returned. Similarly, in retain/release code, you would have to guard against the current autorelease pool being drained.

<edit>
Ah, I think I see what you're getting at. You're suggesting that the compiler could potentially overwrite that stack slot with another variable later in the function? That is an interesting case, although I'd say both retain/release and GC have a lot more immediate pitfalls than that. (examples: RR: retain loops when using blocks due to implicit retain of self on capture. GC: undefined order of finalizers, so an ivar could be finalized before its containing object)
</edit>
( Last edited by Catfish_Man; Aug 13, 2010 at 05:28 PM. )
     
CharlesS
Posting Junkie
Join Date: Dec 2000
Status: Offline
Reply With Quote
Aug 16, 2010, 04:03 PM
 
Yes, it can reuse the stack slots. If you don't believe me, believe Apple's documentation:

Garbage Collection Programming Guide: Using Garbage Collection

And yes, there are other issues with GC, that was just an easy-to-remember example. I myself prefer to use retain/release, because I find it easier to know exactly what will happen in any given situation. To each his own, of course.

Ticking sound coming from a .pkg package? Don't let the .bom go off! Inspect it first with Pacifist. Macworld - five mice!
     
   
 
Forum Links
Forum Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Top
Privacy Policy
All times are GMT -4. The time now is 10:29 AM.
All contents of these forums © 1995-2017 MacNN. All rights reserved.
Branding + Design: www.gesamtbild.com
vBulletin v.3.8.8 © 2000-2017, Jelsoft Enterprises Ltd.,