On Mar 28, 2011, at 22:54, Ken Thomases wrote:

> Do either of you really mean for that property to be read-write?
> 
> I can see making a property read-write with an immutable type, and I can see 
> (although I don't particularly like) making a read-only property return a 
> mutable type.  (Having properties of mutable type breaks encapsulation in a 
> big way.)  But it seems strange to me to make a read-write property of 
> mutable type.
> 
> Especially you, Quincey, when you've made a "sibling", mutable variant of the 
> property.  Is there really a setMutableGizmos: method?  What does it mean?

Well, no, I didn't mean that, but ultimately it doesn't make a lot of 
difference. What I've actually done in the past is either:

>> @interface MutableWidget : Widget {}
>> @property (retain, readonly) NSMutableArray *mutableGizmos;
>> @end
> 


or

>> @interface MutableWidget : Widget {}
>> @property (retain, readwrite) NSArray *gizmos; // redefined
>> @property (retain, readonly) NSMutableArray *mutableGizmos;
>> @end
> 


In the first case, the caller can set the array's contents with a 'replace...' 
or a combination of 'remove...'/'insert...' methods. In the second case, the 
caller can also replace the array's contents with a simple assignment, which is 
a convenience and nothing more.

Either way, "mutableGizmos" is just a derived property of "gizmos". I don't 
think there's any particular dangers in making it readwrite, too or instead. 
There would of course need to be a custom setter for it, since there's no 
instance variable of its own to (accidentally) use @synthesize with, and that 
setter would simply update the "gizmos" property, ensuring KVO compliance. 

In effect, the two properties are the same in the mutable subclass, except for 
return type. That's the whole point -- to get 2 different types for the same 
property. (If you choose to return the mutable proxy in the "mutableGizmo" 
case, though, they're not quite the same, but to me that's a plus.)

> Also, if you're using -mutableArrayValueForKey: without providing the 
> mutation accessors, then that's probably asking for grief. Actually, you said 
> something I didn't follow about a setter for the gizmos property, but that's 
> read-only.  

Sorry, it was a typo. I meant "getter".

> So, it's not clear to me how the proxy is supposed to mutate it (other than 
> KVC's direct instance variable access, which I always disable).  And without 
> either a proper setter or the mutation accessors, you've violated 
> encapsulation.  Clients of your class can mutate your property without you 
> being informed (unless you KVObserve your own property).

You may or may not need to know. If you don't, you haven't exactly violated 
encapsulation, since your accessors would just do the bidding of the caller 
anyway. :)

In my earlier response, I was forgetting that there doesn't seem to be a way of 
giving the mutable proxy access to your array without custom accessors, 
*except* via direct instance variable access. 

However, I never intended to address whether or not there are custom accessors. 
It's a separate point so read on ...

> Here's what makes sense to me:
> 
> @interface Widget : NSObject {}
> @property (retain, readonly) NSArray *gizmos;
> 
> // optionally, any of:
> - (NSUInteger) countOfGizmos;
> - (id) objectInGizmosAtIndex:(NSUInteger)index;
> - (NSArray*) gizmosAtIndexes:(NSIndexSet*)indexes;
> - (void) getGizmos:(Gizmo**)buffer range:(NSRange)inRange;
> 
> @end
> 
> 
> @interface MutableWidget : Widget {}
> 
> // At least one of:
> - (void) insertObject:(Gizmo*)gizmo inGizmosAtIndex:(NSUInteger)index;
> - (void) insertGizmos:(NSArray *)gizmoArray atIndexes:(NSIndexSet *)indexes;
> 
> // At least one of:
> - (void) removeObjectFromGizmosAtIndex:(NSUInteger)index;
> - (void) removeGizmosAtIndexes:(NSIndexSet *)indexes;
> 
> // optionally, either of:
> - (void) replaceObjectInGizmosAtIndex:(NSUInteger)index 
> withObject:(id)anObject;
> - (void) replaceGizmosAtIndexes:(NSIndexSet *)indexes withGizmos:(NSArray 
> *)gizmoArray;
> 
> @end

You don't *have* to make the accessors public API. When I first started doing 
this kind of thing a couple of years ago, I laboriously added the custom 
accessors (at least, the mutable ones) to the @interface, and I eventually took 
them all back out again:

-- It's a huge PITA to put these in the interface in the first place. Reams of 
boilerplate.

-- The custom accessors are significantly more keystrokes for callers to use 
than the generic NSArray/NSMutable array methods. I also found the correct 
order of the pieces to be really hard to remember.

-- Callers *still* can't use the full range of NSArray/NSMutableArray 
convenience methods (say, 'removeObjectsInRange:') without your providing a 
custom version *and* putting them in the interface *and* writing boilerplate 
code for them for *every* array property *and* updating them manually if you 
ever change a property name because Refactor certainly won't do it for you, 
*or* by providing NSArray/NSMutableArray proxies anyway (one of each). If the 
latter, it's easier and more consistent for callers just to use them all the 
time.

At least, that was my experience, and that was the end of making the custom 
accessors public (at least routinely).

> Then, clients of MutableWidget can either directly use those mutation 
> accessors or they can invoke [aMutableWidget 
> mutableArrayValueForKey:@"gizmos"] and mutate that.  If you really want to 
> wrap the latter in a convenience method, you can, but I don't think that 
> constitutes a read-write property.

No, it was an oversight on my part (see above). It's better that "gizmos" 
should be readwrite, if you need that at all.

Personally, I prefer not to make it a caller responsibility to create mutable 
proxies. We have enough experience on this list with callers who've never heard 
of mutable proxies, or get into terrible KVO difficulties misusing them. A 
"mutableGizmos" property can simply solve that for you and your callers, 
forever. Compile-time safety prevents them from writing the wrong code.

> And I reiterate that one should almost always implement 
> +(BOOL)accessInstanceVariablesDirectly { return NO; } on all one's classes.

+1

> If you're looking for guidance, I suggest contemplating what the framework 
> classes do.  Can you think of any mutable class which changes the type of one 
> of the base class's properties to be mutable?  Can you think of any which 
> added a mutable variant of one of the base class's properties (as 
> mutableGizmos is to gizmos)?  Well, frankly, can you think of any framework 
> class that has a property of mutable type?!?

I might be misunderstanding you, and I'm cheating a bit by picking a class 
method, but the obvious case is +[NSArray/NSMutableArray array] (etc). It 
simply punts on the issue by calling the result type 'id', but the caller is to 
assume that the returned object class *is* one or the other. The frameworks 
trades type safety for API brevity. The "mutableGizmos" approach adds a small 
amount of bulk, but gives you very good compile-time type safety.

> No.  Mutable subclasses should provide setters or mutation accessors.  That's 
> the core of what they should add to the interface of the immutable super 
> class.

Actually, I'd leave "setters or" out of this. Having a setter without mutation 
accessors is a potential performance problem. I'd say, "Mutable subclasses 
should provide direct ivar access or mutation accessors", and if your religion 
precludes the former, then just "Mutable subclasses should provide mutation 
accessors" period.

Sorry for the length of this reply. It's the penalty for writing too casual a 
response the first time.


_______________________________________________

Cocoa-dev mailing list ([email protected])

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [email protected]

Reply via email to