Photo: Pexels.com

Accessing Types from Extensions in Swift

Marin Benčević
COBE

--

Swift has a powerful type system which enables us to build really nice systems we can express by type signatures alone. Sometimes it’s valuable to work with a type as with any other variable, or access the current type inside an extension. This can get a bit confusing since there’s multiple ways to achieve this, so here’s a small collection of things I ran into and learned while working with Swift types.

What’s a method?

Before we get into working with the types, let’s think about the difference between a type (like a class), an instance of that type (an object), and methods on that type.

The standard OOP definition of a method is that it’s a function inside a class. It’s true, but that describes a method on a very superficial level. A method is really a function that takes an object, and returns a function.

Yup. When you think about it, every method implicitly takes self as the first parameter. It then uses self’s properties inside a function (the one defined in the body of the method), and returns that function.

A function that takes an object and returns a function is semantically identical to a method!

This is really obvious in Swift: If you look at the type signature of a method.

class MyClass {

let someProperty = "property"

func method(_ arg1: String) {
print(someProperty + arg1)
}
}
let method = MyClass.method // (MyClass) -> (String) -> ()

You can see that it’s a function that takes an object and returns a function. (Also called a curried function.) Methods are mappings from objects to functions. If you call a method with an object, you will get back the actual function, which can use self as one of its parameters.

let method = MyClass.method // (MyClass) -> (String) -> ()
let object = MyClass()
// The next two lines do the same thing:method(object)("argument-1")
object.method("argument-1")

You can then call the method like you regularly would. In a way, the dot operator (`object.method(“argument”)`) is syntax sugar for `Class.method(object)(“argument”)`.

Methods on protocols, structs and enums work the same way!

Now that we understand the basics of how types and methods work, let’s look at how we could access a type from inside a function or a method.

Static/class functions

So how do class or static methods differ from regular methods? They work like global functions inside the class’ namespace. Their type signature is the same as it would be outside the class.

func globalFunction(_ arg1: String) {
print(arg1)
}
class MyClass {

class func classFunction(_ arg1: String) {
print(arg1)
}

}
let globalF = globalFunction // (String)-> Voidlet classF = MyClass.classFunction // (String)-> Void

However, there’s one small, but important difference. In a global function you can’t access `self`. It makes sense, since we aren’t inside any type so we can’t know what self even is.

A surprising feature of Swift is that you can access `self` inside a class or static function! Class functions aren’t called on an object, though, so what is self inside class functions? Self is the type!

class MyClass {

class func classFunction() {
print(self) // "MyClass"
}

}

This is one way to access the current type in Swift. In class or static functions on a type, `self` is the actual type. You can use this type to call methods on it, initialize it etc. like you would with any other type.

Again, this also applies to enums and structs. Even in protocol extensions, self in static functions will be the type conforming to the protocol.

Speaking of protocols, let’s see how this looks inside protocol extensions.

Protocol extensions

There’s a magical keyword that works inside protocol extensions: `Self`(note the capital S) is the type that is conforming to that protocol.

extension MyProtocol {

func protocolFunction()-> String {
return "\(Self.self)"
}

}
let object = MyClass()object.protocolFunction() // "MyClass"

We’re using Self.self because Swift wants us to explicitly say we want to use a type, and not an instance of that type.

Inside static functions in protocol extensions, Self will be completely identical to `self`, because self is the actual type (and not an object).

extension MyProtocol {

static func staticProtocolFunction() {
print(Self.self == self) // true
}
}

You can also use `Self` in the declaration of a function.

protocol MyProtocol {
init()
}
extension MyProtocol {

static func create()-> Self {
return Self()
}
}

Here we have a static function on a protocol that will return an instance of the type that is conforming to that protocol, without needing any type casting.

Class extensions

So far, things have been pretty simple. That is about to change. The reason? Inheritance and polymorphism.

Inheritance and polymorphism are defining concepts of object-oriented programming. They give us the ability to treat any class that inherits from `MyClass` as an actual instance of `MyClass`.

Some methods, however, might act in a different way depending on which subclass we are using. This is often the case when doing more dynamic, runtime kind of programming.

Let’s say you want to have a `className` property on your `UIViewController`s. You want this string to reflect the actual class name of the derived class, and not be “UIViewController”.

extension UIViewController {

var className: String {
return String(describing: Self.self)
// error: use of unresolved identifier 'Self'
}
}

This won’t compile, because `Self`exists only for protocol extensions.

We need to make the computed `className` variable static, which will allow us to use `self` and get the type.

extension UIViewController {

static var className: String {
return String(describing: self)
}
}class MyViewController: UIViewController {}MyViewController.className // "MyViewController"

But what if we can’t have the method static? What if we want to access the type of an actual object?

In that case, we can use the `type(of:)` function from the Swift standard library. This function will return the type of an object dynamically.

extension UIViewController {    var className: String {
let type = type(of: self)
return String(describing: type)
}
}class MyViewController: UIViewController {}let viewController = MyViewController()viewController.className // "MyViewController"

Like above, `type(of:)` will give you a type that you can initialize, call methods on and generally use as any other type.

One caveat I found is that `type(of:)` doesn’t work with the is keyword in Swift. MyViewController is type(of: self) will give a weird compiler error. Use can use MyViewController == type(of: self) instead. The semantics are different, however. Is-a will succeed if one of the types inherits from the other, but equality will only be true if the types are identical.

So meta

let viewControllerType = MyViewController.self 
// MyViewController.Type

Let’s look for a second at the type of the type variable. If that sounds a bit meta to you, that’s because it is! The type of the type variable is what’s called a meta-type.

A type represents a set of values. An `Int` type represents 1, 2, 3 and all other possible integer values. The type of our type variable needs to represent every possible `UIViewController` type, like `UITableViewController` and `MyViewController`.

We have been working with variables whose values aren’t actual objects nor values, but types. Because the value of the variable is a type, the type of that variable must be a “metatype”. You can recognize metatypes by the T.Type suffix in their declaration.

You can use metatypes in function declarations or return values if you want to work with types. For instance, if we want to find a specific type of UIViewController inside a navigation controller.

extension UINavigationController {

func index(of type: UIViewController.Type)-> Int? {
return self.viewControllers.index(where: { vc in
type(of: vc) == type
})
}

}
let navigationController = UINavigationController()navigationController.index(of: MyViewController.self)

Again, we are using MyViewController.self to refer to the actual type.

In a static method of MyClass, `self` is actually `MyClass.Type`, while in a non-static method, it would be `MyClass`.

To sum up, here’s a list of ways to access the current type from inside a function (or computed variable) in an extension:

  1. In static functions, use `self` to refer to the type. In protocol extensions, this will be the type that conforms to the protocol, and not the protocol itself.
  2. In non-static extensions, use type(of: self) to access the current type.
  3. In protocol extensions, you can also use Self as a special associated type which will be the type conforming to the protocol.

Remember, `T.self` is the type as a value that can be assigned to a variable. That variable has to have the `T.Type` type, which is a metatype describing all T subtypes.

Marin is an iOS developer at COBE, a blogger at marinbenc.com, and a Computer Science student at FERIT, Osijek. He likes to program, learn about stuff and then write about them, ride bicycles and drink coffee. Mostly, though, he just causes SourceKit crashes. He has a chubby cat called Amigo. He totally didn’t write this bio himself.

--

--