Lazy property initialization in 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.

  • optional chaining in Swift 3: why does one example work and not the other?
  • Delay while loading locally stored file on webView
  • Mutex alternatives in swift
  • iOS blured overlay view
  • Multiple functions with the same name
  • How to swizzle init in Swift
  • @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 Solutions Collect From Internet About “Lazy property initialization in Swift”

    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")
    

    What about this:

    class Container {
    
        lazy var entries: [String] = self.newEntries()
    
        func newEntries() -> [String] {
    
            // calculate and return entries
    
        }
    
    }
    

    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!
            }
        }
    }
    

    The way lazy works is that the initializer (or init method) runs only when the variable or property is first accessed. I see one main reason why it won’t work (at least straight away) in your code, and that is because you packed two lazy instantiation code into one method (loadEntriesIfNeeded).

    To use lazy instantiation, you might need to extend NSMutableArray and NSDictionary and override or create a custom initializer for your lazy instantiation. Then, distribute the code inside loadEntriesIfNeeded into their respective initializers.

    In entries initializer:

    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];
    

    Then in entriesByNumber initializer:

    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];
    

    Then, you can create your lazy variables by calling on the custom initializers.

    @lazy var entries: CustomArray = custominitforarray()
    @lazy var entriesByNumber: CustomDictionary = custominitfordictionary()
    

    PS: How come you don’t have a property for entriesByNumber? I’m guessing it’s private? Please test this out and reply about the result as I’m too lazy to do it myself.

    You can use Lazy Stored Properties in Swift to implement the Lazy Instantiation pattern. This is done by adding the @lazy attribute before the declaration of the stored property.

    There are two things to keep in mind:

    • Lazy properties must be initialized when declared
    • Lazy properties can only be used on members of a struct or a class (hence why we need to use a DataManager)

    Here’s some code you can throw into a Playground to see how the @lazy attribute works

    // 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
    

    For more information, you can check out the Lazy Stored Properties section on the Properties page of the Swift Programming Language Guide. Note that that link is to pre-release documentation.

    You indicate a lazy stored property by writing the @lazy attribute before its declaration.”

    @lazy var lazyVariable:String? = ""
    

    Please bear in mind, lazy property must have an initialiser.

    There is a @lazy attribute in Swift. I found a small post here and I recommend watching the three Swift video from Apple here (Introduction to Swift, Intermediate Swift, Advanced Swift). These videos show a lot of stuff and the advanced one really is advanced…

    I found this in 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
    

    similar to what @Brian Tracy mentioned but ussing variables instead of a func

    Since the entries property is just an array of the values in entriesByNumber you can do all the loading just in entriesByNumber and just have entries depend on 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 }
    }