Better than writing dozens of empty getters?

I use a lazy instance for my properties so that my class is created and used as quickly as possible. To do this, I write a lot of "empty" getters:

- (VMPlacesListFilter *)currentFilter
{
    if (!_currentFilter) {
        _currentFilter = [[VMPlacesListFilter alloc] init];
    }

    return _currentFilter;
}

They are all the same: if the instance variable nil, call -alloc, and -initthe class properties, and then return the instance variable. Very common and simple.

If I do not create this getter myself, Objective-C automatic synthesis creates for me a getter that only executes the returned part (does not initialize the object if the instance variable nil).

Is there any way to avoid writing this template?

+4
source share
3 answers

, , , . , (, ) (, ) init.

- (instancetype) init {
    self = [super init];
    if( self ) {
        _cheapThing1 = [NSMutableArray array];
        _cheapThing2 = [[MyModelObject alloc] init];
    }
    return self;
}

- (ExpensiveThing*) expensiveThing
{
    if( _expensiveThing == nil ) {
        _expensiveThing = [[ExpensiveThing alloc] init];
    }
    return _expensiveThing;
}

- , . , .

, Objective-C, , Swift .

lazy var currentFilter = VMPlacesListFilter()
+2

-, @zpasternac, " " . Objective-C . , CoreData .

, - , LazyClass, , lazyArray (. ). , , , +alloc -init, . NSMutableDictionary, myVars. , API , .

, , . .

LazyClass.h

@interface LazyClass : NSObject

@property NSMutableDictionary *myVars;

// lazily initialized property
@property NSArray *lazyArray;

@end

LazyClass.m

#import "LazyClass.h"
#import <objc/objc-runtime.h>

@implementation LazyClass

@dynamic lazyArray;

- (instancetype)init {
    self = [super init];

    self.myVars = [NSMutableDictionary dictionary];

    return self;
}

- (NSMutableDictionary *)getMyVars {
    return self.myVars;
}

// the generated getter method
id dynamicGetterMethodIMP(id self, SEL _cmd) {
    // selector name, which is also the property name
    const char *selName = sel_getName(_cmd);
    NSString *selNSName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding];

    NSString *keyPath = [NSString stringWithFormat:@"myVars.%@", selNSName];
    if (![self valueForKeyPath:keyPath]) {
        // get the actual type of the property
        objc_property_t property = class_getProperty([self class], selName);
        const char *attr = property_getAttributes(property);
        NSString *attrString = [[NSString alloc] initWithCString:attr encoding:NSUTF8StringEncoding];
        NSString *typeAttr = [[attrString componentsSeparatedByString:@","] firstObject];
        NSString *typeName = [typeAttr substringWithRange:NSMakeRange(3, typeAttr.length - 4)];

        // the default initialization
        Class typeClass = NSClassFromString(typeName);
        [self setValue:[[typeClass alloc] init] forKeyPath:keyPath];
    }

    return [self valueForKeyPath:keyPath];
}

// the generated setter method
void dynamicSetterMethodIMP(id self, SEL _cmd, id value) {
    // get the property name out of selector name
    // e.g. setLazyArray: -> lazyArray
    NSString *propertyName = NSStringFromSelector(_cmd);
    propertyName = [propertyName stringByReplacingOccurrencesOfString:@"set" withString:@""];
    propertyName = [propertyName stringByReplacingOccurrencesOfString:@":" withString:@""];
    propertyName = [NSString stringWithFormat:@"%@%@", [propertyName substringToIndex:1].lowercaseString, [propertyName substringFromIndex:1]];

    NSString *keyPath = [NSString stringWithFormat:@"myVars.%@", propertyName];
    [self setValue:value forKeyPath:keyPath];
}

// dynamic method resolution
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
    if ([NSStringFromSelector(aSEL) containsString:@"set"]) {
        class_addMethod([self class], aSEL, (IMP)dynamicSetterMethodIMP, "^?");
    } else {
        class_addMethod([self class], aSEL, (IMP)dynamicGetterMethodIMP, "v@:");
    }

    return YES;
}

@end

Documentation

+2

If this verbosity is bothering you, I suppose you could squeeze lazy initializers that only need one-line initialization using the ternary operator:

- (VMPlacesListFilter *)currentFilter
{
    return _currentFilter ? : (_currentFilter = [[VMPlacesListFilter alloc] init]);
}

DISCLAIMER: I do not do this, but it is interesting that this can be done

+1
source

All Articles