PDA

View Full Version : @property NSString* whatsTheProblem?




zeppenwolf
May 8, 2011, 01:22 AM
I'm reading the Steinberg book. In the part where he's describing how turning GC on affects property declarations, specifically where an immutable class like NSString is extended my a mutable one and that creates a problem, he says:

> An NSString is immutable, so you can use copy but not retain...

I don't get it. So it's immutable, so what? Why shouldn't I be able to retain it?

?? Thanks.



Catfish_Man
May 8, 2011, 02:16 AM
I'm reading the Steinberg book. In the part where he's describing how turning GC on affects property declarations, specifically where an immutable class like NSString is extended my a mutable one and that creates a problem, he says:

> An NSString is immutable, so you can use copy but not retain...

I don't get it. So it's immutable, so what? Why shouldn't I be able to retain it?

?? Thanks.

-retain and -copy do the same thing for immutable strings. You can use either, but using -copy guards you against mutable strings cast to NSString*.

zeppenwolf
May 9, 2011, 10:54 PM
-retain and -copy do the same thing for immutable strings.

Thanks for responding, Catfish, but I have to say that your explanation, from my viewpoint, is extremely circular, logic-wise. You've answered the question, but the answer is just as opaque to me...

You can use either, but using -copy guards you against mutable strings cast to NSString*.

...and this part I don't even begin to grok.

Is this one of those things that I just need to figure out for myself? Like maybe if I just keep reading, Steinberg will explain it all later in the book?

Unless I hear otherwise, I guess I will assume it is so...

kainjow
May 10, 2011, 12:41 AM
If you use copy on an NSString, even if it's really an NSMutableString behind the scenes, you will be guaranteed to get back an immutable NSString. If you only use retain, then you're just referencing the original object, and that object's contents could change, introducing problems with your code.

jiminaus
May 10, 2011, 12:47 AM
I'm bored at work (but don't tell my boss), so I'll take a stab at an explanation.

There are rhetorical questions littered through the explanation. Try to stop and answer the question before reading on.

As you know retain increments the retain count of an object so that it isn't deallocated while you still hold a pointer to it.

Copy, naturally, copies an object. What's the goals of copying an object instead of just retaining it? The goal is to have an independent copy from the original so that if the original changes, the copy you have doesn't, or vice-versa.

Now think about an immutable class like NSString. If I was implementing the copy method of NSString, I could actually copy the NSString object so that the object exists twice in memory. But what's the point? A NSString object can never change. I can give you a pointer to the same object and you'd be just as happy as with a complete copy. Why? Because the object you have will not change on you, because it can't change. Similarly you can't change the object on someone else, because it can't change.

The only thing I'd need to do is make sure I retain the object before I return it back to you when you call my NSString copy. Why? Because of the Cocoa memory rules. If you call copy, you own the returned object and so are responsible for calling release. I need to ensure your call to release doesn't deallocate the object if there are other co-owners. So I match the expected future release with a retain.

That is why retain and copy do the same thing for NSString. It's an optimization.


Now think about creating your own class that has an NSString* ivar or property. You can assume an NSString is immutable and it won't change, right, because NSString is immutable? So you could do something like cache the length of the string into another ivar, right?

Well, not always. When is an NSString* not immutable? When the pointer is actually to an NSMutableString object.

If I used your class described above and set the property to a NSMutableString it would work because NSMutableString is subclass of NSString. By way of the substitution principle of OOP, I can assign a pointer to a subclass object to a pointer to a superclass type. Your class would go ahead and cache the length of the string I gave you. But I would break your class if I changed that NSMutableString; perhaps innocently, perhaps deliberately.

How can you guard against this? How can you be sure you actually have an NSString and not an NSMutableString? Make your sure you use a copy property or you call copy when you set the ivar. Don't use retain. By way of convention (not enforcement), when there's an immutable version of a class and a mutable subclass, copy will return an immutable copy.

This is way Steinberg is saying you should use copy and not retain. It's not right that you can't use retain. It's just that you better off in terms of robustness to use copy.

Catfish_Man
May 10, 2011, 02:50 AM
Thanks for responding, Catfish, but I have to say that your explanation, from my viewpoint, is extremely circular, logic-wise. You've answered the question, but the answer is just as opaque to me...



...and this part I don't even begin to grok.

Is this one of those things that I just need to figure out for myself? Like maybe if I just keep reading, Steinberg will explain it all later in the book?

Unless I hear otherwise, I guess I will assume it is so...

Suppose "someone" using your code (probably you, 6 months later, having forgotten not to) passes a mutable string into your object, then continues changing it afterwards. Your object now has this string that, unbeknownst to it, is changing out from under it. Probably not what you wanted.

So, if you -copy it, then if it's mutable, you'll get a nice fresh string that's safe against that problem. If it's *not* mutable, then it's just as fast as if you called -retain.

Given that, why not -copy?

zeppenwolf
May 11, 2011, 10:55 PM
Ok, guys, thanks for your patience. I DO get it now, at least, I get 95% of what you all have written....

I hope you can understand why I was confused: on the surface of it, or perhaps, "at first glance", the idea that an immutable object should be copied rather than retained is just goofy. It's alot like saying that a global variable needs to be passed to a function as a parameter...

That an immutable parameter could in fact be mutable, which leads us to the necessity of copying it to ensure that it is in fact immutable... The *net result* is almost completely bass-ackwards from what my initial reaction was-- it's really a bit of a paradox. See what I mean? But I do get it now.

Anyway: sorry, but I hope you understand why I couldn't get past that one sentence in the book. If you have any patience left, I would like to be certain I understand the following, where I think jiminaus took one step beyond:

That is why retain and copy do the same thing for NSString. It's an optimization.

So, you are saying that the runtime environment actually "cheats"? ( Or maybe "cuts corners"? ) That when it encounters an immutable parameter copy(), it does a runtime ISA() test and if it's really immutable then it just gives me another pointer to the object and tells itself to release after the function?

I'm bored at work

Then clearly something is wrong. Work is exciting and self-fulfilling...

Sydde
May 11, 2011, 11:25 PM
So, you are saying that the runtime environment actually "cheats"? ( Or maybe "cuts corners"? ) That when it encounters an immutable parameter copy(), it does a runtime ISA() test and if it's really immutable then it just gives me another pointer to the object and tells itself to release after the function?

Why would you consider it cheating to not do unnecessary work?

There are two separate protocols for copying, NSCopying and NSMutableCopying. The rules for them are:

- If a class has both mutable and immutable subclasses (NSString, NSArray, NSData, etc), it must provide an immutable instance when -copy is called and a mutable instance for -mutableCopy .

- Any class that does not have mutable and immutable subclasses need only adopt NSCopying protocol. And no class with both types us actually required to adopt NSMutableCopying protocol: a designer should only do what is necessary, sensible and practical.

-copy includes an implicit -retain, so no object you copy will ever be released until your code releases it. Once you copy, you own and are responsible for disposal (note that @property does handle release of the old object when you overwrite a pointer, but it does not relieve you of writing a -dealloc method).

chown33
May 12, 2011, 12:13 AM
So, you are saying that the runtime environment actually "cheats"? ( Or maybe "cuts corners"? ) That when it encounters an immutable parameter copy(), it does a runtime ISA() test and if it's really immutable then it just gives me another pointer to the object and tells itself to release after the function?

The runtime environment doesn't have to cheat, cut corners, or do anything special. You're forgetting that it's the object itself that determines what -copy does. It's not a function copy(), it's a method -copy. The difference is crucial.

NSString's -copy can safely and easily be implemented to return a retain'ed self. This works because any NSString implementation surely knows that NSString is immutable. All objects that are NSString's would use this method when -copy is called. No "runtime ISA() test" is needed, because normal object method dispatch suffices.

NSMutableString's -copy implementation is not so lucky. It knows that the returned object is presumed to be immutable, so it must copy and return another object distinct from self. But once again, every NSMutableString will already do this simply by the nature of Objective-C, which is to invoke a method that is appropriate for the object's actual class. So again no "runtime ISA() test" is needed, because normal object method dispatch suffices.

What seems like a big problem is only a problem when you don't have a generalized object-oriented message system; i.e. if you had to implement this in plain ordinary C. But since you do have a generalized object-oriented message system as the foundation of Objective-C, it's quite simple, and you don't have to do anything special at all: it just happens as a natural consequence.

zeppenwolf
May 13, 2011, 12:04 AM
Ok guys, thanks again. And again, I think, ( after reading it a bazillion times ), I actually understand what you all are saying.

But I feel like I need to explain myself a bit: I encounter a method called "copy" which actually copies nothing whatsoever, and I ask whether the RTE is "cheating", and you guys think I'm crazy...

The situation is that you guys not only know ObjC, while I don't, you also speak and even think ObjC, which I also don't...

I took so much French in high school, there came a time when I was not just speaking English sentences in French, I was actually *thinking* in French... it's a weird thing to discover. But I'm not there yet with ObjC, quite obviously.

In fact, I've been using ObjC/Cocoa for awhile, but I've only recently begun to "learn" it. Heh. Seriously: the Borders bookstore here in Hollywood has just gone out of business, so I went down there before the fact and picked up thirty pounds of ObjCoco books at clearance prices, and I'm reading the Steinberg first...

Perhaps my signature file should identify me as a C++ user in the 12 step program?

Anyway, thanks again.

Catfish_Man
May 17, 2011, 04:06 AM
fwiw I'd say it's totally cheating. It's just English wording at that point, though, not ObjC wording.

Sydde
May 17, 2011, 12:37 PM
fwiw I'd say it's totally cheating. It's just English wording at that point, though, not ObjC wording.

I would say there are two reasons it is not "cheating". The first is, why would you populate memory with a bunch of redundant pointers? It would make no sense to build a shadowing mechanism into Cocoa when you can just reuse the existing object by incrementing its retain count. And I would guess that an object like a NSString would malloc a memory block for its string data, so you cannot just make a new string object that uses the same memory block, because how do you know when the last copy of the string has been released and it is safe to free the underlying block?

You are suggesting that needless complexity is the alternative to "cheating", because you might want different pointers for your "copies", for comparison purposes I guess. Most programmers will write code that does not spit up if a copy of an object is actually the same object, and this is the audience that Apple is writing for.

I do not like to do more work than I feel I need to, but sometimes that is what the task required. Just today, I learned that you can initialize a NSDictionary by reading a .strings file into it, but I had so hoped that the objects array would fill in the sequential order of the file, or something like it. Alas, no such luck, I was forced to write a little extra code to get exactly what I wanted. And that is how programming is. Sometimes you can "cheat" and get a good, consistent result, sometimes you just have to bite the bullet and churn out a few more lines of code or create a new class.

Catfish_Man
May 17, 2011, 06:05 PM
<stuff>

I didn't say cheating was a bad thing. :)