Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C property, expose only superclass

I am declaring a property in my class in header file;

@property (readonly) NSArray *pages

That's how I want it to be exposed publicly. Internally though, I am going to allocating it as NSMutableArray so I can add/remove stuff from it. But to do that, I will have to type cast every time. Is there a better way to do this? Thanks

like image 777
0xSina Avatar asked Mar 08 '26 11:03

0xSina


2 Answers

Your approach is really bad. If you insist on exposing a mutable array with dynamic content, then modify your getter to return an immutable copy, otherwise you are going to get weird side effects and exceptions for mutations during fast enumeration.

like image 110
Морт Avatar answered Mar 10 '26 02:03

Морт


There isn't a solution for this. You have to cast every time, or use different properties. Here is a sample for the second approach:

@interface MyClass : NSObject
@property (nonatomic, strong, readonly) NSArray *pages;
-(void)addObject:(id)obj;
@end

@interface MyClass()
@property (nonatomic, strong, readwrite) NSMutableArray *mPages;
@end

@implementation MyClass
-(id) init {
    self = [super init]
    if (self){
        _mPages = [NSMutableArray array];
    }
    return self;
}
-(NSArray*)pages {
    return [NSArray arrayWithArray:self.mPages];
}
-(void)addObject:(id)obj {
    [self.mPages addObject:obj];
}
@end


int main(int argc, char *argv[]) {
    @autoreleasepool {
        MyClass *m = [MyClass new];
        [m addObject:@"x"];     // the collection is mutable
        NSLog(@"%@",[m pages]); // but only accessible as an immutable copy
    }
}

This will be expensive if you access the collection frequently, and may be out of sync with the internal mutable collection (which may be mutated while you iterate on the copy).

Copying can be avoided returning the internal mutable instance (NSMutableArray) disguised as an immutable class (NSArray), but that incurs the following risks:

  • The client could cast to mutable and change it.
  • The internal copy could be mutated. This will crash the application if you are iterating, or may cause an index out of range exception.

Note that the following idiom doesn't solve the problem:

@interface MyClass : NSObject
@property (nonatomic, strong, readonly) NSArray *pages;
@end

@interface MyClass()
@property (nonatomic, strong, readwrite) NSMutableArray *pages;
@end

This lets you set the variable, but not use it as a different class than the one declared in the interface. In other words, it forces you to cast on every use:

[(NSMutableArray*)pages addObject:@"x"];
like image 45
Jano Avatar answered Mar 10 '26 03:03

Jano