Hi,
I’ve noticed this strange behavior in Swift 4.1 (different than it was in Swift 4.0), but I don’t understand why is that happening and I would appreciate if someone could explain it to me, because I’m not sure if that’s a bug or a feature? :)
Playground example code:
import PlaygroundSupport
import UIKit
PlaygroundPage.current.needsIndefiniteExecution = true
public protocol Item {
var id: String { get }
}
public protocol Section {
var items: [Item] { get set }
}
public protocol ViewModel {
var sections: [Section] { get set }
}
public struct BasicItem: Item {
public let id: String
}
public struct BasicSection: Section {
public var items: [Item]
}
public struct BasicViewModel: ViewModel {
public var sections: [Section]
init(sections: [Section]) {
self.sections = sections
}
init() {
sections = [BasicSection(items: [
BasicItem(id: "Item1"),
BasicItem(id: "Item2"),
BasicItem(id: "Item3")
])
]
}
}
open class TableViewController: UITableViewController {
open var viewModel: ViewModel! {
didSet {
print("didSet")
}
}
open override func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.sections.count
}
open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.sections[section].items.count
}
open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}
class CustomTVC: TableViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad")
viewModel = BasicViewModel()
}
}
let tvc = CustomTVC(style: .plain)
PlaygroundPage.current.liveView = tvc
My observations:
As you can see here, there are a few simple protocols and structs conforming to those, which are later used in a UITableViewController
subclass.
If I run this code with Swift 4.0, print("didSet")
will be called only once, while in Swift 4.1 it will be called multiple times, although I obviously set this property only once in viewDidLoad
.
With a breakpoint in this line I could see that didSet
is called once from viewDidLoad
(expected) and several times from tableView(_:numberOfRowsInSection:)
which was internally calling materializeForSet
and viewModel.setter
(unexpected).
Part which I don’t understand is why accessing a read only property on the viewModel
actually ends up mutating it and calling the setter under the hood?
This problem does not happen (didSet
is called only once) if any of these is true:
-
ViewModel
protocol is defined asViewModel: class { ... }
so no structs can be used for it -
ViewModel
andSection
protocols define their properties like{ get }
instead of{ get set }
- using just a simple structs with the same properties (without protocols)
I’ve tried to find some connection in the release notes for Swift 4.1 but unfortunately, I couldn’t relate this problem to anything I saw there.