Swift iflet and guard haters, you are welcome. I have an extension to the topic for you.

As I previously mentioned in Type Inference in Swift. I’m really tired of writing iflets and guards all over the place in my code.

In order to outline the problems lets imagine we have a UITableView, in which we need to present UITableViewCell subclasses from different inheritance hierarchies:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell { let identifier = ( indexPath . row % 2 ) == 0 ? "GrayCell" : "PinkCell" let cell = tableView . dequeueReusableCell ( withIdentifier : identifier , for : indexPath ) if let pinkCell = cell as ? PinkCell { pinkCell . fill ( withModel : indexPath . row ) } if let grayCell = cell as ? GrayCell { grayCell . fill ( withModel : "row = \ ( indexPath . row ) , column = \ ( indexPath . section ) " ) } return cell }

Then, lets imagine, that Swift dev team invalidates iflets. As we all know, xCode Swift migration tools don’t work all the time and we have to rewrite the code manually for some cases. I had such transitions with Swift selectors and that was a huge pain in my butt. Moreover, in my opinion, iflets are against the DRY principle, which we all love and adore. The non-DRY code is really painful in terms of refactoring or rewriting, as you don’t have centralized access points.

In order to solve those problems, I prefer using my own solution:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell { let identifier = ( indexPath . row % 2 ) == 0 ? "GrayCell" : "PinkCell" let cell = tableView . dequeueReusableCell ( withIdentifier : identifier , for : indexPath ) return castable ( cell ) { ( c : PinkCell ) in c . fill ( withModel : indexPath . row ) } . match { ( c : GrayCell ) in c . fill ( withModel : "row = \ ( indexPath . row ) , column = \ ( indexPath . section ) " ) } . extract ( ) }

We’ll get into the implementation details a bit later, but right now let me explain, why in my (not really) humble opinion, this solution is superior:

It uses type inference;

It is expressive;

It is extensible;

It is robust.

Yeah-yeah, I know, it makes the learning curve steeper for any newcomer, who is not aware of it. And yes, switch pattern matching could do the same, although the syntax would be a bit more verbose. But what it ultimately provides is, that as long, as I abide the interface, I could inject any behavior I want no matter, how Swift changes. Of course, it’s ultimately your decision, if you’d prefer to use it. I definitely prefer, as I can express the same logic in less key strokes and concentrate on the really important stuff (my ideas as to what I want to express, not how I want to express it).

So, how does it work under the hood?

Please, read through one of my previous articles about cast function. I’ll just outline its signature in here for the greater good, but you need to know, how to work with it:

1 2 3 4 func cast < Type , Result > ( _ value : Type ) -> Result ? { return value as ? Result }

The guts of the solution I came up with are based on a Castable generic type:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 enum Castable < Wrapped > { case value ( Wrapped ) @ discardableResult func match < Subject > ( f : ( Subject ) -> ( ) ) -> Castable < Wrapped > { switch self { case let . value ( x ) : cast ( x ) . map { f ( $ 0 ) } return . value ( x ) } } func extract ( ) -> Wrapped { switch self { case let . value ( x ) : return x } } }

First of all, lets answer the most obvious question: “Why the hell do we use switch for one case? Why not if case?” The reason is actually simple. In order for Swift to handle such expressions without the default return, the function should be exhaustive and anything, except for switch covering all cases of enum is considered non-exhaustive by Swift. By exhaustive I mean, that all the cases of enum are handled. Moreover if case has a much worse syntax:

1 2 3 4 5 6 7 8 9 10 11 enum NonExhaustive < Wrapped > { case value ( Wrapped ) func match < Subject > ( f : ( Subject ) -> ( ) ) -> NonExhaustive < Wrapped > { if case . value ( let x ) = self { cast ( x ) . map { f ( $ 0 ) } return . value ( x ) } } }

This fails with error missing return in a function expected to return 'NonExhaustive<Wrapped>'

Rewriting it with default return value as self could solve the problem:

1 2 3 4 5 6 7 8 9 func match < Subject > ( f : ( Subject ) -> ( ) ) -> NonExhaustive < Wrapped > { if case . value ( let x ) = self { cast ( x ) . map { f ( $ 0 ) } return . value ( x ) } return self }

But that’s just too much code for its own good.

The second question, that could probably arise is: “How is it working?”

You see, I consider Castable as a type can be procesed in 2 ways: matching and extraction.

By matching I mean casting a wrapped value and if the cast succeeds, calling the function with the result of casting. To be able to chain operations I just return another Castable wrapping that same value.

Extraction just unwraps and returns a value.

From my POV, creation of Castable by rewriting the same lines over and over again is not a good idea. Just imagine, that each time you want to use matching and extraction, you’d have to write Castable.value(x).match(... And that’s the most common use case, right? Sadly, we can’t just create a global function out of Castable.value initializer, as Swift doesn’t allow that for generics: let castableFunction = Castable.value results in error: generic parameter 'Wrapped' could not be inferred note: explicitly specify the generic arguments to fix this issue . Well, what can I say? Swiftc, you are drunk, go home.

So, in order to fix the thing I created several functions, that are the most common use – cases in my opinion:

1 2 3 4 5 6 7 8 9 10 11 12 func castable < Wrapped > ( _ x : Wrapped ) -> Castable < Wrapped > { return Castable . value ( x ) } func castable < Wrapped , Subject > ( _ x : Wrapped , f : ( Subject ) -> ( ) ) -> Castable < Wrapped > { return castable ( x ) . match ( f : f ) } func match < Wrapped , Subject > ( _ x : Wrapped , f : ( Subject ) -> ( ) ) -> Wrapped { return castable ( x , f : f ) . extract ( ) }

If you see any other use-cases or just want to add something, don’t hesitate to create the pull requests.

The library is readily available at cocoapods IDPCastable and is available on github as well: IDPCastable. You can check out the example in Tests/iOS, if you’d like.

That’s all, folks. Have a great day and stay DRY, no matter, where you are.