2

I have a question in regards to how I would do a lazy initialization of a class variable that holds a function. In the project I am working on - in a view controller - I need to run a function based on information that I will only know after the view controller is created. Hence, I wanted to use a lazy initialization to solve this problem. I suppose I could solve the problem other ways, but now I am curious about what I am not understanding about lazy initialization that I am have such difficulty in figuring out how to get a lazily initialized variable to hold a function. That is, if it can be done at all.

Here is some example code of what I am trying to do. I want to be able to call talk() on an instance of TestClass and then this instance (tc in this case) call f() which is either foo or bar depending on circumstances.

class TestClass {

    func foo() {
        println("foo")
    }

    func bar() {
        println("bar")
    }

    lazy var f: ()->() = {
        return foo
    }()

    func talk() {
        f()
    }
}

var tc = TestClass()
tc.f()

Now, this doesn't compile and I get the error:

Playground execution failed: swiftTesting.playground:28:30: error: 'TestClass -> () -> ()' is not convertible to '() -> ()'
    lazy var f: ()->() = {

Then I tried changing my f lazy var to:

    lazy var f: TestClass->()->() = {
        return foo
    }()

This again did not work and now I get the error, Missing argument for parameter #1 in call.

Can anyone shed some light on how to do lazy initialization with a variable containing a function? I know that I could work around this by not using lazy initialization but I am hoping that someone can help me understand what I am doing wrong in this situation. Thanks.

2 Answers 2

3

foo is an instance method, but closures avoid implicit capture of self by not automatically binding to it. You need to explicitly say self.foo.

lazy var f: ()->() = {
    return self.foo
}()
tc.f()
// tc is leaked, see below

When you instead used TestClass->()->(), the method is not bound to anything, so you need to give it an instance.

lazy var f: TestClass->()->() = {
    return foo
}()
tc.f(tc)()  // Call f with tc to give it an instance, then call the result

The first choice looks ok, but it will actually prevent tc from being deallocated. Binding foo to self and storing it inside self causes a reference cycle. To avoid this, you need to use a closure with weak capture of self.

lazy var f: ()->() = {
    [weak self] in
    self!.foo()  // ! because self is optional due to being weak
}  // no () here
tc.f()
// tc can be deallocated
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the great response. I did not realize the reference cycle I was creating in this situation. Just one thing you might want to update on your answer, it should be return self!.foo instead on the last piece of code. Is it also possible you could explain why you need the exclamation point when you use [weak self] in and you don't have to when you don't? Thanks again.
2

You need to explictly state self:

lazy var f: ()->() = {
    return self.foo
}()

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.