提问人:wL_ 提问时间:8/22/2014 更新时间:12/19/2017 访问量:1784
Objective-C 可变子类模式?
Objective-C Mutable subclass pattern?
问:
在 Objective-C 中实现可变/不可变对象类对是否有标准模式? 我目前有如下内容,这是我根据此链接编写的
不可变类:
@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。有没有更好的实现方式来纠正这种情况?
答:
您的解决方案遵循一个非常好的模式:可变类不会从其基复制任何内容,并且在不存储任何其他状态的情况下公开附加功能。
这有效,但它暴露了 iVar。
由于实例变量默认为实例变量,因此公开的变量仅对继承的类可见。这是一个很好的权衡,因为它可以帮助您避免数据重复,而不会公开用于存储对象状态的数据成员。@protected
_value
MyObject
有没有更好的实现方式来纠正这种情况?
在类扩展中声明属性。扩展就像一个没有名称的类别,但必须是类实现的一部分。在 MyMutableObject.m 文件中,执行以下操作:value
@interface MyMutableObject ()
@property(nonatomic, readwrite, strong) value
@end
现在,您已经声明了属性,但它仅在实现中可见。
dasblinkenlight的答案是正确的。问题中提供的模式很好。我提供了一个在两个方面不同的替代方案。首先,以可变类中未使用的 iVar 为代价,该属性是原子的。其次,与许多基础类一样,不可变实例的副本只是返回 self。
MyObject.h:
@interface MyObject : NSObject <NSCopying, NSMutableCopying>
@property (atomic, readonly, copy) NSString *value;
- (instancetype)initWithValue:(NSString *)value NS_DESIGNATED_INITIALIZER;
@end
我的对象.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 属性。相反,只需对代码进行很少的更改,就可以使它变得强大。
评论
mutableCopyWithZone:
NS{Mutable}Array