Objective-C Processable Subclass Template?

Is there a standard template for implementing a pair of mutable / immutable object classes in Objective-C? I currently have something like the following that I wrote based on 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 

It works, but exposes iVar. Is there a more efficient implementation that fixes this situation?

+5
source share
3 answers

Your solution follows a very good template: a mutable class does not duplicate anything from its base and provides additional functionality without saving any additional state.

It works, but exposes iVar.

Because @protected instance @protected are the default, the public _value is only displayed for classes that inherit from MyObject . This is a good compromise because it helps to avoid data duplication by not publicly revealing the data element used to store the state of the object.

+4
source

Is there a more efficient implementation that fixes this situation?

Declare the value property in the class extension. An extension is like a category with no name, but should be part of the class implementation. In the file MyMutableObject.m do the following:

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

Now you have declared your property, but it is visible only inside your implementation.

0
source

The answer from dasblinkenlight is correct. The sample asked in the question is good. I provide an alternative that differs in two ways. Firstly, due to the unused iVar in a mutable class, the property is atomic. Secondly, as in many base classes, a copy of an immutable instance simply returns itself.

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 

Test code snippet:

 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 immediately 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, the base class requires discipline to use getters instead of iVar. Many believe that this is good, but this discussion is not the topic here.

The slight difference that you might notice is that I used the copy attribute for the property. It can be made strong instead with very little code change.

0
source

Source: https://habr.com/ru/post/1200894/


All Articles