可变与不可变的正确模式
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
属性,不是吗?
编辑:在深入研究这个问题时,我发现了一些与此问题相关的更多问题。
mySet
应该是 copy
而不是 strong
.
- 我的
copyWithZone:
实施也不正确。我没有在第一个 post 中提到它,但该实现与数据结构的不可变版本 (MyDataStructure
) 相关。正如我所读到的,不可变数据结构实际上并不创建副本,它们只是 return 自己。有道理。
- 因为 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
乍一看似乎很棘手,但我想我已经掌握了窍门。所以剩下的问题是:
- 模式是否正确?
- getter for
mySet
return 是可变实例还是不可变实例?
- (之前未列出)使用
copy
属性 属性时我真的需要 copy
信号吗?
感谢您耐心阅读到这里。
最好的。
我无法判断您的模式是否正确,这取决于.. 如果您更容易理解这种方法。那么它是正确的,但不要忘记为未来的开发者添加评论。
mySet
是不可变的,因为你将它初始化为不可变的 NSSet..
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*
,他们可以更改底层可变集。
我想知道实现可变数据结构与不可变数据结构的正确模式是什么。我理解这个概念及其工作原理,但如果使用底层 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
属性,不是吗?
编辑:在深入研究这个问题时,我发现了一些与此问题相关的更多问题。
mySet
应该是copy
而不是strong
.- 我的
copyWithZone:
实施也不正确。我没有在第一个 post 中提到它,但该实现与数据结构的不可变版本 (MyDataStructure
) 相关。正如我所读到的,不可变数据结构实际上并不创建副本,它们只是 return 自己。有道理。 - 因为 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
乍一看似乎很棘手,但我想我已经掌握了窍门。所以剩下的问题是:
- 模式是否正确?
- getter for
mySet
return 是可变实例还是不可变实例? - (之前未列出)使用
copy
属性 属性时我真的需要copy
信号吗?
感谢您耐心阅读到这里。 最好的。
我无法判断您的模式是否正确,这取决于.. 如果您更容易理解这种方法。那么它是正确的,但不要忘记为未来的开发者添加评论。
mySet
是不可变的,因为你将它初始化为不可变的 NSSet..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*
,他们可以更改底层可变集。