Swift Protocols and the Law of Unintended Consequences
I'm currently trying to improve my understanding of best practices in POP. One of my starting points was this article by Natasha The Robot. The example of a shakeable view was easy to follow, but I wanted play with it a bit to make it a bit more configurable.
Following Natasha's post would mean I would end up with something like this
but I wanted something that might be more configurable, so I could control whether the shake was horizontal or vertical. Like this
but instead, I got this.
Wait, what? Hang on. No, nononono. This is not right at all, the views are shaking at completely different speeds. How did I end up messing up a really simple example?
Over-egging my pudding
I had modified the original Shakeable protocol in Natasha's blog to add properties that would make the protocol configurable
The plan was to allow the image and button views to optionally provide the configuration properties as IBInspectables in Interface Builder, giving us the ability to compose the views via a protocol but without tying them to hard coded values when executing.
My shake animation implementation is no different to the original, except I've used the properties instead of hard coded values. But other than that there is nothing that would suddenly make the image shake animation suddenly slow down...
...or is there? Why was the image view animating slowly, but the button was animating fine? There's nothing else for it, things are about to get hardcore. I need.... a print statement!
I guessed the issue was the animation duration, so I printed that out in the shake implementation, and a strange thing happened. The button animation duration was as expected, but the image view's animation duration was zero.
At this point, if you have plenty of experience with UIKit, specifically the UIImageView, you know what I did wrong. But if not, then here's the offending line of code
Yup, just that. What I'd forgotten when defining the protocol's properties is that UIImageView already has a property called animationDuration.
Be careful what you wish for
I got a little bit angry, with myself and with the compiler. Why didn't it warn me that I was shadowing another property? Couldn't it see the collision?
Well, no. Of course not. There is no shadowing or collision. I'm thinking in OOP. The animationDuration property in UIImageView isn't being overridden by anything in the protocol's default extension, the animationDuration property in UIImageView is conforming to the protocol, which is entirely unintentional in this case. As a result, the default property in the extension was not being used, the value in UIImageView was.
The takeaway is this: if you're creating protocols you need to think carefully about the names you use. You are not going to know the names of every property or method of every class that might be asked to conform to your protocol, so this type of unintentional conformance is a very real threat.
So what to do? Well, dilemma time. On one hand you can just give it a different name, which is what I did to fix the problem. But this only fixes the problem for my custom image and button view classes, what's to say this new name isn't going to have the same problem if I make another subclass of UIView conform to the protocol?
What you should really do is give it a much more descriptive name, but that's likely to fly in the face of the new Swift 3.0 naming conventions, where everything is expected to be as concise as possible. Unfortunately, "concise as possible" isn't going to be very possible in your protocols, so you're just going to have to suck it up.