比编写数十个空的getter方法更好的方法是什么?

4

我在属性上使用懒加载,以便尽快创建和使用我的类。为了实现这一点,我编写了很多像这样的“空”getter:

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

    return _currentFilter;
}

他们都是一样的:如果实例变量是nil,则调用属性所属类的-alloc-init方法,并返回实例变量。这种做法非常普遍和简单直接。
如果我不自己创建该getter方法,Objective-C的自动合成将为我创建一个getter方法,它仅执行返回操作(如果实例变量为nil,不会初始化对象)。
有没有办法避免编写这些样板代码?

使用宏,我猜。 - qwerty_so
3个回答

2

抱歉,如果您真的想要延迟初始化,我恐怕没有好的解决方法。就我个人而言,我通常会将延迟初始化用于那些可能非常耗时或占用大量内存的内容(例如加载图像或视图控制器),并在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()

我也喜欢知道我的属性在哪里初始化,而不必搜索它们。 - gklka

2
首先,我完全赞同@zpasternack的观点,“懒加载”不应该被滥用。然而,利用Objective-C运行时的强大功能,自动生成setter和getter是完全可行的。事实上,CoreData正在这样做。
无论如何,我想出了一些愚蠢的代码来实现一个名为LazyClass的类,其中可以声明动态属性,如lazyArray(见下文)。使用动态方法解析,当首次访问属性时,将自动向类中添加调用相应类的默认+alloc-init方法的getter。所有底层实例变量都存储在名为myVarsNSMutableDictionary中。当然,您也可以通过运行时API操作ivars,但使用字典应该可以节省一些工作。
请注意,此实现仅展示了其工作原理的基本思想。它缺乏错误检查,并且不应该被发布。 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

文档

这是关于动态方法解析的官方文档链接。

1

如果您觉得代码冗长,可以使用三元运算符来压缩只需一行初始化的懒惰初始化器:

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

免责声明:我不会这样做,但有趣的是它可以被完成。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接