When would I use this (im)mutable property pair setup? (Misunderstanding advanced KVO)

2014-08-15 Thread Daryle Walker
As I’m stripping out the NSDocument stuff, I realize that I need some sort of 
global object to hold each window(-controller) as I create them, otherwise ARC 
will purge them soon after creation.  The application delegate seems like a 
good option, as it’s my only other custom class and Apple uses it to store the 
window in sample projects with a single window.  I read a neat technique at 
http://eschatologist.net/blog/?p=189, and I’m wondering if I would ever need 
it and should switch to something easier.

//==In the header file===
@interface PrairieAppDelegate : NSObject NSApplicationDelegate, 
NSOpenSavePanelDelegate

// unrelated declarations...

@property (nonatomic, copy) NSSet * windowControllers;
@property (nonatomic, readonly) NSMutableSet *  mutableWindowControllers;

@end

//=

I put the implementation methods in the source file:

//==In the implementation file===
#pragma mark Private interface

@interface PrairieAppDelegate () {
NSMutableSet *  _windowControllers;
}

@end

@implementation PrairieAppDelegate

#pragma mark Initialization

- (id)init
{
if (self = [super init]) {
_windowControllers = [[NSMutableSet alloc] init];
}
return self;
}

// No explicit dealloc.

#pragma mark Property getters  setters

// trim unrelated property accessors...

- (NSSet *)windowControllers
{
return [NSSet setWithSet:_windowControllers];
}

- (void)setWindowControllers:(NSSet *)windowControllers
{
[_windowControllers setSet:windowControllers];
}

- (void)addWindowControllersObject:(PrBrowserController *)controller
{
[_windowControllers addObject:controller];
}

- (void)addWindowControllers:(NSSet *)controllers
{
[_windowControllers unionSet:controllers];
}

- (void)removeWindowControllersObject:(PrBrowserController *)controller
{
[_windowControllers removeObject:controller];
}

- (void)removeWindowControllers:(NSSet *)controllers
{
[_windowControllers minusSet:controllers];
}

- (void)intersectWindowControllers:(NSSet *)controllers
{
[_windowControllers intersectSet:controllers];
}

@synthesize mutableWindowControllers = _windowControllers;

#pragma mark NSApplicationDelegate overrides
//…

//=

My window-controller class instances put themselves in the set on “init” and 
remove themselves on “dealloc”.  I originally had “mutableWindowControllers 
private to the implementation file, but the window-controller instances 
couldn’t insert themselves with only “windowControllers public.  That makes 
sense if I had an actual NSSet; but then what are these mutator methods 
actually supposed to do?  When would they ever be called?  Do I have to move 
them to the header file?

Should I get rid of them and stick to just a mutable-set property?  (Delete the 
current “windowControllers” and rename the mutable variant.)  But the text 
seemed like that would mess up KVO in a different way.

— 
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com 

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Re: When would I use this (im)mutable property pair setup? (Misunderstanding advanced KVO)

2014-08-15 Thread Quincey Morris
(reposted for formatting)

On Aug 15, 2014, at 16:24 , Daryle Walker dary...@mac.com wrote:

 As I’m stripping out the NSDocument stuff, I realize that I need some sort of 
 global object to hold each window(-controller) as I create them, otherwise 
 ARC will purge them soon after creation.  The application delegate seems like 
 a good option, as it’s my only other custom class and Apple uses it to store 
 the window in sample projects with a single window.  I read a neat technique 
 at http://eschatologist.net/blog/?p=189, and I’m wondering if I would ever 
 need it and should switch to something easier.

It’s the right idea, but the wrong implementation.

 @property (nonatomic, copy) NSSet * windowControllers;
 @property (nonatomic, readonly) NSMutableSet *  mutableWindowControllers;

Make the “windowControllers” property ‘readonly’ too. Although it’s feasible to 
allow the entire set to be changed via the setter, there are other ways to do 
this (e.g. removeAll + add), and you’re unlikely to need to do that anyway. The 
‘copy’ property “pretends” that the implementation is something other than it 
is.

 - (NSSet *)windowControllers
 {
return [NSSet setWithSet:_windowControllers];
 }

It’s simpler to ‘return _windowControllers;’. Or, better, use ‘@synthesize 
windowControllers = _mutableWindowControllers;'

Since the return value is type ‘NSSet*’, the client can’t use the pointer to 
mutate the collection. (You can typically assume the client won’t do something 
hacky, like cast the pointer back to a NSMutableSet*. If you can’t assume that, 
you’ve got bigger problems.)

 - (void)setWindowControllers:(NSSet *)windowControllers
 {
[_windowControllers setSet:windowControllers];
 }
 
 You don’t need this, if you make the “windowControllers” property ‘readonly’, 
 as I recommended above.
 
 - (void)addWindowControllersObject:(PrBrowserController *)controller
 {
[_windowControllers addObject:controller];
 }
 
 - (void)addWindowControllers:(NSSet *)controllers
 {
[_windowControllers unionSet:controllers];
 }
 
 - (void)removeWindowControllersObject:(PrBrowserController *)controller
 {
[_windowControllers removeObject:controller];
 }
 
 - (void)removeWindowControllers:(NSSet *)controllers
 {
[_windowControllers minusSet:controllers];
 }

These are correct, though you only need one of the ‘add…’ and one of the 
‘remove…’ methods. Although it’s not wrong to implement both of each, it’s 
overkill for a set that’s not going to be very large. It’s typical to implement 
only the 1st and 3rd of these. (Keep in mind, you’re unlikely to want to add a 
*set* of window controllers at once.)

Note that these are the KVC “mutable unordered accessors” described here:


https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/AccessorConventions.html

This means it’s safe to expose these methods publicly. Or you can keep them 
private, and let clients use “mutableWindowControllers” for all their mutation 
needs. This is a matter of taste only.

 - (void)intersectWindowControllers:(NSSet *)controllers
 {
[_windowControllers intersectSet:controllers];
 }

No, you don’t need this, or any other accessors. If you do everything else 
right, any client invocation of (say) ‘[myAppDelegate.mutableWindowControllers 
intersectSet: controllers];’ will get automatically translated into a 
combination of ‘add…’ and ‘remove…’ methods *and will be KVO-compliant*. Your 
code isn’t KVO compliant.

 @synthesize mutableWindowControllers = _windowControllers;

No, this is wrong and dangerous. You’re handing out a mutable reference to your 
private ivar, thus allowing clients to run rampant on your property’s integrity.

Instead, you should implement it like this:

- (NSMutableSet*) mutableWindowControllers {
return [self mutableSetValueForKey: “windowControllers”];
}

This returns a proxy object, so your underlying set stays private, and any 
mutation that a client applies to the proxy is actually applied to your 
underlying data via your accessors (the ‘add…’ and ‘remove…’ methods) *and* is 
KVO compliant.

Finally, my preference is to use a NSArray metaphor for this sort of thing, 
rather than a NSSet metaphor. Yes, the collection of window controllers is 
unordered, but NSSet uses ‘isEqual:’ equality rather than pointer equality to 
distinguish between members. By default, it’s the same thing, but the reliance 
on *not* having a customized ‘isEqual:’ for your window controller classes is a 
hidden fragility in your implementation.

Again, because you’re not going to have a very big collection of window 
controllers, performance and efficiency of the underlying (set vs array) 
representation is not a significant issue.


___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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