简体   繁体   English

Objective-C可变子类模式?

[英]Objective-C Mutable subclass pattern?

Is there a standard pattern for implementing a mutable/immutable object class pair in Objective-C? 在Objective-C中是否有用于实现可变/不可变对象类对的标准模式? I currently have something like the following, which I wrote based off this link 我目前有类似以下内容,我根据此链接编写

Immutable Class: 不可变类:

@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

Mutable Class: 可变类:

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


@implementation MyMutableObject
@dynamic value;

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

@end

This works, but it exposes the iVar. 这有效,但它暴露了iVar。 Is there a better implementation that remedies this situation? 是否有更好的实施可以解决这种情况?

Your solution follows a very good pattern: the mutable class does not duplicate anything from its base, and exposes an additional functionality without storing any additional state. 您的解决方案遵循一个非常好的模式:可变类不会从其基础复制任何内容,并且在不存储任何其他状态的情况下公开其他功能。

This works, but it exposes the iVar. 这有效,但它暴露了iVar。

Due to the fact that instance variables are @protected by default, the exposed _value is visible only to the classes inheriting MyObject . 由于实例变量默认情况下是@protected ,因此公开的_value仅对继承MyObject的类可见。 This is a good tradeoff, because it helps you avoid data duplication without publicly exposing the data member used for storing the state of the object. 这是一个很好的权衡,因为它可以帮助您避免数据重复,而不会公开暴露用于存储对象状态的数据成员。

Is there a better implementation that remedies this situation? 是否有更好的实施可以解决这种情况?

Declare the value property in a class extension. 在类扩展中声明value属性。 An extension is like a category without a name, but must be part of the class implementation. 扩展名类似于没有名称的类别,但必须是类实现的一部分。 In your MyMutableObject.m file, do this: 在MyMutableObject.m文件中,执行以下操作:

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

Now you've declared your property, but it's only visible inside your implementation. 现在您已经声明了您的属性,但它只在您的实现中可见。

The answer from dasblinkenlight is correct. dasblinkenlight的答案是正确的。 The pattern provided in the question is fine. 问题中提供的模式很好。 I provide an alternative that differs in two ways. 我提供了两种不同的替代方案。 First, at the expense of an unused iVar in the mutable class, the property is atomic. 首先,以可变类中未使用的iVar为代价,属性是原子的。 Second, as with many foundation classes, a copy of an immutable instance simply returns self. 其次,与许多基础类一样,不可变实例的副本只返回self。

MyObject.h: MyObject.h:

@interface MyObject : NSObject <NSCopying, NSMutableCopying>

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

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

@end

MyObject.m 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: MyMutableObject.h:

#import "MyObject.h"

@interface MyMutableObject : MyObject

@property (atomic, copy) NSString *value;

@end

MyMutableObject.m: 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

A fragment of test code: 测试代码片段:

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);

Some debugging right after the last line above: 在上一行之后的一些调试:

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

As mentioned in the comments this requires discipline in the base class to use the getter instead of the iVar. 正如评论中所提到的,这需要在基类中使用getter而不是iVar。 Many would consider that a good thing, but that debate is off-topic here. 许多人会认为这是一件好事,但这场辩论在这里是偏离主题的。

A minor difference you might notice is that I have used the copy attribute for the property. 您可能会注意到的一个小差异是我已经使用了属性的copy属性。 This could be made strong instead with very little change to the code. 这可以变得强大,而代码变化很小。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM