Say I have this Class
@interface CustomClass : NSObject
@property (nonatomic, strong) NSArray * nicestArrayEver;
@end
And I want to create a subClass of CustomClass, but here is the catch
@interface ASubClassCustomClass : CustomClass
@property (nonatomic, strong) NSMutableArray * nicestArrayEver;
@end
The issue as you can imagine is that when I initialize ASubClassCustomClass and call it's super initializer (since there is other properties required) the inmutable nicestArrayEver is created.. how can I avoid it's creation so I can set the mutable one?
Note: This is just an example, the real implementation calls a heavy to create and really customized subclass (is not an NSArray).
You can make it work, by using different backing variables, when synthesizing it looks like this: @synthesize nicestArrayEver = nicestArrayEverSubClass_;
#import <Foundation/Foundation.h>
@interface CustomClass : NSObject
@property (nonatomic, strong) NSArray * nicestArrayEver;
@end
@implementation CustomClass
@synthesize nicestArrayEver ;
-(id)init
{
if (self = [super init]) {
nicestArrayEver = [[NSArray alloc] init];
}
return self;
}
@end
@interface ASubClassCustomClass : CustomClass
@property (nonatomic, strong) NSMutableArray * nicestArrayEver;
@end
@implementation ASubClassCustomClass
@synthesize nicestArrayEver = nicestArrayEverSubClass_;
-(id)init{
if (self = [super init]) {
nicestArrayEverSubClass_ = [[NSMutableArray alloc] init];
}
return self;
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
CustomClass *c1 = [[[CustomClass alloc] init] autorelease];
ASubClassCustomClass *c2 = [[[ASubClassCustomClass alloc] init] autorelease];
NSLog(@"%@", NSStringFromClass([[c1 nicestArrayEver] class]));
NSLog(@"%@", NSStringFromClass([[c2 nicestArrayEver] class]));
}
return 0;
}
output
2012-05-27 01:59:16.221 NicestArray[2312:403] __NSArrayI
2012-05-27 01:59:16.225 NicestArray[2312:403] __NSArrayM
Another approach could be to have 2 init methods in the base class, one, that will instantiate the property and one, that won't, but leaves that task for the child class — this will prevent you from creating expensive objects just to throw them away.
Now the base class could get instantiated directly with the second init and go to a false state. You can avoid this by checking the self class type with isMemberOfClass:
, and throw an error, if the class type is the base class.
@interface CustomClass : NSObject
@property (nonatomic, strong) NSArray * nicestArrayEver;
-(id)initWithoutArray;
@end
@implementation CustomClass
@synthesize nicestArrayEver ;
-(id) initWithoutArray
{
if (self = [super init]) {
if ([self isMemberOfClass:[CustomClass class]]) {
[NSException raise:@"AbstractMethodCall" format:@"%@ should be called only from Subclasses of %@", NSStringFromSelector(_cmd), NSStringFromClass([self class])];
}
}
return self;
}
-(id)init
{
if (self = [super init]) {
nicestArrayEver = [[NSArray alloc] init];
}
return self;
}
@end
@interface ASubClassCustomClass : CustomClass
@property (nonatomic, strong) NSMutableArray * nicestArrayEver;
@end
@implementation ASubClassCustomClass
@synthesize nicestArrayEver = nicestArrayEverSubClass_;
-(id)init{
if (self = [super initWithoutArray]) {
nicestArrayEverSubClass_ = [[NSMutableArray alloc] init];
}
return self;
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
CustomClass *c1 = [[[CustomClass alloc] init] autorelease];
ASubClassCustomClass *c2 = [[[ASubClassCustomClass alloc] init] autorelease];
NSLog(@"%@", NSStringFromClass([[c1 nicestArrayEver] class]));
NSLog(@"%@", NSStringFromClass([[c2 nicestArrayEver] class]));
//this works, as it is the subclass
ASubClassCustomClass *shouldWork = [[[ASubClassCustomClass alloc] init] autorelease];
// ouch!
CustomClass *shouldCrash = [[[CustomClass alloc] initWithoutArray] autorelease];
}
return 0;
}
I don't see the reason why you would want to do that, but I would advise you to do as follows: in your subclass declare a separate NSMutableArray property (let's call it nicestMutableArrayEver) and override the getter for your superclass NSArray property to return the mutableArray instance:
- (NSArray *)nicestArrayEver {
return [self nicestMutableArrayEver];
}
This way, you could get back your mutableArray whenever you reference the superclass property.
Best,
Properties should almost never have mutable type. If they do, then a caller can obtain the pointer and mutate the object's properties behind its back. If a property should be externally mutable, that should be through mutation methods.
Remember that properties define interface, not implementation. @synthesize
can create implementation from the property declaration, but if that would do the wrong thing you shouldn't use it.
So, the first step is to define the interface for your class and subclass, without regard for implementation. Only after you know what the interfaces should be should you design the implementation of each.
Whether or not a property is externally mutable, the backing instance variable may need to be mutable. The class may need to mutate its own property purely internally.
You could make the base class have a designated initializer which takes the array object as a parameter (of type id
so that the subclass's override doesn't have to cast to treat it as an NSMutableArray*
). Then, the class's "normal" initializers would call that designated initializer with the NSArray
to use. The subclass's designated initializer would call the superclass's designated initializer, passing in the NSMutableArray
to use.
Alternatively, the base class initializer(s) could call out to another method to obtain the array. The base class implementation would return an NSArray
. The subclass could override that method to return an NSMutableArray
.
You can't really do that. Just create CustomClass
with NSMutableArray
. You could create them as type id
and check isKindOfClass:
but that's just a chore and not really necessary.
There are really only two reasons I could see to do what you're asking:
- To avoid the extra overhead of
NSMutableArray
- To prevent
CustomClass
from modifying the contents of the array unless it is a ASubClassCustomClass
.
While these are good goals, I'd say that in this case it's worth it to simplify a bit.