That is quite specific circumstances I've come across, and I somewhat struggle to find proper way how to approach this.
I'm given a class written in swift-language, which has a control property, like this (I generalized some names to avoid reference to actual product):
class FooViewController: UIViewController {
...
var controlView: FooControlViewProtocol?
...
}
FooControlViewProtocol
is a quite simple protocol, which has both directive methods and event-based methods, this is kind of communication interface between a UIViewController
instance and a so-called controlView
which is essentially a UIView
instance conforming to the protocol.
protocol FooControlViewProtocol {
func doFirstMethod();
func doSecondMethod();
func eventDidStart(_ event: Event);
func eventDidFinish(_ event: Event);
}
And now here is the complicated part: these parts are rather concrete, and they come from a framework another team is working on, and we should not change that at all cost, because some other parts of the app (and even other projects) also rely on this. However we need to customize it so it meets the needs of our project. Mostly we didn't even bother at all. That couple covered most of the requirements we had. However at some point we had to implement a custom scrolling behavior and inform this control view of current scrolling position, but for only one of dozens ViewControllers in the app. I ended up extending the protocol like this:
protocol ScrollableFooControlViewProtocol: FooControlViewProtocol {
func scrollDidEnd(_ offset: CGFloat)
}
And then, in a FooViewController subclass the property is set like this:
class ScrollableFooViewController: FooViewController {
var scrollableControlView: ScrollableFooControlViewProtocol?
override var controlView: FooControlViewProtocol? {
set {
scrollableControlView = newValue as? ScrollableFooControlViewProtocol
}
get {
scrollableControlView
}
}
}
The apparent problem is that if a new controlView
instance does not conform to ScrollableFooControlViewProtocol
, the application just ignore it silently and keeps working AND I find this mistake extremely likely to happen, because existing code use given abstractions heavily (FooViewController
and FooControlViewProtocol
). I cannot throw an exception from the property and it's somewhat too complicated to switch the property to a method (because of abstraction). One option I've put into consideration is force unwrapping newValue
cast like this:
set {
if newValue = newValue {
scrollableControlView = newValue as! ScrollableFooControlViewProtocol
} else {
scrollableControlView = nil
}
}
And the application can just crash in case of mistake, revealing the problem immediately. However this approach is far from perfect - the feature this protocol adds is not essential for application lifecycle and it can work seemingly flawless without it. So making it crash because of it is kind of overacting.
Integration test might be an answer here, where i can check these two components interaction, however i doubt this will pay off for only such a tiny case.
So the question is how to make this code safe? Probably I should have implemented it somehow differently in the first place.