2

I have a superclass called SuperClass a read-only property. That looks like this:

@property (nonatomic, strong, readonly) NSArray *arrayProperty;

In a subclass I need an initializer that takes a instance of SuperClass as a parameter:

- (instancetype)initWithSuperClass:(SuperClass *)superClass

I created a GitHub sample project that shows what the problem is: https://github.com/marosoaie/Objc-test-project

I cannot do _arrayProperty = superClass.arrayProperty in the initializer. I want to keep the property read-only in SubClass as well.

Any ideas on how this could be solved?

I know I could declare the property as readwrite in a class extension inside the SubClass implementation file, but I'm hoping that there's a better solutions than this.

Edit: SuperClass.h

#import <Foundation/Foundation.h>

@interface SuperClass : NSObject
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

@property (nonatomic, strong, readonly) NSString *stringProperty;
@property (nonatomic, strong, readonly) NSArray *arrayProperty;

@end

SuperClass.m

#import "SuperClass.h"

@implementation SuperClass

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
    self = [super init];
    if (self) {
        _arrayProperty = dictionary[@"array"];
        _stringProperty = dictionary[@"string"];
    }
    return self;
}

@end

SubClass.h:

#import <Foundation/Foundation.h>
#import "SuperClass.h"

@interface SubClass : SuperClass

@property (nonatomic, strong, readonly) NSString *additionalStringProperty;
- (instancetype)initWithSuperClass:(SuperClass *)superClass;

@end

SubClass.m:

#import "SubClass.h"

@implementation SubClass
@synthesize additionalStringProperty = _additionalStringProperty;

- (NSString *)additionalStringProperty
{
    if (!_additionalStringProperty) {
        NSMutableString *mutableString = [[NSMutableString alloc] init];

        for (NSString *string in self.arrayProperty) {
            [mutableString appendString:string];
        }

        _additionalStringProperty = [mutableString copy];
    }
    return _additionalStringProperty;
}

- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
    self = [super init];
    if (self) {
//        Doesn't work
//        _stringProperty = superClass.stringProperty;
//        _arrayProperty = superClass.arrayProperty;

    }
    return self;
}



@end
marosoaie
  • 2,352
  • 23
  • 32
  • I guess, you don't require to do anything, it will get inherited as readOnly property only. No need to declare or set anything in subclass property declarations. – Mrunal Dec 20 '14 at 08:40
  • But how can set the property in the initializer I mentioned in the question? `- (instancetype)initWithSuperClass:(SuperClass *)superClass` – marosoaie Dec 20 '14 at 08:41
  • What do you need that initializer for? Do you want to copy the information from the given instance to the new one? – Nero Dec 20 '14 at 08:43
  • @marosoaie : Go through this http://benedictcohen.co.uk/blog/archives/149 This is an example, to use private properties. – Mrunal Dec 20 '14 at 08:45
  • You require to create a override property in SuperClass.m file and when you inherit this in ChildClass, that will only have rights to read that property in ChildClass. – Mrunal Dec 20 '14 at 08:46
  • Why can't you do `_arrayProperty = superClass.arrayProperty`? – DarkDust Dec 20 '14 at 08:47
  • 2
    Maybe you should paste the code from GitHub here, it’s not that long. People won’t have to click links to answer and links can go dead. – Tricertops Dec 20 '14 at 09:07
  • @DarkDust The error is "Use of undeclared identifier '_arrayProperty'. I know that ivars are supposed to be protected by default, so I'm not sure why It doesn't allow me to access it – marosoaie Dec 20 '14 at 09:19
  • Ivars are `@private` by default, you have to make them `@protected` to be accessible from subclasses. – Tricertops Dec 20 '14 at 12:56
  • Correction: Automatic _property ivars_ are `@private` by default. – Tricertops Dec 20 '14 at 12:59

4 Answers4

4

You already exposed an initializer, that writes to that readonly property -initWithDictionary:. Call that in your SubClass, instead [super init]:

- (instancetype)initWithSuperClass:(SuperClass *)superClass {
    NSDictionary *dict = @{
        @"array": superClass.arrayProperty,
        @"string": superClass.stringProperty,
    };
    self = [super initWithDictionary:dict];
    if (self) {
        // Nothing here.
    }
    return self;
}

It’s quite common to have an initializer for readonly properties, although using dictionary is not that good solution. Typically, I would create:

- (instancetype)initWithArray:(NSArray *)array string:(NSString *)string;
Tricertops
  • 8,492
  • 1
  • 39
  • 41
  • But isn't there anyway of accessing the ivar used for the property in the subclass? (_arrayProperty) – marosoaie Dec 20 '14 at 09:08
  • Yes, there is. You have to declare the ivar yourself in the SuperClass, mark it as `@protected` and then synthesize the property in its implementation to link it with the desired readonly property. – Tricertops Dec 20 '14 at 12:54
  • @marosoaie Sorry, marking them `@protected` is not necessary, but you have to declare them explicitly, since automatic property ivars are `@private` by default. – Tricertops Dec 20 '14 at 13:00
  • Thanks, that's what I was looking for. Declaring the ivar in the header made it visible in the subclass – marosoaie Dec 20 '14 at 17:41
2

First of all, there is a bug in your test setup: Your key in - (instancetype)initWithDictionary:(NSDictionary *)dictionary is @"array", where the array contains @"arrayProperty".

Regarding your problem:

//...
@interface SuperClass : NSObject
{
    @protected // this is what you want: a protected class property, accessible in subclasses, but no where else
    NSString *_stringProperty;
    NSArray *_arrayProperty;
}

@property (nonatomic, strong, readonly) NSString *stringProperty;
@property (nonatomic, strong, readonly) NSArray *arrayProperty;

- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

@end


// SubClass.m
//...
@implementation SuperClass

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
    self = [super init];
    if (self) {
        _arrayProperty = dictionary[@"arrayProperty"]; // this was @"array", so could not work
        _stringProperty = dictionary[@"stringProperty"]; // same here
    }
    return self;
}

@end

Then it works. In addition, I would write

@interface SubClass ()

@property (nonatomic, strong, readwrite) NSString *additionalStringProperty;

@end


@implementation SubClass

- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
    self = [super init];
    if (self) {
        _stringProperty = superClass.stringProperty;
        _arrayProperty = superClass.arrayProperty;
    }
    return self;
}

because I prefer the readwrite property in a class extension over the @synthesize magic. But this is a personal opinion.

One main issue regarding to class design still holds: What happens if (similar to your test setup) the dictionary of the superclass does not contain the key? Then it won't be initialized, which is not a good idea, because you expect them to be initialized. So you should check in the subclass if superclass.stringProperty is not nil and add a standard constructor for the superclass to avoid that the two dictionaries are uninitialized.

Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
  • What do you mean by “uninitialized” in the last paragraph? They will be initialized to nil, which is ok. – Tricertops Dec 20 '14 at 20:54
  • 1
    Uninitialized is a garbage value. – Tricertops Dec 26 '14 at 12:42
  • I meant by "uninitialized" that no constructor (`-(id)init`) was called. In Objective-C with ARC all instance variables of objects are initialized to `nil` in `alloc`. – Michael Dorner Dec 26 '14 at 14:50
  • That’s what I’m saying: _initialized to nil_. Also, it’s not related to ARC, since it objects were always initialized to zeros. ARC initializes object refs in function variables to `nil`. – Tricertops Dec 28 '14 at 20:26
  • 1
    A small reminder: in the subclass use the ivar (_myVariable) instead of the accessor (self.myVariable). – Rudolf Real Jan 17 '17 at 15:21
0

In your SuperClass.m:

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
    self = [super init];
    if (self) {
       // these were always nil, check your dictionary keys
       _arrayProperty = dictionary[@"arrayProperty"];
       _stringProperty = dictionary[@"stringProperty"];
    }
    return self;
}

In your SubClass.m:

 @interface SubClass ()

 @property (strong, nonatomic) NSString * additionalStringProperty;

 @property (strong, nonatomic) NSString * subClassString;
 @property (strong, nonatomic) NSArray * subClassArray;

 @end

@implementation SubClass
- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
    self = [super init];
    if (self) {
       _subClassString = superClass.stringProperty;
       _subClassArray  = superClass.arrayProperty;
    }
return self;
}
Louis Tur
  • 1,303
  • 10
  • 16
  • 1
    This would require duplicating every property in the superclass, I think there should be a better way – marosoaie Dec 20 '14 at 09:23
  • if you decide to declare readonly variables in your header, you need to have private versions of those variables declared in your implementation file to actually manipulate them without calling their accessor methods. meaning, you can make changes by calling `_stringProperty`, but not `self.stringProperty`. moreover, if you plan on calling them from subclasses, you have to create read/write variables and assign them to the super class's read-only properties.. you can't call self.stringProperty in a subclass and alter it's value if it's inheriting it as a read-only property – Louis Tur Dec 20 '14 at 09:38
0

I tried the answers here to no avail. What ended up working for me was this answer which mentions that you should directly access the member variable (after declaring it as protected) like so:

self->_stringProperty = @"some string";
Community
  • 1
  • 1
Stunner
  • 12,025
  • 12
  • 86
  • 145