AppVenture has been updated

All articles have been rewritten and improved. You will be forwarded to the updated article.

Click here to go there directly.

Wed, 17 Jun 2015 #

Generic method overloading by protocol in Swift

I just stumbled upon a very nice way of abstraction in Swift, which I previously did not know was possible. First a bit of background:

1 Method Overloading in Swift

There're multiple ways to overload or override methods in Swift. I don't want to go into the details here. If you're interested, this blog post has a pretty good overview. One of the possible overloading mechanisms is by type:

class Example
{
    func method(a : String) -> NSString {
	return a;
    }
    func method(a : UInt) -> NSString {
	return "{\(a)}"
    }
}

Example().method("Foo") // "Foo"
Example().method(123) // "{123}"

As you can see, you can call the same method name with different types, and during compile time the correct code path will be determined and optimized. So, unlike Objective-C, there's no runtime dynamic dispatch to figure out whether the argument to method is a String or a Int.

This is certainly nice, but what if your setup is more complex, and you don't know which kind of type you're getting in a generic function:

class DBStore<T>
{
    func store(a : T) {
	store T
    }
}

Now imagine that T can have different invariants. Let's say you have a protocol 'Storeable' for objects which can be stored in your database, and another protocol 'Interim for objects which should only exist temporarily in memory.

2 Generic Method Overloading by Protocol

You want to call 'store' on all your objects, without having to dynamically check the type to see if the object is Storeable or Interim. You can simply do that by adding the protocol:


class DBStore<T>
{
    func store<T: Storeable>(a : T) {
	store T
    }

    func store<T: Interim>(a : T) {
	compress T
    }
}

Now, you can call the same method in your code, and the correct code path will be determined at compile time without any overhead.

3 Advanced Protocols

With this basic mechanism, you can do much more. as the Swift documentation points out, we can apply any kind of generic constraints in order to better structure our types.

You can overload a generic function or initializer by providing different constraints, requirements, or both on the type parameters in the generic parameter clause. When you call an overloaded generic function or initializer, the compiler uses these constraints to resolve which overloaded function or initializer to invoke. (Apple Documentation)

Here're some examples:

/// Call for all Storeable types which are also equatable
func store<T: Storeable where T:Equatable>(a : T) {
    store T
}

/// Call only for collections, whose objects conform to Storeable
func store<T: Storeable>(a: [T]) {
}

/// Alternatively:
func store<T where T.Generator.Element: Storeable>(a: T) {
}

You can also extend your Structs and Classes from a different file, to add additional functionality based on a value or object which is local to that class

// Objects which are stored in the cloud
protocol CloudStorable {
}
extension DBStore {
    func store<T: CloudStorable> {
      // write your custom cloud store code
    }
}

All in all this gives you great flexibility without the overhead of dynamic dispatch.

If you read this far, you should follow me (@terhechte)
on Twitter