3
class Foo {
    let result: CGFloat

    init() {
        result = calculate()
    }

    private func calculate() -> CGFloat {
        // do many complex calculation with many codes
        return 0
    }
}

There is no doubt that the error occurs.

'self' used in method call 'calculate' before all stored properties are initialized`

I know several ways to solve this problem.

  1. var instead of let. e.g. var result
  2. lazy. e.g. lazy result: CGFloat = { return 0 }
  3. make calculate() to be class/static or global function. e.g static func calculate().

But I think that's not what I want.

Why let

let means immutable. Though the calculation of result may be complex, but it's truly immutable. So var is not the best practice.

Why calculate()

Too many codes in init() is not idiomatic and annoying

Why not class/static

Other instance properties can not be used in static function.


Update

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    let result: CGFloat

    init() {
        foo3 = foo1 * foo2
        result = calculate()
    }

    private func calculate() -> CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }
}

For more clear, I add another snippet. In fact result is the same with foo3. So why foo3 = foo1 * foo2, but result = calculate()?

That's because the calculation of result(maybe 10 lines codes) is a little more complex than the calculation of foo3(just one lines). If I put all these codes in init(), it will be messy.


Try all ways mentioned

1. var instead of let

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    var result: CGFloat

    init() {
        foo3 = foo1 * foo2
        result = calculate()
    }

    private func calculate() -> CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }
}

It works, but result is not immutable.

2. lazy

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    lazy var result: CGFloat = {
        calculate()
    }()

    init() {
        foo3 = foo1 * foo2
    }

    private func calculate() -> CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }
}

It works, but result is also not immutable.

3. static function

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    let result: CGFloat

    init() {
        foo3 = foo1 * foo2
        result = Foo.calculate()
    }

    static private func calculate() -> CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }
}

Build fails,

Instance member 'foo3' cannot be used on type 'Foo'

4. closure

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    let result: CGFloat
    private let calculate = { () -> CGFloat in
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }

    init() {
        foo3 = foo1 * foo2
        result = calculate()
    }
}

Build fails,

Instance member 'foo3' cannot be used on type 'Foo'

5. computing property

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    var result: CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }

    init() {
        foo3 = foo1 * foo2
    }
}

It works, but result is 'var' and will be calculated for each usage.

6. tool class

class Foo {
    let foo1: CGFloat = 1.0
    let foo2: CGFloat = 2.0
    let foo3: CGFloat
    let result: CGFloat

    init() {
        foo3 = foo1 * foo2
        result = Tool.calculate(foo3: foo3)
    }
}

class Tool {
    static func calculate(foo3: CGFloat) -> CGFloat {
        // do some calculation
        let constants: CGFloat = 100
        // (10 lines...)
        return foo3 + constants
    }
}

It works, but we bring in a Tool class.

Summary

1, 2, 6 is appropriate. 6 is suit for complex calculation.

Extension

"Implicitly unwrapped optionals" is mentioned several time. I confused why until see this answer.

Liam
  • 172
  • 1
  • 10
  • Related: [Swift initialize non optional property before super init call without duplicating code](https://stackoverflow.com/q/49839059) – jscs Feb 20 '19 at 02:35
  • 1
    It sounds like this class is doing too much. In most cases, I find that complex algorithms don't belong in the same class as the containers that will store the results. – Alexander Feb 20 '19 at 03:09
  • @Alexander It's not that complex. In fact there are just several other instance properties in `calculate`. e.g. `let foo1 = 1`, `let foo2 = 2`, `func calculate() { return foo1 + foo2 }` – Liam Feb 20 '19 at 03:16
  • @Liam why is lazy not an option for you? You mention it but don’t say why you think it’s not good? – allenh Feb 20 '19 at 15:02
  • @Liam Even still, the "Parameter object" design pattern seems more appropriate here – Alexander Feb 20 '19 at 16:07
  • @Alexander Yes, move calculation to another class can solve this problem. Now I think it's impossible to finish all these task in a class. – Liam Feb 21 '19 at 01:57
  • @allenh Good point. I also think lazy is a good expedient, but not perfect. Because lazy is var, but in fact the result is truly immutable. I want to immutable in codes. – Liam Feb 21 '19 at 01:59
  • @Liam There are several ways to "make it work", lazy vars, implicitly unwrapped optioanls, etc. They sound bad, but in reality, most languages (Python, Ruby, Java, C# up until recently) work as if all their references are implicitly unwrapped optionals. The Swift type system helps write safer code, most of the time, but it's not fully expressive to cover all use cases, such as your own. Implicitly unwrapped optionals offer an "escape hatch" to get you back into every-other-lanugage-land, where you can express what you're going after – Alexander Feb 21 '19 at 02:18
  • @Liam That said, I think this is a good sign that you should refactor, and make this method part of another class, and either pass the variables in, or define a "FooAlgInputData" struct to wrap them in (so you can pass them around as one complete "package"). – Alexander Feb 21 '19 at 02:19
  • @Alexander You point in. it's unnecessary to make codes absolutely right, but suitable. – Liam Feb 21 '19 at 03:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/188779/discussion-between-alexander-and-liam). – Alexander Feb 21 '19 at 03:16
  • I would probably do `lazy private(set) var result: CGFloat` to achieve the immutability from the public-facing API. Possibly with a `_ = result` in the initializer to control when the calculations are performed. – allenh Feb 21 '19 at 03:18
  • @allenh That's efficient and keep codes clear except `result` is immutable. I think it's a good solution. – Liam Feb 21 '19 at 03:37

1 Answers1

2

The other two ways you haven't mentioned are turning result into one of

  • an implicitly unwrapped optional
  • a computed property

A third is, move the calculation to its own type/free function and provide it to this class. Which is probably the right solution.

Swift simply won't allow you to do what you're trying to do: the rule is that all stored properties must be set before you do anything with the new instance's other properties. It takes the hard view that anything it can't prove to be correct must be disallowed.

For example, what if you changed calculate to read from result? Or what if a subclass did so? You would have a situation where the value is not just undefined but undefinable.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • Yes, but the two ways below also don't match the case. optional is not required and the computed property will make the `calculate` many times which is also not that good. – Liam Feb 20 '19 at 02:58
  • About the third one, can you provide more detail? I'm not clear about *"move the calculation to its own type/free function and provide it to this class"*. – Liam Feb 20 '19 at 03:00
  • I don't know where the class's other property values are coming from, but basically you add parameters to `Foo`'s initializer and calculate the value of `result` before creating your `Foo`, then pass all the values in, instead of `Foo` doing the calculation. – jscs Feb 20 '19 at 03:10
  • I update the question, I think it will be more clear now. – Liam Feb 20 '19 at 03:31