I’m currently doing a very big refactor of try! Swift Data now that the Tokyo conference is over and I have time to repay some technical debt. As part of the refactor, I’m removing a bunch of large switch statements for view-level display data and putting them into individual view models that conform to a strict protocol.

The Setup

The conference app has different sessions – talks, breakfast, lunch, announcements, etc. They are all displayed in a Table View with a title, subtitle, location, etc. Before, the data layer was messy like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

// Session.swift

import RealmSwift

import Foundation

@objc publicenumSessionType:Int{

caseworkshop

casemeetup

casebreakfast

caseannouncement

casetalk

caselightningTalk

casesponsoredDemo

casecoffeeBreak

caselunch

caseofficeHours

caseparty

}

publicclassSession:Object{

/** The type of content in this particular session */

open dynamic vartype:SessionType=.talk

// many other class properties here

/***************************************************/

// VIEW DISPLAY LOGIC BELOW

/** The main name of this session */

publicvarformattedTitle:String?{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

/** A follow-up tagline for the session */

publicvarformattedSubtitle:String?{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

/** What image, if any is available for this session */

publicvarlogoURL:URL{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

/** The location for where this session will occur */

publicvarformattedLocation:String{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

/** A long-form description of the session */

publicvarsessionDescription:String{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

/** Presentation Summary */

publicvarpresentationSummary:String{

switchself.type{

// VERY LONG SWITCH STATEMENT

// LOTS OF DISPLAY LOGIC

}

}

// YOU GET THE POINT

// MORE METHODS HERE WITH A LOT OF SWITCH STATEMENTS

}

So I extracted the data display methods into a protocol:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

protocolSessionDisplayable{

/** The main name of this session */

vartitle:String{get}

/** A follow-up tagline for the session */

varsubtitle:String{get}

/** What image, if any is available for this session */

varlogoURL:URL{get}

/** The location for where this session will occur */

varlocation:String{get}

/** A long-form description of the session */

varsessionDescription:String{get}

/** Presentation Summary */

varpresentationSummary:String{get}

/** What Twitter handle, if any represents this session */

vartwitter:String{get}

/** Whether this type of session requires a new view controller to display more information */

varselectable:Bool{get}

}

And created individual view models for each session type. For example, here is the BreakfastSessionViewModel:

The Problem

The big thing here is that multiple session view models have the same default data implementation. That is why I created a SessionDataDefaults object to access the default data easily (see the use-case in the BreakfastSessionViewModel implementation).

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// SessionDefaults.swift

structSessionDataDefaults{

let title:String

let subtitle="try! Conference"

let logoImageURL=Bundle.trySwiftAssetURL(for:"Logo.png")!

let imageURL:URL?

let location:String

let summary=Conference.current.localizedDescription

let twitter=Conference.current.twitter!

init(session:Session){

// properties set here

}

}

So as you can imagine, some of the session view model implementations (check the BreakfastSessionViewModel for reference) use the default data values.

When I shared this refactor with a friend, he immediately saw a new refactoring opportunity – create default implementations of the relevant methods in the protocol!

Default Implementation?

At first, that sounded great, but after thinking about it I decided against the default implementation refactor. Here’s why:

Each variable of a session needs a lot of thought put into it. Even if the implementation details end up being the same as the default, I want it to be a strong conscious choice. If I make it a default implementation, it would be too easy to forget and not think about much. Oh, and the compiler won’t complain!

I want it to be easy to change the variable for each session. If a variable is not included in the file because the implementation is in the protocol, it’s more work to make the decision to add that into the file. The assumption is that the default should win. If it’s already there, it’s easier to just go in and make a small change.

Similar to the above point, I want it to be super readable where each variable is coming from. If a bunch of variables are in the default implementation of the protocol, it’s not as clear just by looking at the file.

I think adding default implementations to protocols should be considered very very carefully. The default needs to be something that is consistent and is the default for most cases, so if someone forgets to implement it (because the compiler won’t complain), it’s most likely not a big deal.

The one case where I love default implementations in protocols is when the function is not included in the the protocol definition – it’s just a common method with no interface exposed. Otherwise, it’s way too easy to forget about and introduce confusing bugs later!

Enjoy the article? Join over 20,000+ Swift developers and enthusiasts who get my weekly updates.

Philip Zhao

I don’t really see the need of using protocol here. Why can’t you create a concrete object and set up a builder pattern to configure its properties? Protocol seems a bit overkill.

I’ve actually been down exactly the same path in my rewrite of Harken. Initially I wrote a default implementation, then realised I needed to specialise it in 6 out of the 7 classes that conformed to the protocol. And it feels like the default implementations are a bit too magical and it’s easy to forget they exist, almost like a side effect and that smells wrong to me.

Sam Duke

Coming from Java development on Android, default implementations on protocols feel like the Swift equivalent of an abstract class in Java. I think the points you raise are valid but don’t think we should blanket say we shouldn’t use default implementation. I think we could learn a lot from the do’s and don’ts of abstract classes which have many of the same properties (compiler doesn’t complain, easy to forget etc)