Overriding property getters with lazy loading in Objective-C
Asked Answered
M

2

9

I usually lazy instantiate my @property objects in their getter methods like this:

@interface MyGenericClass : UIViewController
@property(nonatomic, readonly) UIImageView *infoImageView
// ...

@implementation GenericClass

- (UIImageView *)infoImageView
{
    if (!_infoImageView) {
        _infoImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"PlaceholderInfoImage"]];
    }
    return _infoImageView;
}

But when subclassing, I would often like to override some of the @properties to be more subclass specific. So I'd like to change the instantiation and do something like:

@interface MySpecificSubclass : MyGenericClass
//...

@implementation MySpecificSubclass

- (UIImageView *)infoImageView
{
    if (!_infoImageView) {
        _infoImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"SpecialInfoImage"]];
    }
    return _infoImageView;
}

But that's not possible, because the subclass can't access the _infoImageView iVar.

Is what I'm trying to do bad style? Or is there a common solution / best practice for this? The only solution I see is to make the iVar public, which feels like violating encapsulation principles...

It feels like this is such a very basic question, that there must be millions of answers already out there, but after searching for hours all I could find was Objective-C: Compiler error when overriding a superclass getter and trying to access ivar , but it provides no solution.

Muttonchops answered 9/10, 2013 at 15:26 Comment(0)
R
10

You might want to declare _infoImageView as a protected variable in the header file alongside with the property. Another idea is to create a public defaultImageView method to call inside the lazy getter. Something like this:

@interface MyGenericClass : UIViewController
@property (nonatomic, readonly) UIImageView *infoImageView

...

@implementation GenericClass

- (UIImageView *)infoImageView
{
    if (!_infoImageView) {
        _infoImageView = [self defaultImageView];
    }
    return _infoImageView;
}

- (UIImageView *)defaultImageView
{
    return [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"PlaceholderInfoImage"]];
}

...

@interface MySpecificSubclass : MyGenericClass

...

@implementation MySpecificSubclass

- (UIImageView *)defaultImageView
{
    return [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"SpecialInfoImage"]];
}
Redeploy answered 9/10, 2013 at 15:30 Comment(0)
A
4

You could use the technique that UIViewController uses for its view:

- (UIView *)view{
    if(!_view){
        [self loadView];
        NSAssert(_view, @"View must be set at end of loadView");
    }
    return _view;
}

- (void)loadView{
    // subclasses must set self.view in their override of this.
    NSAssert(NO, @"To be overridden by subclass");
}
Aft answered 7/12, 2013 at 20:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.