0

I recently got into a minor (but not unfriendly) disagreement in the comments on this question about how best to implement a read-only, immutable array property with underlying mutable storage in Objective-C. (The comments have since been moved to chat.)

First, let me be clear about what I want.

  1. I want a property that is read-only, i.e., it is not assignable. myObject.array = anotherArray should fail.
  2. I want the type of that property to be immutable so that new elements cannot be added to it through the property itself. Clearly, this means the type of the property should be NSArray.
  3. I want the storage (i.e., the ivar) for that property to be mutable, because I will provide methods on the containing class to mutate it.

Since it seems not to be clear to some, let me stress that this question is about the frogs property of FrogBox class below, nothing more and nothing less.

I'll use the same contrived example from the linked question, a box of frogs, called FrogBox:

@interface FrogBox : NSObject
@property (nonatomic, readonly) NSArray *frogs;
- (void)addFrog:(Frog*)frog;
- (void)removeFrog:(Frog*)frog;
@end

@implementation FrogBox {
    NSMutableArray *_frogs;
}

@dynamic frogs;

- (instancetype)init {
    self = [super init];
    if (self) {
        _frogs = [NSMutableArray array];
    }
    return self;
}

- (NSArray*)frogs {
    return _frogs;
}

- (void)addFrog:(Frog*)frog {
    [_frogs addObject:frog];
}

- (void)removeFrog:(Frog*)frog {
    // Frog implements isEqual and hash.
    [_frogs removeObject:frog];
}

@end

Now, let's get something out of the way. The @dynamic directive is not strictly necessary here. Using @dynamic suppresses automatic synthesis of an ivar for the frogs property. Of course, if automatic synthesis sees an ivar with the same name as what it would have created, it just uses the supplied one. So why do I use it? I think it adds clarity and signals intent. If you don't like it, just imagine it's not there. It's not germane to the question.

The question is not about whether it's a good idea to want a publicly readonly, immutable and privately mutable property or not. It's about whether this is the most efficient way (in terms of syntax and clock cycles) to achieve that goal. I believe that it is, but I'd like to hear from the community, and am perfectly open to having my mind changed.

Community
  • 1
  • 1
Gregory Higley
  • 15,923
  • 9
  • 67
  • 96
  • Clients of this class can still mutate the underlying collection by casting it back to `NSMutableArray`. That would never pass muster in my world, but your needs may vary. – warrenm Nov 23 '14 at 04:54
  • You want too much. You basically can't have an object that is not assignable. You could define a subclass of NSMutableArray to make it non-mutable, but it would still be assignable. You're pretty much stuck with providing getter methods for accessing the array you hold internally. – Hot Licks Nov 23 '14 at 04:54
  • Not sure I agree, Hot Licks. The implementation above does prevent assignment of the property through ordinary means. If I say `FrogBox *frogBox = [[FrogBox alloc] init]; frogBox.frogs = [NSArray array];`, the compiler won't allow it. That's all I'm trying to achieve. That part is easy and complete, and really not at issue. – Gregory Higley Nov 23 '14 at 04:56
  • If you call `copy` on an `NSMutableArray`, you get back an immutable `NSArray` containing the same objects. In fact, Foundation is probably smart enough to make it a copy-on-write alias to the original array's storage. If you care about making the property readonly and immutable, that's one way to do it. – warrenm Nov 23 '14 at 04:57
  • I don't have to create a FrogBox to assign the one you have to a different pointer. I simply have to do `FrogBox* myPointer = [yourContainerObject getTheFrogBox];`. – Hot Licks Nov 23 '14 at 04:59
  • It's still unclear what you're asking. – Hot Licks Nov 23 '14 at 04:59
  • So, you're suggesting I implement my `frogs` property as `return [_frogs copy];`? I don't see a problem with that as long as it's a copy-on-write alias. – Gregory Higley Nov 23 '14 at 05:00
  • Yep. YMMV, but I've done it plenty of times in production and never noticed a hit. – warrenm Nov 23 '14 at 05:00
  • I don't think you understand the question, Hot Licks. I'm not talking about assignment of `FrogBox` itself, but rather its `frogs` property. – Gregory Higley Nov 23 '14 at 05:00
  • OK, I don't understand why the downvote or the close vote. This is a perfectly legitimate question. – Gregory Higley Nov 23 '14 at 05:02
  • Well, if you never expose `frogs` it can't be copied, or modified by external code. – Hot Licks Nov 23 '14 at 05:02
  • 1
    What is the question? – Hot Licks Nov 23 '14 at 05:03
  • It's right in the title, but I'll repeat it here: "What is the most efficient way to have a publicly immutable, read-only array property that is privately mutable?" Note the "array property" part. That's what we're talking about. The discussion is about how best to implement the `frogs` property, which declares itself as `NSArray` but whose underlying storage is an `NSMutableArray`. Is this the best way to do it? – Gregory Higley Nov 23 '14 at 05:05
  • Not the way in your example. I can simply do `(NSMutableArray*) (aFrogBox.frogs)[5] = "Hello";` to modify your array. – Hot Licks Nov 23 '14 at 05:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/65458/discussion-between-gregory-higley-and-hot-licks). – Gregory Higley Nov 23 '14 at 05:08
  • I've voted to close this question since it's a duplicate of http://stackoverflow.com/q/21109083/27779 – Gregory Higley Nov 23 '14 at 05:20
  • So you weren't worried about efficiency after all? – Hot Licks Nov 23 '14 at 13:37
  • Actually, I was. But since this question seemed to be inexplicably hated, why not just shoot it? – Gregory Higley Nov 23 '14 at 16:25

1 Answers1

3
  1. I want a property that is read-only, i.e., it is not assignable. myObject.array = anotherArray should fail.
  2. I want the type of that property to be immutable so that new elements cannot be added to it through the property itself. Clearly, this means the type of the property should be NSArray.
  3. I want the storage (i.e., the ivar) for that property to be mutable, because I will provide methods on the containing class to mutate it.

Well, I'm going to give the same answer I gave in the comments on the other question. What I do is:

  1. Declare the property a readonly copy NSArray property in the public interface. This takes care of your 1 and 2; this thing now cannot be written to and it's an immutable array.

  2. Redeclare the property readwrite privately in the .m file. Now I have to right to assign to it from within this class. To implement a mutating method, I read with mutableCopy, call an NSMutableArray method, and set - thus getting an immutable NSArray again automatically. Despite the use of the word "copy" there is actually no overhead.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Could you give a quick code example? Most of the related questions on SO implement it more or less as I do, such as http://stackoverflow.com/q/21109083/27779. – Gregory Higley Nov 23 '14 at 05:21
  • So, you're saying I'd implement `addFrog:` as follows: `NSMutableArray *mutable = [self.frogs mutableCopy]; [mutable addObject:frog]; self.frogs = mutable;`? – Gregory Higley Nov 23 '14 at 05:23
  • Yes, exactly. You can package that elegantly if you're doing that sort of thing from a lot of places, but ultimately that is exactly what I said on the other question and what I'm saying here. Also note that in this particular case you could do it in one line by using `arrayByAddingObject:`. But in other more elaborate mutations the `mutableCopy` will be needed. – matt Nov 23 '14 at 05:25
  • BTW I've no grounds for talking about "better" and I don't think that sort of thing is appropriate for StackOverflow. I'm just saying, yes, of course the problem of vending an NSArray which you are allowed to change internally comes up all the time, and this is what I personally do about it. – matt Nov 23 '14 at 05:26
  • OK. I got the (mis)impression that you thought the way I was doing it was incorrect in some way. The way I see it, it's a matter of taste. Which do you dislike more, the ivar or calling `mutableCopy` and reassigning? I'd say the main advantage of yours is that the underlying storage is `NSArray` and so it can't be mutated by typecasting tricks. But I could also `return [_frogs copy]` to achieve much the same. – Gregory Higley Nov 23 '14 at 05:31
  • In any case, I learned something. I had always thought that `mutableCopy` (and `copy`) could potentially have a high overhead, but now that I know they don't, I may change the way I do this. Thanks. – Gregory Higley Nov 23 '14 at 05:33
  • 2
    Also I find it wise to try to remember that the other party in the OOP contract is myself, not an inimical hacker. Objective-C is horribly introspective. If some other code wants to mess with my ivars, it can. So I don't spend brain cycles trying to lock myself out of my own code. `readonly copy` NSArray describes the contract and the compiler writes a much better pair of accessors than you or I would know how to write, so at that point I'm done. – matt Nov 23 '14 at 05:38
  • Agreed. I'm not trying to make it impossible for a user of my class to mutate the underlying array if they want to cast or use introspection. I've signaled my intent by declaring it as `NSArray`. – Gregory Higley Nov 23 '14 at 05:43