可变与不可变

时间:2015-06-26 18:45:32

标签: objective-c immutability nscopying mutability

我想知道实现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

我使用可变集作为底层数据结构的唯一原因是这个类的可变版本可以篡改它。 MyDataStructure本身并不需要一个可变的集合。因此,假设我已经实现了一些初始化工具来使这个课程变得有用,这里有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:返回不可变副本吗?所以我基本上将NSSet分配给NSMutableSet属性,不是吗?

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

  1. mySet应该是copy而不是strong
  2. 我的copyWithZone:实施也不对。我在第一篇文章中没有提到它,但该实现与数据结构的不可变版本(MyDataStructure)有关。正如我所读到的,不可变数据结构实际上并没有创建副本,它们只是自行返回。这很有道理。
  3. 由于2.,我需要在Mutable版本(copyWithZone:)中覆盖MyMutableDataStructure
  4. 说清楚:

    // 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. mySet的getter是否返回可变或不可变的实例?
    3. (之前没有列出)使用copy属性属性时,我真的需要copy信号吗?
    4. 感谢您耐心等待。 最好的。

2 个答案:

答案 0 :(得分:1)

Apple就是这样

所有Apple的库中使用的模式都是,类的可变版本可以通过-mutableCopy创建,或者(假设该类称为NSSomething),然后可以创建它通过方法-initWithSomething:(NSSomething*)something+somethingWithSomething:(NSSomething*)somethingNSMutableSomething始终从NSSomething继承,因此构造函数方法是相同的。 (即+[NSArray arrayWithArray:]+[NSMutableArray arrayWithArray:]返回各自的实例类型,同时传入一个可变对象以生成不可变副本,即[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
  

通过使用这种模式,底层数据结构总是可变的,并且它的不可变版本只是一个不可变的副本(或者它是??)。

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

<强> copyWithZone

请确保您使用[self class],以便MyMutableDataStructure可以继承此方法并返回自己的类型,并且在-init <之后也不要忘记对+allocWithZone:的调用/ p>

__typeof(self)只是将变量“copy”声明为self类型,因此它可以被可变子类完全继承。

- (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

的实现

在您的原始实施中,

  

//我们真的不需要副本,它是不可变的

虽然这可能适用于您的项目,但这是滥用命名约定。以-copy开头的方法应该返回对象的副本。

稍微偏离主题:

我想谈谈我在原始问题中看到的一些事情......第一个是关于使用“不可变对象”指针引用隐藏“可变对象”。也许你也需要这个功能,所以这里有一个更健壮的方法和原因。

MyClass.h(注意没有所有权属性 - 因为它只是一个真正的别名 - 我们只需要这个用于你的setter和getters的合成)

@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,以进一步保护getter和setter。在原始代码中,将@property (...) myMutableSet放在.m文件的接口扩展中。这会自动合成getter和setter,所以即使声明显然是“私有”,也可以调用[myDataStructure performSelector:@selector(setMutableSet:) withObject:someMutableSet];,尽管“未声明的选择器”编译器警告,它仍然有效。

此外,在-mySet的原始实现中:

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

这将返回指向_myMutableSet的指针的副本,类型转换为NSSet*。因此,如果某人将其重新转换为NSMutableSet*,则可以更改基础可变集。

答案 1 :(得分:0)

  1. 我不知道你的模式是否正确,这取决于......如果这种方法对你来说更容易理解。那么它是正确的,但不要忘记为未来的开发人员添加评论。

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

  3. copy,关于这一点,只需参考此discussion

  4. 希望这对你有所帮助。