可变集合作为属性的最佳实践

3

我打算使用一个NSMutableDictionary属性来存储游戏数据(如分数、设置等)。

@property (nonatomic, copy) NSMutableDictionary *gameData;

在研究为什么属性没有“mutablecopy”选项时,我找到了这个讨论,其中被接受的答案说:

正确的方法不是将可变数组作为属性

那么,现代Objective-C中处理可变集合作为属性的最佳方式是什么?
3个回答

7
这种情况下,一个类为集合对象公开可变属性的模式相当罕见,因为它似乎会破坏封装性。
然而,在某些情况下,这样做可能是有意义的:例如,您可以返回一个 KVO 代理以管理某些内部集合(使用 KVC 的 mutableArrayValueForKey:)。
在这些情况下,无论将 getter 声明为属性还是普通方法都没有太大关系。但是,如果使用属性,则不应该是 copy 属性。
为了更清楚地说明这一点,这里有一个例子。我们有一个声明单个公共 getter 的类:
@interface Foo : NSObject

@property (strong, readonly) NSMutableArray *publicBars;

@end

实现管理一个封装的、私有的可变数组,命名为_bars
@implementation Foo
{
    NSMutableArray *_bars;
}

这个数组的元素可以通过 KVC to-many accessors 进行访问:
- (NSUInteger)countOfBars
{
    return [_bars count];
}

- (id)objectInBarsAtIndex:(NSUInteger)idx
{
    return _bars[idx];
}

- (void)insertObject:(id)object inBarsAtIndex:(NSUInteger)idx
{
    if (_bars == nil)
        _bars = [NSMutableArray array];
    [_bars insertObject:object atIndex:idx];
}

- (void)removeObjectFromBarsAtIndex:(NSUInteger)idx
{
    [_bars removeObjectAtIndex:idx];
}

这里是精华部分:我们通过返回反映和改变内部状态的KVC代理来实现公共属性,而不暴露内部 ivars。它只使用上面定义的公共访问器来更改内部数组。

- (NSMutableArray *)publicBars
{
    return [self mutableArrayValueForKey:@"bars"];
}

@end

我们可以使用代理来改变内部集合:
Foo *foo = [[Foo alloc] init];

NSMutableArray *bars = foo.publicBars;

[bars addObject:@1];
[bars addObject:@2];
[bars removeObjectAtIndex:0];

 // Now the internal _bars array is @[ @2 ].

这种模式在Cocoa中有实际的例子。例如,-[NSTextView textStorage]返回内部后备存储(一个派生自NSMutableAttributedString的对象)。虽然不是一个集合对象,但它是一个可变对象,可以将其变化传递给其主机对象——文本视图。


我喜欢你的想法,但它不会为keyPath publicBars的添加和删除生成KVO通知。但是,它将为keyPath bars生成KVO通知。我自己也很困扰 - 如何记录一个类具有KVC和KVO兼容的集合属性,而不实际在接口中声明属性。如果您在接口中有像addBars和removeBar这样的方法,那么可能会给您一个放置有关KVO / KVC属性的文档的位置,但是如果您只想让其他人观察对bars集合的更改而不进行这些更改呢? - Joel
@Joel 说得好。在头文件中声明KVO兼容性没有通用的方法。我通常使用注释,但是有一个更“标准”的方式可能会更好,比如一个NSKVOCompliance属性或类似的东西。静态分析器甚至可以使用它来生成有关错误观察的警告。 - Nikolai Ruhe

1

在我看来,你可以很好地使用可变属性 - 因为在Objective-C中,属性不仅用于封装,还用于内存管理(通过ARC)。

然而,你应该将此属性保留在实现文件中,并将其公开为其他类的非可变、只读部分。

//your_header_file.h

@interface YouClass : NSObject

@property (readonly) NSDictionary *gameState;
@end

//your_implementation_file.m

@interface YouClass ()
@property (nonatomic, strong) NSMutableDictionary *gameState;
@end

@implementation SomeClass

//deal with mutable state here


@end

编辑:

另外,由于可变集合通常不是线程安全的,请确保您始终从同一线程更改它们。


1
可变属性的问题在于其他类可以更改集合内容,而所有者并不会注意到。好的设计应该只让一个类负责特定任务,比如存储游戏数据。
我会将可变字典作为私有强引用属性,并提供以下形式的访问器方法给外部使用:
- (void)storeObject:(id)object forSetting:(NSString *)settingName;
- (id)objectForSetting:(NSString *)settingName;

如果您确实需要外部类能够整体设置字典,那么在确保这确实是必要的情况下,您仍然可以将其设置为非可变属性,当设置时,从不可变字典中的项创建内部可变字典的新实例。但我认为您应该能够绕过这个必要条件。

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