简体   繁体   中英

Pass self as argument within init method in Swift 1.2

The following class has a ' let ' property declared as implicitly unwrapped variable. This previously worked with Xcode 6.2:

class SubView: UIView {

    let pandGestureRecognizer: UIPanGestureRecognizer!

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.pandGestureRecognizer = UIPanGestureRecognizer(target: self, action: "panAction:")
    }

    func panAction(gesture: UIPanGestureRecognizer) {
        // ...
    }
}

After updating to Xcode 6.3 (with Swift 1.2), the following compilation errors occur:

Property 'self.panGestureRecognizer' not initialized at super.init call
Immutable value 'self.panGestureRecognizer' may only be initialized once

Moving the following line before the super.init call:

self.pandGestureRecognizer = UIPanGestureRecognizer(target: self, action: "panAction:")

gives the following error:

'self' is used before super.init call

The property ' panGestureRecognizer ' requires no mutation, therefore it has to be declared as constant ' let '. Since it is a constant, it has to have an initial value upon declaration, or initialize it within the init method. To initialize it, it requires to pass ' self ' in the ' target ' parameter.

Other thread suggested to declare it as implicitly unwrapped optional, and initialize it after the super.init call. This previously worked until I updated to Xcode 6.3.

Does anybody know a proper implementation or a workaround for this case?

The Problem

The problem is your use of let - optionals declared as let aren't given a default value of nil ( var is however). The following, introduced in Swift 1.2, wouldn't be valid otherwise since you wouldn't be able to give myOptional a value after declaring it:

let myOptional: Int?

if myCondition {
    myOptional = 1
} else {
    myOptional = nil
}

Therefore, you're getting the error 'Property 'self.panGestureRecognizer' not initialized at super.init call' because before calling super.init(coder: aDecoder) , because panGestureRecognizer isn't nil ; it hasn't been initialised at all.

The Solutions:

1. Declare panGestureRecognizer as a var , meaning it will be given a default value of nil , which you could then change after calling super.init(coder: aDecoder) .

2. In my opinion, the better solution: don't use an implicitly unwrapped optional and declare panGestureRecognizer with an initial value of UIPanGestureRecognizer() . Then set the target after super.init is called:

class SubView: UIView {
    let panGestureRecognizer = UIPanGestureRecognizer()

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        panGestureRecognizer.addTarget(self, action: Selector("panAction:"))
    }
}

You can't use self unless the class is initialized. And if you would like to use self for property initialization, it must be lazy . But lazy is not supported for let , just var .

That's because:

You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.

It's kind of compromise and if you can live with private setter, you can do this:

class SubView: UIView {

  private(set) lazy var panGestureRecognizer: UIPanGestureRecognizer = { [unowned self] in UIPanGestureRecognizer(target: self, action: "panAction:") }()

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  func panAction(gesture: UIPanGestureRecognizer) {
  }
}

Or initialize panGestureRecognizer with just UIPanGestureRecognizer() and add target later.

A workaround for this specific case would be:

class SubView: UIView {

let pandGestureRecognizer: UIPanGestureRecognizer

required init(coder aDecoder: NSCoder) {
    self.pandGestureRecognizer = UIPanGestureRecognizer()
    super.init(coder: aDecoder)
    self.pandGestureRecognizer.addTarget(self, action: "panAction:")
}

func panAction(gesture: UIPanGestureRecognizer) {
    // ...
}

}

If you want to pass self to initializer of an object, you should declare your object as lazy. Because when this object is initialized, self is not ready yet.

lazy var viewModel = IntroViewModel(controller: self)

class IntroViewModel {

    private weak var controller: IntroViewController?

    init(controller: IntroViewController?) {
        self.controller = controller
    }
}

I had this problem for a different reason, it had nothing to do with Optionals or lazy. Just literally that the person object had to be initialized once.

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Account {
    static let shared = Account(person: Person(name: "Bobby")) // <-- person initialized once
    let person: Person = Person(name: "Lio") // initialized again!

    init(person: Person) {
        self.person = person
    }
}

It's quite interesting that Swift can catch this error

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM