可变与不可变的正确模式

Correct pattern for mutable vs immutable

我想知道实现可变数据结构与不可变数据结构的正确模式是什么。我理解这个概念及其工作原理,但如果使用底层 Cocoa 数据结构,我应该如何实现?我的意思是,例如,如果我使用 NSSet。假设我有以下内容:

// MyDataStructure.h
@interface MyDataStructure : NSObject
@property (nonatomic, strong, readonly) NSSet * mySet;
@end


// MyDataStructure.m
@interface MyDataStructure ()
@property (nonatomic, strong) NSMutableSet * myMutableSet;
@end

@implementation MyDataStructure

- (NSSet *)mySet
{
    return [_myMutableSet copy];
}

@end

我使用可变集作为底层数据结构的唯一原因是 class 的可变版本可以篡改它。 MyDataStructure 本身并不需要可变集。因此,假设我已经实现了一些初始化程序来使 class 有用,下面是 MyMutableDataStructure 的样子:

// MyDataStructure.h (same .h as before)
@interface MyMutableDataStructure : MyDataStructure

- (void)addObject:(id)object;

@end

// MyDataStructure.m (same .m as before)
@implementation MyMutableDataStructure

- (void)addObject:(id)object
{
    [self.myMutableSet addObject:object];
}

@end

通过使用此模式,底层数据结构始终是可变的,其不可变版本只是一个不可变副本(或者是??)。

这也引出了实施 NSCopying 协议时出现的另一个问题。这是一个示例实现:

- (id)copyWithZone:(NSZone *)zone
{
    MyDataStructure * copy = [MyDataStructure allocWithZone:zone];
    copy->_myMutableSet = [_myMutableSet copyWithZone:zone];

    return copy;
}

copyWithZone: return 不可变副本(如果适用)吗?所以我基本上是将 NSSet 分配给 NSMutableSet 属性,不是吗?

编辑:在深入研究这个问题时,我发现了一些与此问题相关的更多问题。

  1. mySet 应该是 copy 而不是 strong.
  2. 我的 copyWithZone: 实施也不正确。我没有在第一个 post 中提到它,但该实现与数据结构的不可变版本 (MyDataStructure) 相关。正如我所读到的,不可变数据结构实际上并不创建副本,它们只是 return 自己。有道理。
  3. 因为 2.,我需要在可变版本 (MyMutableDataStructure) 中覆盖 copyWithZone:

要说清楚:

// MyDataStructure.h
@property (nonatomic, copy, readonly) NSSet * mySet;

// MyDataStructure.m
@implementation MyDataStructure

- (id)copyWithZone:(NSZone *)zone
{
    // We don't really need a copy, it's Immutable
    return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    // I also implement -mutableCopyWithZone:, in which case an actual (mutable) copy is returned
    MyDataStructure * copy = [MyMutableDataStructure allocWithZone:zone];
    copy-> _myMutableSet = [_myMutableSet mutableCopyWithZone:zone];

    return copy;
}

@end

@implementation MyMutableDataStructure

- (id)copyWithZone:(NSZone *)zone
{
    return [self mutableCopyWithZone:zone];
}

@end

乍一看似乎很棘手,但我想我已经掌握了窍门。所以剩下的问题是:

  1. 模式是否正确?
  2. getter for mySet return 是可变实例还是不可变实例?
  3. (之前未列出)使用 copy 属性 属性时我真的需要 copy 信号吗?

感谢您耐心阅读到这里。 最好的。

  1. 我无法判断您的模式是否正确,这取决于.. 如果您更容易理解这种方法。那么它是正确的,但不要忘记为未来的开发者添加评论。

  2. mySet 是不可变的,因为你将它初始化为不可变的 NSSet..

  3. copy,关于这个就参考这个discussion

希望对您有所帮助。

苹果才是王道

在 Apple 的所有库中使用的模式是,class 的可变版本可以通过 -mutableCopy 创建,或者(假设 class 被称为 NSSomething),则可以通过-initWithSomething:(NSSomething*)something+somethingWithSomething:(NSSomething*)something方法创建。 NSMutableSomething 总是从 NSSomething 继承,所以构造方法是相同的。 (即 +[NSArray arrayWithArray:]+[NSMutableArray arrayWithArray:] return 它们各自的实例类型,您也可以传入一个可变对象来制作不可变副本,又名 [NSArray arrayWithArray:someNSMutableArrayObject]

所以我会这样做:

界面

MyDataStructure.h

// MyDataStructure.h
@interface MyDataStructure : NSObject
@property (nonatomic, strong) NSSet * mySet;
+ (instancetype)dataStructureWithDataStructure:(MyDataStructure*)dataStructure;
- (instancetype)initWithDataStructure:(MyDataStructure*)dataStructure;
@end

MyMutableDataStructure.h

// MyMutableDataStructure.h
#import "MyDataStructure.h"
@interface MyMutableDataStructure : MyDataStructure
@property (nonatomic, strong) NSMutableSet * mySet; // Only needs to redefine this property.  The instantiation methods will be borrowed from the immutable class.
@end;

实施

MyDataStructure.m

@implementation MyDataStructure

+ (instancetype)dataStructureWithDataStructure:(MyDataStructure *)dataStructure {
    return [[self alloc] initWithDataStructure:dataStructure];
}

- (instancetype)initWithDataStructure:(MyDataStructure *)dataStructure {
    self = [super init];
    if (self) {
        self.mySet = [NSSet setWithSet:dataStructure.mySet];
    }
    return self;
}

- (instancetype)mutableCopy {
    return [MyMutableDataStructure dataStructureWithDataStructure:self];
}    

@end

MyMutableDataStructure.m

@implementation MyMutableDataStructure

- (instancetype)initWithDataStructure:(MyDataStructure *)dataStructure {
    self = [super init];
    if (self) {
        self.mySet = [NSMutableSet setWithSet:dataStructure.mySet];
    }
    return self;
}

@end

By using this pattern the underlying data structure is always mutable, and its immutable version is just an immutable copy (or is it??).

没有。为了减少内存占用,不可变对象不需要与其可变对象相同的开销。

copyWithZone

只要确保你使用 [self class] 这样 MyMutableDataStructure 就可以继承这个方法和 return 它自己的类型,并且不要忘记在之后调用 -init +allocWithZone:

__typeof(self) 只是将变量“copy”声明为 self 的任何类型,因此它完全可以由可变子 class.

继承
- (id)copyWithZone:(NSZone *)zone
{
    __typeof(self) copy = [[[self class] allocWithZone:zone] init]; // don't forget init!
    copy.mySet = [self.mySet copyWithZone:zone];
    return copy;
}

^ 该方法进入 MyDataStructure

的实现

在您最初的实施中,

// We don't really need a copy, it's Immutable

虽然您的项目可能确实如此,但这是对命名约定的滥用。以 -copy 开头的方法应该 return 对象的副本。

跑题了:

我想谈一谈我在您原来的问题中看到的一些事情...首先是关于用 "immutable object" 指针引用隐藏 "mutable object"。也许您也需要这个功能,所以这里有一个更强大的方法来实现它以及为什么。

MyClass.h(注意没有所有权属性 - 因为它只是一个真正的别名 - 也就是说我们只需要它来合成 setter 和 getter)

@property (nonatomic) NSSet *mySet;

MyClass.m

@interface MyClass () {
    NSMutableSet *_myMutableSet;
}
@implementation MyClass

// returns a copy of the internal mutable set as an NSSet
- (NSSet*)mySet {
    return [NSSet setWithSet:_myMutableSet];
}

// setter saves the internal mutable set as a copy of whatever set you hand it
- (NSSet*)setMySet:(NSSet*)mySet {
    _myMutableSet = [NSMutableSet setWithSet:mySet];
}

我将_myMutableSet定义为一个ivar,以进一步保护getters和setters。在您的原始代码中,您将 @property (...) myMutableSet 放在 .m 文件的接口扩展名中。这会自动合成 getter 和 setter,因此即使声明显然是 "private",也可以调用 [myDataStructure performSelector:@selector(setMutableSet:) withObject:someMutableSet];,尽管有 "Undeclared selector" 编译器警告,它仍然可以工作。

此外,在您最初的 -mySet 实现中:

- (NSSet *)mySet {
    return [_myMutableSet copy];
}

此 return 是指向 _myMutableSet 的指针的副本,类型转换为 NSSet*。因此,如果有人将其重新转换为 NSMutableSet*,他们可以更改底层可变集。