An exhaustive guide to Static keyword AKA Type methods in Swift

This post is about the static keyword. When and how can you use it along with a specific use case? Quick note, in some cases the static keyword can be replaced by the class keyword and although Apple describes it under Type Methods, you can use the static keyword on much more than just methods.

Theory

Class

Methods

In Swift, we differentiate between instance- and type methods. Instance methods are what you are most probably familiar with. Instance methods are what you call on the instance of a class:

class SomeClass {
	func instanceFunc() { print("instance func called") }
}

let instance = SomeClass()
instance.instanceFunc()

On the other hand, we have Type methods. Type methods are called directly on the type instead of the instance. In order to specify a method as a type method, we have to use the statickeyword.

class SomeClass {
	func instanceFunc() { print("instance func called") }
	static func typeMethod () { print("type method called") }
}

let instance = SomeClass()
instance.instanceFunc()

SomeClass.typeMethod()

A quick note that due to some Obj-C shenanigans within classes you can use the classkeyword instead of the statickeyword. However, it is recommended that in Swift you use the statickeyword consistently.

Properties

Unlike the name would suggest the statickeyword can be used on more than just methods. You can specify type properties as well which can be either mutable (var) and immutable (let).

class SomeClass {
	func instanceFunc() { print("instance func called") }
	static func typeMethod () { print("type method called") }

	let instanceProperty = "instance prop"
	var instancePropertyMutable = "instance prop mut"

	static let typePropImmut = "type prop imm"
	static var typePropMut = 1
}

let instance = SomeClass()
instance.instanceFunc()
instance.instanceProperty
instance.instancePropertyMutable

SomeClass.typeMethod()
SomeClass.typePropImmut
SomeClass.typePropMut

The mutable Type properties (static var) can be manipulated.

SomeClass.typePropMut // 1
SomeClass.typePropMut += 1
SomeClass.typePropMut // 2

Within a class or a type, the staticstuff can call each other without the selfkeyword.

class SomeClass {
	func instanceFunc() { print("instance func called") }
	static func typeMethod () { print("type method called") }

	let instanceProperty = "instance prop"
	var instancePropertyMutable = "instance prop mut"

	static let typePropImmut = "type prop imm"
	static var typePropMut = 1

	static func addOne() { typePropMut += 1 }
}

SomeClass.typePropMut // 1
SomeClass.typePropMut += 1
SomeClass.typePropMut // 2

SomeClass.addOne()
SomeClass.typePropMut // 3

Struct

Structs behave much the same way that classes do.

struct SomeStruct {
	static func someFunc() { }
	static let someProp: String = ""
}

SomeStruct.someFunc()
SomeStruct.someProp

Protocol

You can also specify staticstuff in protocols but you cannot call them on the protocol types, not even if you have provided a default implementation in an extension.

protocol SomeProtocol {
	static func someFunc ()
}

SomeProtocol.someFunc() // ERROR

extension SomeProtocol {
	static func someFunc() { print ("protocol") }
}

SomeProtocol.someFunc() // ERROR

In practice

Now let’s look at a specific example. In the Official Apple Documentation, Apple uses a Level tracker minigame as an example so I’m going to stick to it.

The idea of the Level tracker is that you have a game and there are multiple users on a single device. A player can select a level to play on from the already unlocked levels. Anyone can unlock a level and it will sync across to everyone. At first, only level 1 is unlocked.

Here is the first half of the code:

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

The above struct works in the following manner.
The highestUnlockedLeveltype property is storing the highest level that was unlocked by any player.
unlocktype method unlocks a specified level.
isUnlockedtype method checks if a level is already unlocked or not, this is for convenience.
currentLevelinstance property keeps track of which level is an individual player on.
advancemethod advances the player to the next level if it is unlocked.
@discardableResulttag is used in order to silence those pesky warnings about not using the returned value.

The LevelTrackerclass is used in conjunction with the new class calledPlayer.

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

The Playerclass creates an instance ofLevelTracker, gives a name to the player and the only thing it can do is to advance to the next level which is done by first unlocking the next level and then advancing to it.

The above mechanism could be used like so for example:

let player1 = Player(name: "Player1")
player1.complete(level: 1)
player1.complete(level: 2)
player1.complete(level: 4)
print("Highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")

let player2 = Player(name: "Player2")
player2.tracker.advance(to: 5)

Here you can see that player1 advances up to level 5. Then I check what is the highest level globally and I advance player2 to that level.


I hope you have found this post useful. The main source for the post comes from Apple’s Official Documentation.

Make sure to check back regularly and to subscribe to my YouTube channel since I post content at least weekly.

Feedback is always appreciated.