Swift中的惰性属性初始化

6

如何在Swift中实现以下设计模式?

Container类是用包含字典的JSON数组初始化的。这些字典用于初始化Entry类。但是,Entry对象的初始化发生惰性加载,只有当访问entriessearchEntries属性时才会进行。

@interface Container

@property (readonly, nonatomic) NSArray *entryDicts;

@property (readonly, nonatomic) NSArray* entries;
@property (readonly, nonatomic) NSDictionary *searchEntries;

@end



@implementation Container

- (instancetype)initWithArray:(NSArray *)array
{
    self = [super init];
    if (self) {
        _entryDicts = array;
    }
    return self;
}

@synthesize entries = _entries;
- (NSArray *)entries
{
    [self loadEntriesIfNeeded];
    return _entries;
}

@synthesize entriesByNumber = _entriesByNumber;
- (NSDictionary *)entriesByNumber
{
    [self loadEntriesIfNeeded];
    return _entriesByNumber;
}

- (void)loadEntriesIfNeeded
{
    if (_entries == nil) {
        // Load entries
        NSMutableArray *entries = [NSMutableArray arrayWithCapacity:[self.entriesDict count]];
        NSMutableDictionary *entriesByNumber = [NSMutableDictionary dictionaryWithCapacity:[self.entriesDict count]];

        [self.entriesDict enumerateKeysAndObjectsUsingBlock:^(NSString *number, NSDictionary *entryDict, BOOL *stop) {
            Entry *entry = [[Entry alloc] initWithDictionary:entryDict container:self];
            [entries addObject:entry];
            entriesByNumber[number] = entry;
        }];

        _entries = [entries copy];
        _entriesByNumber = [entriesByNumber copy];

        // Delete dictionaries
        _entriesDict = nil;
    }
}

@end
9个回答

12

这个怎么样?

class Container {

    lazy var entries: [String] = self.newEntries()

    func newEntries() -> [String] {

        // calculate and return entries

    }

}

谢谢。在类名后面缺少开头的 {,以及函数中的返回值。 - Evgenii
@Evgeny 已修复缺失的“{”。谢谢! - Rudolf Adamkovič

8

看起来这个问题已经得到了很大程度的回答,但是回到原始帖子,这里(我的观点)是Swift中相对简洁的翻译。关键在于可以链接惰性属性。请注意,我既使用了类函数也使用了闭包——两者都可以。

import Swift

println("begin")

class ClassWithLazyProperties {

    lazy var entries:[String] = ClassWithLazyProperties.loadStuff()
    lazy var entriesByNumber:Dictionary<Int, String> = {

        var d = Dictionary<Int, String>()
        for i in 0..<self.entries.count {
            d[i] = self.entries[i]
        }
        return d
    }()

    private class func loadStuff() -> [String] {
        return ["Acai", "Apples", "Apricots", "Avocado", "Ackee", "Bananas", "Bilberries"]
    }

}

let c = ClassWithLazyProperties()
c.entriesByNumber
    // 0: "Acai", 1: "Apples", 2: "Apricots", 3: "Avocado", 4: "Ackee", 5: "Bananas", 6: "Bilberries"]


println("end")

5
你可以将可选项用作实例变量。然后创建一个函数,如果存在可选项,则返回可选项,如果不存在,则返回新对象以模拟延迟加载。
class Lazy {
    var lazyVariable:String?

    func lazilyGetEntries() -> String {
        if let possibleVariable = self.lazyVariable { // optional already exists
            return possibleVariable
        }
        else {                                        // optional does not exist, create it
            self.lazyVariable = String()
            return self.lazyVariable!
        }
    }
}

4
懒加载的工作方式是仅在首次访问变量或属性时运行初始化器(或init方法)。我看到它无法直接在您的代码中起作用的一个主要原因,那是因为您将两个懒加载实例化代码打包到一个方法 (loadEntriesIfNeeded) 中。
要使用懒加载,您可能需要扩展NSMutableArray和NSDictionary,并覆盖或创建用于您的懒加载的自定义初始化器。然后,将 loadEntriesIfNeeded 内部的代码分发到它们各自的初始化器中。
在 entries 初始化器中:
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:[self.entriesDict count]];
[self.entriesDict enumerateKeysAndObjectsUsingBlock:^(NSString *number, NSDictionary *entryDict, BOOL *stop) {
            Entry *entry = [[Entry alloc] initWithDictionary:entryDict container:self];
            [entries addObject:entry];}];
_entries = [entries copy];

接下来是entriesByNumber初始化器:

NSMutableDictionary *entriesByNumber = [NSMutableDictionary dictionaryWithCapacity:[self.entriesDict count]];
// Then do fast enumeration accessing self.entries to assign values to entriesByNumber.
// If self.entries is null, the lazy instantiation should kick in, calling the above code
// and populating the entries variable.
_entriesByNumber = [entriesByNumber copy];

然后,您可以通过调用自定义初始化程序来创建惰性变量。
@lazy var entries: CustomArray = custominitforarray()
@lazy var entriesByNumber: CustomDictionary = custominitfordictionary()

PS:为什么你没有一个entriesByNumber属性?我猜它是私有的?请测试一下并回复结果,因为我懒得自己去做。


在beta4版本中,它变得有些懒惰了,没有使用“@”符号。 - Ilya Belikin

2
您可以使用Swift中的Lazy Stored Properties来实现Lazy Instantiation模式。这是通过在存储属性声明之前添加@lazy属性来完成的。
需要注意以下两点:
- 必须在声明时初始化惰性属性。 - 惰性属性只能用于结构体或类的成员(因此我们需要使用DataManager)。
下面是一些代码,您可以将其放入Playground中以查看@lazy属性的工作原理。
// initialize your lazily instantiated data
func initLazyData() -> String[] {
    return ["lazy data"]
}

// a class to manage the lazy data (along with any other data you want)
class DataManager {
    @lazy var lazyData = initLazyData()

    var otherData = "Other data"
}

// when we create this object, the "lazy data" array is not initialized
let manager = DataManager()

// even if we access another property, the "lazy data" array stays nil
manager.otherData += ", more data"
manager

// as soon as we access the "lazy data" array, it gets created
manager.lazyData
manager

更多信息请查看Swift编程语言指南的Properties页面上的懒存储属性部分。请注意,该链接是预发布文档。


0

在Swift中有一个@lazy属性。我在这里找到了一篇小文章here,我建议观看苹果公司的三个Swift视频here(Swift介绍,中级Swift,高级Swift)。这些视频展示了很多东西,而高级视频确实是高级的...


我已经看了所有的视频。不幸的是,@lazy在这里不起作用。请查看我的代码。 - Florian
很遗憾,@Florian,我的开发者计划还没有激活,所以我无法运行代码 :( - Kametrixom

0

由于entries属性只是entriesByNumber中值的数组,因此您可以在entriesByNumber中完成所有加载工作,并且只需使entries依赖于entriesByNumber

lazy var entriesByNumber: [String : Entry] = {
        var ret: [String : Entry] = [:] 
        for (number, entryDict) in entriesDict
        {
            ret[number] = Entry(dictionary: entryDict, container: self)
        }
        return ret
    }

var entries: [Entry] 
{
    get { return self.entriesByNumber.values }
}

0

在声明之前写上@lazy属性,就可以表示一个懒惰的存储属性。

@lazy var lazyVariable:String? = ""

请记住,惰性属性必须有一个初始化器。

0
我在PageBaseApplication中找到了这个。
var modelController: ModelController {
    // Return the model controller object, creating it if necessary.
    // In more complex implementations, the model controller may be passed to the view controller.
    if !_modelController {
        _modelController = ModelController()
    }
    return _modelController!
}

var _modelController: ModelController? = nil

类似于@Brian Tracy提到的,但使用变量而不是函数。

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