10

I want to protect access to NSMutableArray in public interface

I am trying to do this by defining property as NSArray in public interface and as NSMutableArray in private interface like this:

@interface Order : NSObject
@property (readonly, strong, nonatomic) NSArray* comments;
@end

@interface Order()
@property (readwrite, strong, nonatomic) NSMutableArray* comments;
@end

But this doesn't work - so I have to define property in public interface NSMutableArray instead:

@interface Order
@property (strong, nonatomic) NSMutableArray* comments;
@end

The goal is to provide read-only access to comments for API clients and full access to methods like addObject: in the implementation.

So defining goal more clearer:

  1. Client should have access to property as NSArray without ability to access mutation methods.
  2. Client should not have ability to update the comments to point to new value.
  3. The solution must be done without creating extra structures and array copying.

So simply the question was if it's possible to make public definition of property more general (NSArray instead of NSMutableArray).

Is there any other clean way to reach the goal or I have to use NSMutableArray everywhere?

RESOLUTION

After reviewing my original question and answers I realized that I would like to use more generic class NSArray in the public interface and NSMutableArray in the implementation - but it's just not possible for one property. So the answer is not possible.

So I will just use single property with NSMutableArray without any extra desired protection.

But I will also pick the most appropriate answer it might help if you really prefer protection over simplicity and efficiency.

Vladimir
  • 4,782
  • 7
  • 35
  • 56

5 Answers5

14

You don't need a public property if all you want is to allow clients to read the array.

Just create an accessor method that returns a copy of your private mutable array:

@interface Order : NSObject
- (NSArray *)allComments;
@end

@implementation Order ()
@property (nonatomic, strong) NSMutableArray * comments;
@end

@implementation Order

@synthesize comments;

- (NSArray *)allComments
{
    return [[self comments] copy];
}

@end

This pattern can be seen in e.g., NSView: constraints and subviews are internally mutable, but only exposed for reading via a single method returning a nonmutable array.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • 1
    Picking this answer as most appropriate for original question. But I will not use the "copy" and will not create extra getter - will keep the code a bit unprotected, but clean * simple with one property. Thanks for the answer! – Vladimir Apr 02 '13 at 19:53
  • Vladimir, hi, I'm exactly in the same situation as you were. Why wouldn't you go with this solution? It sounds actually a great solution for our problem; what's is the problem with shallow copying of the array? – kernix Aug 08 '15 at 18:24
4

One solution would be to declare the property as readonly as an NSArray. Then in your implementation, create a separate, writable property based on NSMutableArray.

In the .h:

@interface Order : NSObject
@property (readonly, strong, nonatomic) NSArray* comments;
@end

In the .m:

@interface Order()
@property (strong, nonatomic) NSMutableArray* internalComments;
@end

Instead of synthesizing the readonly property, write:

- (NSArray *)comments {
    return [self.internalComments copy];
}

In the .m you do everything with self.internalComments.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
2

A far cleaner solution, if it's acceptable for your use case, would be to declare the property as an NSArray, while backing it with an NSMutableArray. The client could technically modify the array by simply casting it to a mutable one, but you are making it clear that doing so would be a bad idea.

A @property is simply a wrapper around two methods, a getter and a setter, which are typically backed by a storage variable. The simplest way to implement this solution would be:

@interface Order : NSObject
{
    NSMutableArray *_comments;
}

@property (readonly, strong, nonatomic) NSArray *comments;

- (void)addComment:(Comment *)comment;

@end

@implementation Order
@synthesize comments=_comments; // Can be omitted if you use Xcode 4.4+

- (void)addComment:(Comment *)comment
{
    [_comments addObject:comment];
}

@end

If you want the user to be able to replace the entire array (order.comments = ...), remove the readonly attribute on the property and override the -setComments: method:

- (void)setComments:(NSArray *)array
{
    [_comments release]; // Can be omitted if you are using ARC
    _comments = [array mutableCopy];
}

It's worth noting that since Objective-C is a dynamic language, it's not possible to fully prevent someone from accessing a variable, as you can interface directly with the runtime or call methods by their selector if you really want to poke around in things you're not supposed to. All you can really do is make it clear that doing so is a bad idea.

liclac
  • 412
  • 3
  • 9
1

Two solutions:

  1. Return a non-mutable copy of the array - client gets all the comments in one go.
  2. Return the number of comments and individual comments.

Take your pick, there are arguments for both and it depends on what suits you:

@interface Order : NSObject

// solution one - return a copy
@property (readonly, strong, nonatomic) NSArray* comments;

// solution two - return individual comments
@property (readonly) NSUInteger commentCount;
- (id) comment:(NSUInteger)number;

@end

@interface Order()

// internal property - mutable
@property (readwrite, strong, nonatomic) NSMutableArray* privateComments;

@end

@implementation Order

// solution one - return a copy
- (NSArray *) comments            { return [self.privateComments copy]; }

// solution two - return individual comments
- (NSUInteger) commentCount       { return self.privateComents.count; }
- (id) comment:(NSUInteger)number { return self.privateComment[number]; }

@end
CRD
  • 52,522
  • 5
  • 70
  • 86
0

If you don't want the client to be able to set this property at all, then declare it in the public interface as readonly. If you don't want the client to be able to mutate the array that the client can read, then declare it as NSArray. Meanwhile, on your side you are readwrite and copy. By being readwrite you can take a mutableCopy (which will be an NSMutableArray), make your changes, and then set the property again; by being copy you ensure that the client always sees an NSArray.

So, client:

NSArray* arr = order.comments; // ok
[arr addObject: @"ha"]; // no, can't
order.comments = someOtherArray; // no, can't

Inside Order:

NSMutableArray* marr = self.comments.mutableCopy;
[marr addObject: @"ha"];
self.comments = marr; // and it is magically turned back into an NSArray
matt
  • 515,959
  • 87
  • 875
  • 1,141