I'm wondering what the correct pattern for implementing Mutable vs Immutable data structures would be. I understand the concept and how it works, but how should I implement if using an underlying Cocoa data structure? I mean, if I use a NSSet
, for instance. Lets say I have the following:
// MyDataStructure.h
@interface MyDataStructure : NSObject
@property (nonatomic, strong, readonly) NSSet * mySet;
@end
// MyDataStructure.m
@interface MyDataStructure ()
@property (nonatomic, strong) NSMutableSet * myMutableSet;
@end
@implementation MyDataStructure
- (NSSet *)mySet
{
return [_myMutableSet copy];
}
@end
The only reason I'm using a mutable set as the underlying data structure,is so that the mutable version of this class can tamper with it. MyDataStructure
per se does not really need a mutable set. Therefore, assuming that I have implemented some initialisers to make this class useful, here's how MyMutableDataStructure
looks like:
// MyDataStructure.h (same .h as before)
@interface MyMutableDataStructure : MyDataStructure
- (void)addObject:(id)object;
@end
// MyDataStructure.m (same .m as before)
@implementation MyMutableDataStructure
- (void)addObject:(id)object
{
[self.myMutableSet addObject:object];
}
@end
By using this pattern the underlying data structure is always mutable, and its immutable version is just an immutable copy (or is it??).
This also begs another question that arises when implementing the NSCopying
protocol. Here's a sample implementation:
- (id)copyWithZone:(NSZone *)zone
{
MyDataStructure * copy = [MyDataStructure allocWithZone:zone];
copy->_myMutableSet = [_myMutableSet copyWithZone:zone];
return copy;
}
Doesn't copyWithZone:
return an immutable copy if that applies? So I'm basically assigning a NSSet
instead to a NSMutableSet
property, isn't that right?
Edit: While diving deeper into the issue I found some more issues surrounding this concern.
mySet
should becopy
instead ofstrong
.- My
copyWithZone:
implementation isn't right either. I didn't mention it in the first post but that implementation relates to the Immutable version of the data structure (MyDataStructure
). As I've read, Immutable data structures don't actually create a copy, they just return themselves. That makes sense. - Because of 2., I needed to override
copyWithZone:
in the Mutable version (MyMutableDataStructure
).
To make things clear:
// MyDataStructure.h
@property (nonatomic, copy, readonly) NSSet * mySet;
And
// MyDataStructure.m
@implementation MyDataStructure
- (id)copyWithZone:(NSZone *)zone
{
// We don't really need a copy, it's Immutable
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
// I also implement -mutableCopyWithZone:, in which case an actual (mutable) copy is returned
MyDataStructure * copy = [MyMutableDataStructure allocWithZone:zone];
copy-> _myMutableSet = [_myMutableSet mutableCopyWithZone:zone];
return copy;
}
@end
@implementation MyMutableDataStructure
- (id)copyWithZone:(NSZone *)zone
{
return [self mutableCopyWithZone:zone];
}
@end
It seems tricky at first, but I think I'm getting the hang of it. So the remaining questions are:
- Is the pattern correct?
- Does the getter for
mySet
return a mutable or immutable instance? - (not listed before) Do I really need the
copy
signal when using thecopy
property attribute?
I appreciate your patience to read this far. Best.
Apple is the way
All across Apple's libraries the pattern that is used is, the mutable version of the class can be created through
-mutableCopy
, or (let's say the class is calledNSSomething
), then it can be created through the methods-initWithSomething:(NSSomething*)something
or+somethingWithSomething:(NSSomething*)something
.NSMutableSomething
always inherits fromNSSomething
and so the constructor methods are the same. (i.e.,+[NSArray arrayWithArray:]
and+[NSMutableArray arrayWithArray:]
return their respective instance types, also you pass in a mutable object to make an immutable copy, aka[NSArray arrayWithArray:someNSMutableArrayObject]
)So this is how I would do it:
Interface
MyDataStructure.h
MyMutableDataStructure.h
Implementation
MyDataStructure.m
MyMutableDataStructure.m
No. To cut down on memory footprint, immutable objects do not need the same overhead as their mutable counterparts.
copyWithZone
Just make sure you use
[self class]
soMyMutableDataStructure
can inherit this method and return its own type, and also don't forget the call to-init
after+allocWithZone:
__typeof(self)
just declares the variable "copy
" as whatever typeself
is, so it's completely inheritable by the mutable subclass.^ That method goes into the implementation of
MyDataStructure
In your original implementation,
While this may be true for your project, it's an abuse of the naming convention. A method that starts with
-copy
should return a copy of the object.Going a little off topic:
I want to touch on some things I saw in your original question...The first is about hiding a "mutable object" with an "immutable object" pointer reference. Maybe you need this functionality too, so here is a more robust way to do it and why.
MyClass.h (notice no ownership attributes - because it's only really an alias - aka we only need this for thy synthesis of setters and getters)
MyClass.m
I defined
_myMutableSet
as an ivar, to protect the getters and setters further. In your original code you put@property (...) myMutableSet
in an interface extension in the .m file. This synthesizes getters and setters automatically, so even if the declaration is apparently "private", one could call[myDataStructure performSelector:@selector(setMutableSet:) withObject:someMutableSet];
and it will work despite the "Undeclared selector" compiler warning.Also, in your original implementation of
-mySet
:This returns a copy of a pointer to
_myMutableSet
, type-casted as anNSSet*
. Therefore if someone re-casted it asNSMutableSet*
they could alter the underlying mutable set.