3

In Swift, lazy properties allow us to only initialise a class member when we ask for it instead of directly at runtime - useful for computationally expensive operations.

I have a class in Swift 4 that is responsible for initialising a strategy from an array of compile-time (developer-hardcoded) provided StrategyProtocol objects. It looks something like this:

class StrategyFactory {
    private var availableStrategies: [StrategyProtocol] = [
        OneClassThatImplementsStrategyProtocol(),
        AnotherThatImplementsStrategyProtocol() // etc
    ]

    public func createStrategy(): StrategyProtocol {
        // Depending on some runtime-calculated operation
        // How do I do this nicely here?
    }
}

However, from my understanding, placing () at the end of each strategy initialises the objects(?), when I may only want to create one depending on certain runtime conditions.

Either way, is it possible to place lazy somewhere around the values in an Array class member to only instantiate the one I want when I ask for it? Or would I have to go about this with closures or some other alternative?


Current attempt

Is this doing what I think it is? Until I get the first element of the array and execute it, it won't actually instantiate the strategy?

private var availableStrategies: [() -> (StrategyProtocol)] = [
    { OneClassThatImplementsStrategyProtocol() }
]
Community
  • 1
  • 1
Jimbo
  • 25,790
  • 15
  • 86
  • 131

2 Answers2

2

Your "Current attempt" does what you think it does. You have an array of closures, and the strategy is initialized only when the closure is executed.

A possible alternative: Store an array of types instead of instances or closures (as Zalman Stern also suggested).

In order to create instances on demand, a init() requirement has to be added to the protocol (which must then be satisfied by a required init() unless the class is final, compare Why use required Initializers in Swift classes?).

A possible advantage is that you can query static properties in order to find a suitable strategy.

Here is a small self-contained example, where createStrategy() creates and returns the first "fantastic" strategy:

protocol StrategyProtocol {
    init()
    static var isFantastic: Bool { get }
}

class OneClassThatImplementsStrategyProtocol : StrategyProtocol {
    required init() { }
    static var isFantastic: Bool { return false }
}

final class AnotherThatImplementsStrategyProtocol : StrategyProtocol {
    init() { }
    static var isFantastic: Bool { return true }
}

class StrategyFactory {
    private var availableStrategies: [StrategyProtocol.Type] = [
        OneClassThatImplementsStrategyProtocol.self,
        AnotherThatImplementsStrategyProtocol.self // etc
    ]

    public func createStrategy() -> StrategyProtocol? {
        for strategy in availableStrategies {
            if strategy.isFantastic {
                return strategy.init()
            }
        }
        return nil
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • _"does what you think it does"_ It may not be clear to the OP that the array elements aren't _replaced_ with the results of the closure, which is what it sounds to me like he's expecting when he talks about lazy properties. – jscs Dec 09 '17 at 20:27
  • @JoshCaswell: That was not my impression (from *"Until I get the first element of the array and execute it, it won't actually instantiate the strategy?"*) – perhaps OP can comment on this, and I'll clarify if necessary. – Martin R Dec 09 '17 at 20:31
  • I wasn't expecting the results of the closure to replace the closure. Thanks for the answer! I ended up going the closure route as I wasn't keen on adding `inits` anywhere that was not a direct requirement for my API choice. However I played with the `Type`s and it seems like a good way of doing it, so I'll mark this as correct. – Jimbo Dec 10 '17 at 15:49
0

ANYCLASS, META TYPE AND .SELF may answer your question. (I am not expert on Swift, but use of metaclasses is likely what you want and Swift, as I expected, appears to support them.) You can look through this Stack Overflow search.

EDIT: In case it wasn't clear, the idea is to have the array of strategies contain the metaclasses for the protocols rather than instantiations. Though this depends on whether you want a new strategy object for each instantiation of the class with the lazy property or whether strategies are effectively global and cached ones created. If the latter, then the lazy array approach for holding them might work better.

Zalman Stern
  • 3,161
  • 12
  • 18