3

I have the following code:

// ClassA.h
@interface ClassA : NSObject 
@property (nonatomic, retain, readonly) id my_property;
@end

// ClassA.m
@implementation ClassA
@synthesize my_property;
- (id)init {
    if (self = [super init]) {
        self->my_property = [NSNumber numberWithInt:1];
    }
    return self;
}

- (void)debug {
    NSLog(@"%@", self->my_property);
}
@end

// ClassB.h
#import "ClassA.h"

@interface ClassB : ClassA 
@end

// ClassB.m
#import "ClassB.h"

@implementation ClassB
@synthesize my_property;
- (id)init {
    if (self = [super init]) {
        self->my_property = [NSNumber numberWithInt:2];
    }
    return self;
}
@end

I call the above code like so:

ClassB *b = [[ClassB alloc] init];
[b debug];

The output is 1. If I change the -[Class A debug] method to use self.my_property, the output is 2.

My (limited) understanding is that with the "modern" Objective-C runtime, class ivars are generated dynamically. Can subclasses of classes with these dynamically-generated ivars access said instance variables? If I do not include the @synthesize my_property line in ClassB.m, the compiler gives me the error:

error: 'struct ClassB' has no member named 'my_property'

However, if I change the -[ClassB init] method to use property notation rather than ivar notation, it will recognise the inherited property, albeit not let me write to it on account of it being read-only. How can I write to it while maintaining its read-only state to consumers of my API?

Clarification: A couple of answers have pointed out that I can use vanilla ivars. That is indeed correct, but then the public-facing @interface reveals implementation details that are best kept private. As in this post on SO:

I prefer my public-facing interfaces to be as minimal and clean as possible, only revealing aspects of my class that are pertinent.

Community
  • 1
  • 1
Aidan Steele
  • 10,999
  • 6
  • 38
  • 59

3 Answers3

2

My guess is that if you declare the ivar explicitly in ClassA.h, it will work as expected. You will not need the @synthesize in ClassB.m, and you will be able to access the ivar with arrow notation and the property with dot notation as usual.

Felixyz
  • 19,053
  • 14
  • 65
  • 60
  • Sorry for the confusion, I have updated the OP to clarify my intentions. – Aidan Steele Feb 08 '11 at 08:05
  • 1
    Unfortunately, keeping things private in Objective-C also keeps them secret from sub-classes. That's why the ivars of most of Apple's classes can be read by anyone. If you're willing to delve into the black magic of class clusters, you can create objects that basically front as one class but are actually instances of a private subclass (with hidden ivars). I agree that the situation you ran into is highly unintuitive. – Felixyz Feb 08 '11 at 08:11
  • "How can I write to it while maintaining its read-only state to consumers of my API?" -- Again, the only thing I can think of is class clusters. But be aware that it's always possible for users to access an object's ivars by using introspection. So it might not be worth going to a lot of trouble to prevent them from knowing what those ivars are. – Felixyz Feb 08 '11 at 08:16
1

In your interface, you can declare your variable as private with @private. You can still make it a read-only property. Unless I am misunderstanding your intent, this would do it. You can access it inside your class, and from inside descendant classes, but outside users can only read it.

jakev
  • 2,815
  • 3
  • 26
  • 39
0

If you want to override the value put in to my_property, the correct way is for the initialiser for class A to have a parameter that is the value of my_property. i.e.

// ClassA.h
@interface ClassA : NSObject 
@property (nonatomic, retain, readonly) id my_property;

// designated initialiser
-(id) initWithMyProperty: (NSNumber*) newMyProperty;

@end

// ClassA.m
@implementation ClassA
@synthesize my_property;

- (id)initWithMyProperty: (NSNumber*) newMyProperty 
{
    if (self = [super init]) 
    {
        my_property = [newMyProperty retain];
    }
    return self;
}

-(id) init
{
    return [self initWithMyProperty: [NSNumber numberWithInt: 1]];
}

-(void) dealloc
{
    [my_property release];
    [super dealloc];
}

...

@end

// ClassB.m
#import "ClassB.h"

@implementation ClassB
- (id)init 
{
    if (self = [super initWithMyProperty: [NSNumber numberWithInt:2]]) 
    {
        // Any other initialisation
    }
    return self;
}

@end
JeremyP
  • 84,577
  • 15
  • 123
  • 161