0xDEADBEEF

Home Projects AboutRSS

🔈 Swift Talk: Limiting the use of Protocols

  • swift
  • talk

TL;DR Practice like it’s real. So when it’s real, it’s just like practice

Product first

Swift니까 무조건 프로토콜을 써야 할까요? 🤔 프로토콜을 제대로 사용하는 방법을 알려드립니다!

Realm Korea에 의해 게시 됨 2017년 7월 24일 월요일

Credits to these awesome photos go to @gbmksquare

The Talk

The topic of the talk was…

Using protocols everywhere in Swift is not a good thing.

Protocols?

To see where I am trying to go with this, we first need to know what a protocol is. There are tons of tutorials online about protocols so I will keep it simple.

Object Oriented ✅ Protocol Oriented

Basically, protocols allow you to go from the left to the right and that’s considered a good thing.

Flatter architecture made possible with the concept of composition is easier to deal with than a complex class hiearchy.

What’s so bad then?

Let’s look at some examples.

protocol URLStringConvertible {
    var urlString: String { get }
}

// ...

func sendRequest(urlString: URLStringConvertible, method: () -> ()) {
    let string = urlString.urlString
}

This URLStringConvertible is not accomplishing anything here and can simply be replaced by a value.

But somehow, I think a lot people feel as if using protocols for everything is the right thing to do in Swift. Maybe this is because of the large number of “Protocol Oriented X” tutorials on the web or maybe because Apple has been trying to sell Swift as a “Protocol Oriented Language”. But whatever the reason,

Using protocols without thinking about the consequences is NOT ideal

More Examples

Suppose we are making a library that is good at creating UIView elements with data inserted in them. I’m going to completely ignore what I just said above and make a protocol first because that’s the “cool thing to do”.

Protocol Oriented Approach

protocol HeaderViewProtocol {
    associatedtype Content
    func setHeader(data: Content)
}

That looks like a cool protocol that will make us look like we know what we are doing. Let’s now apply this protocol to various UIView subclassess to build up our library.

class MyLabel: UILabel, HeaderViewProtocol {
    func setHeader(data: String) {
        self.text = data
    }
}

class MyButton: UIButton, HeaderViewProtocol {
    func setHeader(data: String) {
        self.titleLabel?.text = data
    }
}

Simple enough. I just successfully abstracted the idea of a class that can be used as a HeaderView with a single protocol. Now, I want to make an array of HeaderViewProtocol elements so that I can later insert them into a UICollectionView.

let elements = [MyLabel(), MyButton(), UIStackView()]

Wait.. UIStackView isn’t a HeaderViewProtocol but why does the compiler not raise any errors? If you look at elements type, the Swift compiler tells us that it’s just a [UIView] and not an array of UIViews that conform to HeaderViewProtocol. You could go ahead and try things like

let elements : [HeaderViewProtocol] = [MyLabel(), MyButton(), UIStackView()]
let elements : [UIView<HeaderViewProtocol>] = [MyLabel(), MyButton(), UIStackView()]

but nothing works because there is not a way to express a class that conforms to a protocol type in Swift 3. We could have done UIView<HeaderViewProtocol> in Objective-C but that’s a whole different story.

Note: turns out you can do this in Swift 4 with class subtype existentials but this doesn’t really change anything

let elements: [HeaderViewProtocol & UIView]

So in Swift 3, we need to go ahead and create a type eraser like this.

struct AnyHeaderView<Content>: HeaderViewProtocol {
    var _setHeader: (Content) -> ()
    
    init<T: HeaderViewProtocol>(_ view: T) where Content == T.Content {
        _setHeader = view.setHeader
    }
    
    func setHeader(data: Content) {
        return _setHeader(data)
    }
}

Ok, that’s a lot of code to keep the compiler happy. Anyway, we can now go ahead make the array with type safety we want.

let elements = [AnyHeaderView(MyLabel()), AnyHeaderView(MyButton())]

Value Oriented Approach

But instead of passing in a type that promises things, couldn’t we just pass the things we promised?

Read that again and think about that for a second.

And to do just that, let’s make a type that can wrap all the things we promised into a struct.

struct HeaderView<T>{
    let view: UIView
    let setHeader: (T) -> ()
}

Now, we can do this…

let label = UILabel()
let labelHeader = HeaderView<String>(view: label) { str in
    label.text = str
}

let imageView = UIImageView()
let imageHeader = HeaderView<UIImage>(view: imageView ) { img in
    imageView.image = img
}

Using a struct instead of a protocol halved the code size. Once implemented, the solution seems almost too simple to be true that we wonder, “Why couldn’t we think of this the first time?” The reason maybe because we came up with a solution to a problem we didn’t even try tounderstand.

Pick the right tool for the job, not the other way around.

Conclusion

If using protocols in your code is making you write unncessary code to keep the compiler quiet and satisfied, maybe you should consider using struct / function values instead.

Here’s a general rule of thumb.

Situation Solution
1 function in protocol? Function
1 > functions? Protocol
Used only once? (completion handlers, callbacks) Function
Used a lot? (data source, delegate) Protocol

About the talk

This was my first ever programming talk. My content was very techinal and the talk had some live coding in the middle. So naturally, I was nervous.

The talk was held in Korea, at one of Kakao’s buildings. I had just served 2 years in the Army and also had just come back from the ISC that was held in Germany for a week.

My flight landed the day before the conference so I was sleep deprived and so tired that I couldn’t really say all the things I had in my head. I was not prepared for this and maybe I was almost arrogant to think that I would be able to pull this off.

Oh well, at least I learned something from this. At least I won’t make the same mistake for my next talk! 🤦‍♂️