Die Antwort von dasblinkenlight ist korrekt. Das im Fragestellungsmuster bereitgestellte Muster ist in Ordnung. Ich biete eine Alternative an, die sich in zwei Punkten unterscheidet. Erstens ist die Eigenschaft auf atomic gesetzt, jedoch auf Kosten einer ungenutzten iVar in der Mutable-Klasse. Zweitens gibt eine Kopie einer unveränderlichen Instanz einfach sich selbst zurück.
MyObject.h:
@interface MyObject : NSObject
@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 {
// Hier oder an anderer Stelle nicht die iVar verwenden.
// Dieses Muster erfordert die Verwendung von self.value anstelle von _value (außer bei der Initialisierung).
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; // Dies ist nicht dieselbe iVar wie in der Oberklasse.
- (instancetype)initWithValue:(NSString *)value {
// Übergabe von nil, um die iVar in der Elternklasse nicht zu verwenden.
// Dies ist relativ sicher, da diese Methode mit NS_DESIGNATED_INITIALIZER deklariert wurde.
self = [super initWithValue:nil];
if (self) {
_value = [value copy];
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
// Die Mutable-Klasse muss wirklich kopieren, im Gegensatz zur Superklasse.
return [[MyObject allocWithZone:zone] initWithValue:self.value];
}
@end
Ein Ausschnitt des Testcodes:
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, @"Diese sollten identisch sein.");
MyMutableObject *mutable1 = [object mutableCopy];
mutable1.value = string;
[string appendString:@" three"];
NSLog(@"object: %@", object.value);
NSLog(@"mutable: %@", mutable1.value);
Einige Debugging-Ausgaben direkt nach der letzten Zeile oben:
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
Wie in den Kommentaren erwähnt, erfordert dies Disziplin in der Basisklasse, den Getter anstelle von iVar zu verwenden. Viele würden das als positiv betrachten, aber diese Debatte ist hier off-topic.
Ein kleiner Unterschied, den Sie vielleicht bemerken, ist, dass ich das Kopierenattribut für die Eigenschaft verwendet habe. Dies könnte durch sehr geringe Änderungen im Code in strong geändert werden.