6 Stimmen

Objective-C Veränderbares Unterklasse-Muster?

Gibt es ein Standardmuster für die Implementierung eines Paars von mutabler/immutterabler Objektklasse in Objective-C? Derzeit habe ich etwas wie folgt, das ich basierend auf diesem Link geschrieben habe

Unveränderliche Klasse:

@interface MyObject : NSObject  {
    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

Veränderliche Klasse:

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

@implementation MyMutableObject
@dynamic value;

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

@end

Dies funktioniert, aber es macht die iVar zugänglich. Gibt es eine bessere Implementierung, die diese Situation behebt?

4voto

Sergey Kalinichenko Punkte 694383

Ihre Lösung folgt einem sehr guten Muster: Die veränderliche Klasse dupliziert nichts von ihrer Basis und bietet eine zusätzliche Funktionalität, ohne zusätzlichen Zustand zu speichern.

Dies funktioniert, aber es stellt den iVar offen.

Aufgrund der Tatsache, dass Instanzvariablen standardmäßig @protected sind, ist das freigegebene _value nur für Klassen sichtbar, die MyObject erben. Dies ist ein guter Kompromiss, weil es Ihnen hilft, die Datenredundanz zu vermeiden, ohne das Datenmitglied öffentlich freizulegen, das für das Speichern des Zustands des Objekts verwendet wird.

0voto

Caleb Punkte 121906

Gibt es eine bessere Implementierung, die diese Situation behebt?

Deklarieren Sie die value-Eigenschaft in einer Klassenerweiterung. Eine Erweiterung ist wie eine Kategorie ohne Namen, muss aber Teil der Klassenumsetzung sein. In Ihrer MyMutableObject.m-Datei tun Sie Folgendes:

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

Jetzt haben Sie Ihre Eigenschaft deklariert, aber sie ist nur innerhalb Ihrer Implementierung sichtbar.

0voto

Rik Renich Punkte 694

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.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X