samedi 27 juin 2015

Correct pattern for mutable vs immutable

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.

  1. mySet should be copy instead of strong.
  2. 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.
  3. 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:

  1. Is the pattern correct?
  2. Does the getter for mySet return a mutable or immutable instance?
  3. (not listed before) Do I really need the copy signal when using the copy property attribute?

I appreciate your patience to read this far. Best.

Aucun commentaire:

Enregistrer un commentaire