简体   繁体   中英

NSArray @property backed by a NSMutableArray

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!

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

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

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.

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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM