Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Fake Swift Enums and User-Friendly Frameworks

Save for later
  • 7 min read
  • 28 Sep 2016

article-image

Swift Enums are a great and useful hybrid of classic enums with tuples. One of the most attractive points of consideration here is their short .something API when you want to pass one as an argument to a function. Still, they are quite limited in certain situations. OK, enough about enums themselves, because this post is about fake enums (don't take my word literally here).

Why?

Everybody is talking about usability, UX, and how to make the app user-friendly. This all makes sense, because at the end of the day we all are humans and perceive things with feelings. So the app just leaves the impression footprint. There are a lot of users who would prefer a more friendly app to the one providing a richer functionality. All these talks are proven by Apple and their products.

But what about a developer, and what tools and bricks are we using? Right now I'm not talking about the IDEs, but rather about the frameworks and the APIs. Being an open source framework developer myself (right now I'm working on Swift Express, a web application framework in Swift, and the foundation around it), I'm concerned about the looks of the APIs we are providing. It matches one-to-one to the looks of the app, so it has the same importance. Call it Framework's UX if you would like. If the developer is pleased with the API the framework provides, he is 90% already your user.

This post was particularly inspired by the Event sub-micro-framework, a part of the Reactive Swift foundation I'm creating right now to make Express solid. We took the basic idea from node.js EvenEmitter, which is very easy to use in my opinion. Though, instead of using the String Event ID approach provided by node, we wanted to use the .something approach (read above about what I think of nice APIs) and we are hoping that enums would work great, we encountered limitations with it. The hardest thing was to create a possibility to use different arguments for closures of different event types. It's all very simple with dynamic languages like JavaScript, but well, here we are type-safe. You know...

The problem

Usually, when creating a new framework, I try to first write an ideal API, or the one I would like to see at the very end. And here is what I had:

eventEmitter.on(.stringevent) { string in
   print("string:", string)
}
      
eventEmitter.on(.intevent) { i in
   print("int:", i)
}
      
eventEmitter.on(.complexevent) { (s, i) in
   print("complex: string:", s, "int:", i)
}

This seems easy enough for the final user. What I like the most here is that different EventEmitters can have different sets of events with specific data payloads and still provide the .something enum style notation.

This is easier to say than to do. With enums, you cannot have an associated type bound to a specific case. It must be bound to the entire enum, or nothing. So there is no possibility to provide a specific data payload for a particular case. But I was very clear that I want everything type-safe, so there can be no dynamic arguments.

The research

First of all, I began investigating if there is a possibility to use the .something notation without using enums. The first thing I recalled was the OptionSetType that is mainly used to combine the flags for C APIs. And it allows the .somthing notation. You might want to investigate this protocol as it's just cool and useful in many situations where enums are not enough.

After a bit of experimenting, I found out that any class or struct having a static member of Self type can mimic an enum. Pretty much like this:

struct TestFakeEnum {
   private init() {
   }
  
   static let one:TestFakeEnum = TestFakeEnum()
   static let two:TestFakeEnum = TestFakeEnum()
   static let three:TestFakeEnum = TestFakeEnum()
}

func funWithEnum(arg:TestFakeEnum) {
}

func testFakeEnum() {
   funWithEnum(.one)
   funWithEnum(.two)
   funWithEnum(.three)
}

This code will compile and run correctly. These are the basics of any fake enum. Even though the example above does not provide any particular benefit over built-in enums, it demonstrates the fundamental possibility.

Getting generic

Let's move on. First of all, to make our events have a specific data payload, we've added an EventProtocol (just keep in mind; it will be important later):

//do not pay attention to Hashable, it's used for internal event routing mechanism. Not a subject here
public protocol EventProtocol : Hashable {
   associatedtype Payload
}

To make our emitter even better we should not limit it to a restricted set of events, but rather allow the user to extend it. To achieve this, I've added a notion of EventGroup. It's not a particular type but rather an informal concept, so every event group should follow. Here is an example of an EventGroup:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
struct TestEventGroup<E : EventProtocol> {
   internal let event:E
  
   private init(_ event:E) {
       self.event = event
   }
  
   static var string:TestEventGroup<TestEventString> {
       return TestEventGroup<TestEventString>(.event)
   }
  
   static var int:TestEventGroup<TestEventInt> {
       return TestEventGroup<TestEventInt>(.event)
   }
  
   static var complex:TestEventGroup<TestEventComplex> {
       return TestEventGroup<TestEventComplex>(.event)
   }
}

Here is what TestEventString, TestEventInt and TestEventComplex are (real enums are used here only to have conformance with Hashable and to be a case singleton, so don't bother):

//Notice, that every event here has its own Payload type

enum TestEventString : EventProtocol {
   typealias Payload = String
   case event
}

enum TestEventInt : EventProtocol {
   typealias Payload = Int
   case event
}

enum TestEventComplex : EventProtocol {
   typealias Payload = (String, Int)
   case event
}

So to get a generic with .something notation, you have to create a generic class or struct having static members of the owner type with a particular generic param applied.

Now, how can you use it? How can you discover what generic type is associated with a specific option? For that, I used the following generic function:

// notice, that Payload is type-safety extracted from the associated event here with E.Payload

func on<E : EventProtocol>(groupedEvent: TestEventGroup<E>, handler:E.Payload->Void) -> Listener {
   //implementation here is not of the subject of the article
}

Does this thing work? Yes. You can use the API exactly like it was outlined in the very beginning of this post:

let eventEmitter = EventEmitterTest()

   eventEmitter.on(.string) { s in
   print("string:", s)
}
      
eventEmitter.on(.int) { i in
   print("int:", i)
}
      
eventEmitter.on(.complex) { (s, i) in
   print("complex: string:", s, "int:", i)
}

All the type inferences work. It's type safe. It's user friendly. And it is all thanks to a possibility to associate the type with the .something enum-like member.

Conclusion

Pity that this functionality is not available out of the box with built-in enums. For all the experiments to make this happen, I had to spend several hours. Maybe in one of the upcoming versions of Swift (3? 4.0?), Apple will let us get the type of associated values of an enum or something. But… okay. Those are dreams and out of the scope of this post. For now, we have what we have, and I'm really glad that are abele to have an associatedtype with enum-like entity, even if it's not straightforward.

The examples were taken from the Event project. The complete code can be found here and it was tested with Swift 2.2 (Xcode 7.3), which is the latest at the time of writing.

Thanks for reading. Use user-friendly frameworks only and enjoy your day!

About the Author

Daniel Leping is the CEO of Crossroad Labs. He has been working with Swift since the early beta releases and continues to do so at the Swift Express project. His main interests are reactive and functional programming with Swift, Swift-based web technologies, and bringing the best of modern techniques to the Swift world. He can be found on GitHub.