Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy property initialization in Swift

Tags:

swift

How would you implement the following pattern in Swift?

The Container class is initialized with a JSON array that contains dictionaries. These dictionaries are used to initialize Entry classes. However, the initialization of the Entry objects happens lazily, when either the entries or the searchEntries property is accessed.

@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
like image 262
Florian Avatar asked Jun 08 '14 15:06

Florian


3 Answers

What about this:

class Container {

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

    func newEntries() -> [String] {

        // calculate and return entries

    }

}
like image 95
Rudolf Adamkovič Avatar answered Nov 15 '22 09:11

Rudolf Adamkovič


It seems that this question has largely been answered, but to circle back to the original post, here is (IMHO) a relatively succinct translation in Swift. The key is that you can chain lazy properties. Note that I used both a class function and a closure - either is fine.

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")
like image 27
Chris Conover Avatar answered Nov 15 '22 09:11

Chris Conover


You could use an optional as the instance variable. Then create a function that returns the optional if it exists, and a new object if it does not to simulate lazy loading.

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!
        }
    }
}
like image 35
Brian Tracy Avatar answered Nov 15 '22 07:11

Brian Tracy