获取声明为协议的 属性 时调用 didSet

didSet called when getting a property declared as a protocol

这是一些代码:

import UIKit

protocol ViewModelProtocol {
    var price: String { get }
}

class ViewModel: ViewModelProtocol {
    var price: String {
        return "0"
    }
}

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.
    }
}

此代码将运行 无限循环。具体来说,它在 updateDisplay()print("\(viewModel?.price)")viewModeldidSet 之间反弹。

如果直接将viewModel的类型改为ViewModel(跳过协议),死循环就会消失。或者,如果在使用 updateDisplay() 之前将 viewModel 展开,无限循环也会消失。

这是在 Swift 2 中,虽然我还没有验证它是否在早期的 Swift 中具有相同的行为。另一个数据点,调用协议上的方法不会导致调用 didSet.

您觉得这像是 Swift 错误吗?

这是一个非常令人印象深刻的案例。

您的 willSet 属性可能有问题,您的协议类型内部有一个 readonly 属性。我做了很多测试,很难找到解决方案。但是,如果你真的需要继续这样下去......我认为以下变化可以帮助你

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

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

        set {
            return 
        }
    }
    //...
}

如您所说...只有在尝试通过 viewModel 对象直接访问价格属性时才会出现此问题

我只是把我的答案放在这里,因为它不适合评论字段的大小。

但是非常令人印象深刻...我将尝试找到最终解决方案。 :)

好像不止我一个didSet{}有这个问题。 有针对此问题的公开雷达:https://openradar.appspot.com/22574299