Objective-C可变子类模式?

时间:2014-08-21 23:43:18

标签: ios objective-c design-patterns mutable subclassing

在Objective-C中是否有用于实现可变/不可变对象类对的标准模式? 我目前有类似以下内容,我根据this link

编写

不可变类:

@interface MyObject : NSObject <NSMutableCopying> {
    NSString *_value;
}

@property (nonatomic, readonly, strong) NSString *value;
- (instancetype)initWithValue:(NSString *)value;

@end

@implementation MyObject
@synthesize value = _value;
- (instancetype)initWithValue:(NSString *)value {
    self = [self init];
    if (self) {
        _value = value;
    }
    return self;
}


- (id)mutableCopyWithZone:(NSZone *)zone {
    return [[MyMutableObject allocWithZone:zone] initWithValue:self.value];
}

@end

可变类:

@interface MyMutableObject : MyObject
@property (nonatomic, readwrite, strong) NSString *value;
@end


@implementation MyMutableObject
@dynamic value;

- (void)setValue:(NSString *)value {
    _value = value;
}

@end

这有效,但它暴露了iVar。是否有更好的实施可以解决这种情况?

3 个答案:

答案 0 :(得分:4)

您的解决方案遵循一个非常好的模式:可变类不会从其基础复制任何内容,并且在不存储任何其他状态的情况下公开其他功能。

  

这样可行,但它暴露了iVar。

由于默认情况下实例变量为@protected,因此只有继承_value的类才能看到公开的MyObject。这是一个很好的权衡,因为它可以帮助您避免数据重复,而不会公开暴露用于存储对象状态的数据成员。

答案 1 :(得分:0)

  

是否有更好的实施可以解决这种情况?

在类扩展中声明value属性。扩展名类似于没有名称的类别,但必须是类实现的一部分。在MyMutableObject.m文件中,执行以下操作:

@interface MyMutableObject ()
@property(nonatomic, readwrite, strong) value
@end

现在你已经宣布了你的财产,但它只在你的实施中可见。

答案 2 :(得分:0)

dasblinkenlight的回答是正确的。问题中提供的模式很好。我提供了两种不同的替代方案。首先,以可变类中未使用的iVar为代价,属性是原子的。其次,与许多基础类一样,不可变实例的副本只返回self。

MyObject.h:

@interface MyObject : NSObject <NSCopying, NSMutableCopying>

@property (atomic, readonly, copy) NSString *value;

- (instancetype)initWithValue:(NSString *)value NS_DESIGNATED_INITIALIZER;

@end

MyObject.m

#import "MyObject.h"
#import "MyMutableObject.h"

@implementation MyObject

- (instancetype)init {
    return [self initWithValue:nil];
}

- (instancetype)initWithValue:(NSString *)value {
    self = [super init];
    if (self) {
        _value = [value copy];
    }
    return self;
}

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

- (id)mutableCopyWithZone:(NSZone *)zone {
    // Do not use the iVar here or anywhere else.
    // This pattern requires always using self.value instead of _value (except in the initializer).
    return [[MyMutableObject allocWithZone:zone] initWithValue:self.value];
}

@end

MyMutableObject.h:

#import "MyObject.h"

@interface MyMutableObject : MyObject

@property (atomic, copy) NSString *value;

@end

MyMutableObject.m:

#import "MyMutableObject.h"

@implementation MyMutableObject

@synthesize value = _value; // This is not the same iVar as in the superclass.

- (instancetype)initWithValue:(NSString *)value {
    // Pass nil in order to not use the iVar in the parent.
    // This is reasonably safe because this method has been declared with NS_DESIGNATED_INITIALIZER.
    self = [super initWithValue:nil];
    if (self) {
        _value = [value copy];
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    // The mutable class really does need to copy, unlike super.
    return [[MyObject allocWithZone:zone] initWithValue:self.value];
}

@end

测试代码片段:

NSMutableString *string = [NSMutableString stringWithString:@"one"];
MyObject *object = [[MyObject alloc] initWithValue:string];
[string appendString:@" two"];
NSLog(@"object: %@", object.value);
MyObject *other = [object copy];
NSAssert(object == other, @"These should be identical.");
MyMutableObject *mutable1 = [object mutableCopy];
mutable1.value = string;
[string appendString:@" three"];
NSLog(@"object: %@", object.value);
NSLog(@"mutable: %@", mutable1.value);

在上一行之后进行一些调试:

2017-12-15 21:51:20.800641-0500 MyApp[6855:2709614] object: one
2017-12-15 21:51:20.801423-0500 MyApp[6855:2709614] object: one
2017-12-15 21:51:20.801515-0500 MyApp[6855:2709614] mutable: one two
(lldb) po mutable1->_value
one two

(lldb) po ((MyObject *)mutable1)->_value
 nil

正如评论中所提到的,这要求基类中的纪律使用getter而不是iVar。许多人会认为这是一件好事,但这场辩论在这里是偏离主题的。

您可能会注意到的一个小差异是我已经使用了属性的copy属性。这可以很强大,而代码的变化很小。