If the concern was merely how to implement the protocol's properties, I wouldn't necessarily let that sway my choice between struct
vs class
. If you had a variety of properties that your struct
types must implement, you have two basic options:
If you're talking about a few properties, just implement those few properties in your struct
types that conform to that protocol. We do this all the time. E.g. when defining custom types that conform to MKAnnotation
, we simply implement the three required properties.
Sure, if we're talking about a much larger set of properties, this gets tedious, but the compiler holds our hand through this process, ensuring that we don't miss anything. So the challenge is fairly modest.
While I'm not a fan of this approach, https://stackoverflow.com/a/38885813/1271826 shows that you could implement the shared properties as a component, where you have struct
to wrap all of these properties, and then implement default computed properties for your protocol in an extension:
enum SubviewArrangement {
case none
case horizontal
case vertical
case flow
}
struct ViewComponent {
var frame = CGRect.zero
var weight = 1
var subviews = [ViewModel]()
var subviewArrangement = SubviewArrangement.none
}
protocol HasViewComponent {
var viewComponent: ViewComponent { get set }
}
protocol ViewModel: HasViewComponent { }
extension ViewModel {
var frame: CGRect {
get { return viewComponent.frame }
set { viewComponent.frame = newValue }
}
var weight: Int {
get { return viewComponent.weight }
set { viewComponent.weight = newValue }
}
var subviews: [ViewModel] {
get { return viewComponent.subviews }
set { viewComponent.subviews = newValue }
}
var subviewArrangement: SubviewArrangement {
get { return viewComponent.subviewArrangement }
set { viewComponent.subviewArrangement = newValue }
}
}
Where, you can then create an instance that conforms to ViewModel
, like so:
struct LabelModel: ViewModel {
var viewComponent = ViewComponent()
}
var label = LabelModel()
label.weight = 2
print(label.weight)
I have to confess, this isn't the most elegant approach. (I hesitate to even present it.) But it avoids having to implement all of those properties individually in your types that conform to ViewModel
.
So, let's set the property question aside. The real question is whether you should be using value type (struct
) or reference type (class
). I think it's illuminating to consider Apple's discussion of value vs reference semantics near the end (@42:15) the Protocol-Oriented Programming in Swift video. They touch upon those cases where you actually may still want to use classes. For example, they suggest you might want to use reference types when, "Copying or comparing instances doesn't make sense". They suggest this rule might apply when dealing with "Window" instances. The same applies here.
On top of that, it doesn't seem to me that there is much benefit to use value types to represent a view hierarchy, which is a collection of reference type objects. It only makes it more confusing. I would just stick with class
types, as it will accurately mirror the view hierarchy it represents.
Don't get me wrong: We're so used to using reference types that I think it's always good to challenge our preconceived notions and take a long hard look at whether a value type could better address the situation. In this case, though, I simply wouldn't worry about it and just stick with a class
hierarchy that mirrors the hierarchy of those objects you're modeling.
That having been said, the class hierarchy proposed in your question doesn't quite feel right, either. It feels strange that you can actually instantiate a ViewModel
to which you can't later add subviews (whereas all UIView
objects have subview
property). Also, your horizontal and vertical group types don't feel correct either. For example, should it be a single type with some "axis" property, like UIStackView
or some other "arrangement" property, to broaden the notion to capture UICollectionView
layouts, too?. As you'll see in my ViewComponent
example, above, I've flattened this a bit, with these two caveats in mind, but do whatever you see fit.