leftspin
leftspin

Reputation: 2488

didSet called when getting a property declared as a protocol

Here's some code:

import UIKit

protocol ViewModelProtocol {
    var price: String { get }
}

class ViewModel: ViewModelProtocol {
    var price: String {
        return "$420"
    }
}

class ViewController: UIViewController {

    // If you change the type to ViewModel directly, no infinite loop
    var viewModel: ViewModelProtocol? = nil {
        didSet {
            print("viewModel didSet called")
            updateDisplay()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        viewModel = ViewModel()
        super.init(coder: aDecoder)
        updateDisplay()
    }

    func updateDisplay() {
        print("In updateDisplay()")
        print("\(viewModel?.price)")
        // if you access the viewModel like this, no infinite loop
        // if let v = viewModel {
        //     print("\(v.price)")
        // }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

This code will run in an infinite loop. Specifically, it bounces between print("\(viewModel?.price)") in updateDisplay() and the didSet for viewModel.

If you change viewModel's type to ViewModel directly (skipping the protocol), the infinite loop disappears. Alternatively, if you unwrap viewModel in updateDisplay() before using it the infinite loop also disappears.

This is in Swift 2, although I haven't verified if it has the same behavior in earlier Swift. One other data point, calling methods on the protocol doesn't cause a call to didSet.

Does this look like a Swift bug to you?

Upvotes: 4

Views: 1151

Answers (2)

dtd
dtd

Reputation: 127

It seems that not only I have this problem with didSet{}. There is open radar for this issue: https://openradar.appspot.com/22574299

Upvotes: 2

dede.exe
dede.exe

Reputation: 1310

It's a very impressive case.

It maybe a problem with your willSet attribute with your protocol type having a readonly attribute inside. I made a lots of tests and it's being very hard to find a solutions. But, if you really need to keep going in this way... I think the follow change can help you

protocol ViewModelProtocol {
    var price: String { get set }
}

class ViewModel: ViewModelProtocol {
    var price: String  {
        get {
            return "$420"
        }

        set {
            return 
        }
    }
    //...
}

As you said... The problem only happens when try to access directly the price attribute via viewModel object

I just put my answer here, cause it didn't fit in comment field size.

But is very impressive... I will try to find a final solution. :)

Upvotes: 2

Related Questions