41

I've defined a class where I'd like a public property to appear as though it is backed by an NSArray. That is simple enough, but in my case the actual backing ivar is an NSMutableArray:

@interface Foo
{
    NSMutableArray* array;
}
@property (nonatomic, retain) NSArray* array;

@end

In my implementation file (*.m) I @synthesize the property but I immediately run into warnings because using self.words is the same as trying to modifying an NSArray.

What is the correct way to do this?

Thanks!

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
Matty P
  • 881
  • 1
  • 7
  • 11

6 Answers6

21

I would declare a readonly NSArray in your header and override the getter for that array to return a copy of a private NSMutableArray declared in your implementation. Consider the following.

Foo.h

@interface Foo

@property (nonatomic, retain, readonly) NSArray *array;

@end

Foo.m

@interface Foo ()

@property (nonatomic, retain) NSMutableArray *mutableArray

@end

#pragma mark -

@implementation Foo

@synthesize mutableArray;

- (NSArray *)array
{
    return [[self.mutableArray copy] autorelease];
}

@end
Mark Adams
  • 30,776
  • 11
  • 77
  • 77
  • This code copies the mutable array to a normal one. That's good. – Tom Andersen Dec 06 '11 at 23:25
  • 3
    The problem with making a property out of something thats not is that the caller could easily write code like aFoo.array 50 times in one piece of code, creating many copies of the array - which kills performance dead. – Tom Andersen Dec 06 '11 at 23:30
  • You should just use `array` for both property names: internal usage will get the mutable array and external will get the non-mutable, and you won't need to override the `array` method to copy anything (note that you can add the `readwrite` keyword to the private property to make it clear what you're doing) –  Dec 06 '11 at 23:49
  • 1
    How does this work in times of ARC? I'm not sure if I should use `strong` or `weak` for the `NSArray*`. Since it's not backed by an ivar I assume it needs to be `weak`. Is that correct? – znq May 10 '12 at 16:17
  • 1
    @darvids0n I like this idea, but when I try something like `[self.array addObject:object]`, the compiler complains that array is an `NSArray`. – Michael Mior Jun 09 '12 at 17:26
  • Sounds like you just need to expose an `NSMutableArray`. This doesn't really apply. – Mark Adams Jun 09 '12 at 19:33
  • 1
    @MichaelMior Well, you can always cast it: `NSMutableArray *myInternalArray = (NSMutableArray *)self.array; [myInternalArray addObject:object]`. This is a dangerous thing though, so probably don't do that. For places where you're referencing `self.array` you can always just reference the internal variable directly: `[array addObject:object]` and that will work. `self.array` when used internally *should* get you an `NSMutableArray` pointer though, if you declared it the way I suggested.. –  Jun 12 '12 at 01:33
  • @darvids0n Totally correct. I realized later that expecting this to work didn't make any sense :) – Michael Mior Jun 12 '12 at 13:20
15

Basically, put the NSArray property in a category in your header file and the NSMutableArray property in the class extension in your implementation file. Like so...

Foo.h:

@interface Foo
@end

@interface Foo (Collections)
@property (nonatomic, readonly, strong) NSArray *someArray;
@end

Foo.m

@interface Foo ()
@property (nonatomic, readwrite, strong) NSMutableArray *someArray;
@end
sam
  • 3,399
  • 4
  • 36
  • 51
  • Thanks. It looks like the author of the blog post removed that post. I removed the link. – sam Dec 10 '14 at 21:04
  • 4
    Calling `self.someArray` in the .m file will give you access to the `NSArray` and hide the `NSMutableArray` which makes this solution less than ideal – Kevin May 15 '15 at 13:37
  • It works for me. However, I am wondering why it works only if the read-only immutable array is in a category? Why Apple don't want us just put the public part in the interface? That seems a neat design. – Franklin Yu Apr 03 '16 at 03:22
5

Simple:

1) Don't use a property when it ain't one.

2) Code simplifies to:

- (NSArray *)currentArray {
    return [NSArray arraywithArray:mutableArray]; // need the arrayWithArray -    otherwise the caller could be in for surprise when the supposedly unchanging array changes while he is using it. 
}

- (void)setArray:(NSArray *)array {
    [mutableArray setArray:array];
}

When the object is alloced create the array, when it dies, dealloc the array.

When large effects happen at the mere use of a '.' operator, its easy to overlook hugely inefficient code. Accessors are just that. Also - if someone calls aFoo.array - the contract is to get access to foo's array members - but really its just a copy at the time of the call. The difference is real enough that it caused bugs in the other implentations posted here.

Tom Andersen
  • 7,132
  • 3
  • 38
  • 55
  • Likely keeping self.mutableArray private to the .m file could be a real win - you might for instance want to change the underlying implementation later. – Tom Andersen Dec 06 '11 at 23:36
4

Update: this answer is not valid anymore. Use one of suggested solutions below.

These days you can do the following:

Foo.m:

@implementation Foo {
    NSMutableArray* _array;
}

@end

Foo.h:

@interface Foo

@property (readonly, strong) NSArray* array;

@end

You can still address mutable _array by ivar from the inside of implementation and outside it will be accessible via immutable property. Unfortunately this doesn't guarantee that others can't cast it to NSMutableArray and modify. For better protection from idiots you must define accessor method and return immutable copy, however that might be very expensive in some cases.

I would actually agree with one of the comments above that it's better to use simple accessor methods if you need to return some read-only data, it's definitely less ambiguous.

pronebird
  • 12,068
  • 5
  • 54
  • 82
2

That's because your property must match the actual ivar's class type.

A possible solution/workaround:

//Foo.h:
@interface Foo
{
    NSMutableArray* mutableArray;
}
@property (readwrite, nonatomic, retain) NSArray* array;
//or manual accessor declarations, in case you're picky about wrapper-properties.
@end

//Foo.m:
@interface Foo ()
@property (readwrite, nonatomic, retain) NSMutableArray* mutableArray;
@end

@implementation

@synthesize mutableArray;
@dynamic array;

- (NSArray *)array {
    return [NSArray arrayWithArray:self.mutableArray];
}

- (void)setArray:(NSArray *)array {
    self.mutableArray = [NSMutableArray arrayWithArray:array];
}

@end

You're adding a private mutableArray property in a class extension and making the public array simply forward to your private mutable one.

With the most recent language extensions of ObjC I tend to remove the

{
    NSMutableArray* mutableArray;
}

ivar block entirely, if possible.

And define the ivar thru the synthesization, as such:

@synthesize mutableArray = _mutableArray;

which will generate a NSMutableArray *_mutableArray; instance for you.

Regexident
  • 29,441
  • 10
  • 93
  • 100
  • Removing ivars has a serious impact on your ability to debug and maintain your code in a large code base. "Where did that come from and why does it not show up in the debugger?" Its C code. Lets be straight about that. – Tom Andersen Dec 06 '11 at 23:24
  • This code -(NSArray*)array call returns a modifiable NSArray back to the caller. Either the caller or the callee could modify that array after the call, likely causing hard to debug problems in future code. Never do that. Plus worse it looks like a call to a property so the caller can make all sorts of assumptions about what would happen. – Tom Andersen Dec 06 '11 at 23:28
  • It's not gone. It's just hidden from the public interface (as it should be) and instead moved into the implementation. All I need to know I can get from `@property` and `@synthesize`. Also, it's what Apple has been embracing lately (as in "Xcode's default behaviour"). Alternatively you could simply move the ivar block to the `@implementation`. – Regexident Dec 06 '11 at 23:29
  • @TomAndersen: Fair point about the returning of the mutable ivar. Fixed. However if you're casting an `NSArray` to an `NSMutableArray`, which you'd need to do, in order to modify it, you're doing it wrong anyway. – Regexident Dec 06 '11 at 23:31
  • I know exactly how it works under the hood, but then you drop into the debugger you often don't get any idea of what is going on. The code above has been fixed a bit, but still having an accessor make a copy of a possibly very large array every time you type a '.' could be a disaster for someone who does not understand the underlying model, which you claim should be hidden, and so they in fact can't see it. – Tom Andersen Dec 06 '11 at 23:34
  • 1
    The object could modify the array itself - after giving it to the caller, promising the caller a non mutable object. There are lots of failures like this in the real world. – Tom Andersen Dec 06 '11 at 23:40
  • Well, I assume that if one knows the subtle differences between properties and barebones accessors, then one probably would know that an accessor to a mutable ivar that returns an immutable one either has to copy it or disclose it's mutable ivar. Which is the same for barebones & property and imho wouldn't be any clearer than without the latter. For cases like these, there is documentation. And if you aren't doing documentation (for at least all your non-trivial methods, like this one), then in the end you're doomed to fail anyway, maintenance-wise. – Regexident Dec 06 '11 at 23:44
0

Simplest answer: your property type (NSArray) doesn't match your instance variable type (NSMutableArray).

This is yet another good reason that you shouldn't define your own backing variables. Let @synthesize set up your instance variables; don't do it by hand.

emma ray
  • 13,336
  • 1
  • 24
  • 50