在公共接口中防止向NSMutableArray添加对象

10

我想保护NSMutableArray在公共接口中的访问

我尝试通过在公共接口中将属性定义为NSArray,在私有接口中将其定义为NSMutableArray来实现此目的,就像这样:

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

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

但这样不起作用 - 所以我必须在公共接口 NSMutableArray 中定义属性:

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

目标是为API客户端提供只读评论访问权限,并在实现中提供像addObject:这样的方法的完全访问权限。

因此,更清晰地定义目标:

  1. 客户端应该有作为NSArray的属性访问权限,但不能访问变异方法。
  2. 客户端不应该能够更新评论以指向新值。
  3. 解决方案必须在不创建额外结构和数组复制的情况下完成。

所以简单地说,问题是是否可能使公共属性的定义更加通用(使用NSArray而不是NSMutableArray)。

是否有其他干净的方法来实现目标,或者我必须在所有地方都使用NSMutableArray?

解决方案

在审核我的原始问题和答案后,我意识到我想在公共接口中使用更通用的NSArray类,在实现中使用NSMutableArray - 但对于一个属性来说这是不可能的。所以答案是否定的。

因此,我将只使用具有NSMutableArray的单个属性,没有任何额外的所需保护。

但如果您确实更喜欢保护而不是简洁和效率,则我还将选择最合适的答案,这可能会有所帮助。

5个回答

14

如果你只是想允许客户端读取数组,那么你不需要一个公共属性。

只需创建一个访问器方法来返回私有可变数组的副本:

@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

例如,在NSView中可以看到这种模式: constraintssubviews在内部是可变的,但只通过一个返回不可变数组的单个方法进行读取。


1
选择这个答案作为最合适的原始问题。但我不会使用“复制”,也不会创建额外的getter——代码会有点不受保护,但是简单明了,只有一个属性。感谢您的回答! - Vladimir
弗拉基米尔,你好,我和你当时处于完全相同的情况。为什么不采用这个解决方案呢?它似乎是我们问题的一个很好的解决方案;那么浅复制数组有什么问题呢? - kernix

4

一种解决方案是将属性声明为只读的 NSArray。然后在实现中,基于 NSMutableArray 创建一个单独的可写属性。

.h 文件中:

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

在.m文件中:
@interface Order()
@property (strong, nonatomic) NSMutableArray* internalComments;
@end

不要合成只读属性,应该这样写:

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

在 .m 文件中,您需要使用 self.internalComments 进行所有操作。

2
一种更加简洁的解决方案,如果适用于您的使用场景,是将属性声明为NSArray,同时以NSMutableArray作为后备。客户端可以通过将其强制转换为可变数组来修改数组,但您明确表明这样做是不好的。 @property只是两个方法(getter和setter)的包装器,通常由存储变量支持。实现此解决方案的最简单方法是:
@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

如果想让用户能够替换整个数组(order.comments = ...),请移除该属性上的readonly属性,并重写-setComments:方法:
- (void)setComments:(NSArray *)array
{
    [_comments release]; // Can be omitted if you are using ARC
    _comments = [array mutableCopy];
}

值得注意的是,由于Objective-C是一种动态语言,无法完全防止某人访问变量,因为您可以直接与运行时接口交互或通过选择器调用方法,如果您真的想要探究不应该探究的事情。你真正能做的就是让人们清楚地知道这样做是一个坏主意。

1

两种解决方案:

  1. 返回一个不可变的数组副本-客户端一次性获取所有评论。
  2. 返回评论数量和单个评论。

任选其一,两种方案都有其优点,具体取决于您的需求:

@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

0
如果您不希望客户端能够设置此属性,则在公共接口中将其声明为readonly。如果您不希望客户端能够更改客户端可以读取的数组,则将其声明为NSArray。同时,在您这一侧,您是readwritecopy。通过readwrite,您可以获取一个mutableCopy(它将是一个NSMutableArray),进行更改,然后再次设置该属性;通过copy,您确保客户始终看到的是NSArray。

因此,客户端:

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

内部顺序:

NSMutableArray* marr = self.comments.mutableCopy;
[marr addObject: @"ha"];
self.comments = marr; // and it is magically turned back into an NSArray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接