Published by Arunprasadh C on 26 May 2022Last Updated on 02 June 2022

Classes in Swift

A Class is considered as a blueprint of objects. In Swift, a Class is similar to a Structure in most of the aspects. However, Swift Classes have the following unique properties and additional capabilities as compared to Structures:

Due to the above mentioned capabilities, Classes in Swift can leverage all the benefits of Object-oriented Programming. Classes in Swift are marked by the class keyword.

Syntax :

class ClassName
{
   //Class Properties and Methods
}

It is similar to struct declaration, right ? So, when to choose Class over Structure ?

Choosing between Classes and Structures

The Apple Developer Documentation suggests the following Recommendations (Quoted from the Documentation):

Object Creation and Accessing Members

Object creation, accessing properties and methods of a class and are all similar to struct.

Example 1:

class Student
{
    var rollNo: Int
    var name: String
    
    init(rollNo: Int, name: String)
    {
        self.rollNo = rollNo
        self.name = name
    }
}

let student = Student(rollNo: 1, name: "Kris")
print(student.rollNo)
print(student.name)
print(student)

Output 1:

1
Kris
helloworld.Student

You can notice that when I printed the student object, I didn’t get a standard String representation of the object like Structures. This is because class in Swift doesn’t conform to CustomStringConvertible Protocol by default unlike Structures and even this can be attributed to Inheritance because only at runtime, the type of object is decided.

Initializers

We have already seen about Initializers in Structures, but we will see more about them here. For stuctures, memberwise initializers are automatically generated by the compiler. But, for classes the compiler doesn’t generate Member-wise initializer on its own, as Classes allow Inheritance and it is difficult for the compiler to decide how the Initializer should be generated. If Initializer is not defined manually for the Class, the code won’t compile and the compiler will throw an error Class 'ClassName' has no initializers. However, if all the properties of a class or struct have default values, then a default initializer will be generated even if it is not manually written.

The Initializers for Classes are classified into various types. The general Initializers are called Designated Initializers while the Initializers which make the calling of Designated ones easier, are called as Convenience Initializers and are marked by the keyword convenience. Convenience Initializers are generally used for assigning default values to properties. Designated and Convenience Initializers will be explored in detail while exploring Inheritance. We can also define Failable Initializers which can fail and return nil instead of an instance. Failable Initializers are defined by marking the init keyword with Question Mark ?, like init?. Failable Initializers will always return Optional wrapped Instances which have to be unwrapped for further use. Even ! can be used with Failable initializer like init! to throw an error when Initialization fails. When a property needs to be assigned the same initial value all the time, it is advised to give the default value in the property declaration itself, rather than assigning the default value via an initializer.

Example 2:

class Age: CustomStringConvertible
{
    var description: String
    {
        "Age(\(value))"
    }
    
    var value: Int
    
    init?(_ value: Int)
    {
        if value < 0 || value > 100
        {
            print("Invalid Age \(value)! ")
            return nil
        }
        else
        {
            self.value = value
        }
    }
    
    init()
    {
        self.value = Int.random(in: 0...100)
    }
}

for x in [-2, 21, 500]
{
    if let age = Age(x)
    {
        print(age)
    }
    else
    {
        print("As Invalid Age \(x) is provided, randomizing Age Value: \(Age())")
    }
}

Output 2:

Invalid Age -2! 
As Invalid Age -2 is provided, randomizing Age Value: Age(3)
Age(21)
Invalid Age 500! 
As Invalid Age 500 is provided, randomizing Age Value: Age(82)

Deinitializer

In addition to Initializers, Swift Classes can also have a single Deinitializer, which is called just before the Object of the Class is deallocated (Similar to C++ Destructors). These can be used for performing additional tasks like closing a file or any held resource, thereby freeing up the memory before the class object itself is deallocated. Messages printed inside Deinitializer block can generally be realized when the object is being deallocated.

Example 3:

class Student: CustomStringConvertible
{
    var rollNo: Int
    var name: String
    var description: String
    {
        "Student(rollNo = \(rollNo), name = \(name))"
    }
    
    init(rollNo: Int, name: String)
    {
        self.rollNo = rollNo
        self.name = name
    }
    
    func changeRollNoAndName(newRollNo:Int, newName: String)
    {
        rollNo = newRollNo
        name = newName
    }
    
    deinit
    {
        print("Student: \(name) is being deinitialized...")
    }
}

var student: Student? = Student(rollNo: 1, name: "Kris")
print(student!)
student = nil // Deinitializing the object
let _ = Student(rollNo: 2, name: "Siva") // Using '_' to indicate ignored or unused object. Hence it is deallocated immediately.

Output 3:

Student(rollNo = 1, name = Kris)
Student: Kris is being deinitialized...
Student: Siva is being deinitialized...

When nil is assigned to the Optional student object, it means that it can be deallocated and hence the deinitializer is called. Underscore _ is used for Wildcard Pattern Matching in Swift. It is used whenever a variable or constant is ignored or unwanted. It is also used when the return type of a function is ignored. It is used in for loops too when the iterating variable can be ignored. In the Above example, the _ indicates that the object can be ignored or is not going to be used. Hence, the scope of the object gets over as soon as it is created and hence, the deinitializer is called.

Protocol Conformance

Classes can also conform to Protocols like Structures and use the same syntax as Structures for conforming. We will see more about Protocols in a separate Topic Page.

Behaviour with let Constants

As mentioned earlier in Structures, let Constants behave differently with Classes. The regular behaviour of preventing reassigning of objects is maintained. However, the properties of Class object can be mutated even when using let, as Classes are Reference Types.

Example 4:

class Student: CustomStringConvertible
{
    var rollNo: Int
    var name: String
    var description: String
    {
        "Student(rollNo = \(rollNo), name = \(name))"
    }
    
    init(rollNo: Int, name: String)
    {
        self.rollNo = rollNo
        self.name = name
    }
}
let student = Student(rollNo: 1, name: "Kris")
print(student.description)
print(student.rollNo)
student.name = "Shiv" // Modifying the property of a let constant object
print(student.name)
print(student)

Output 4:

Student(rollNo = 1, name = Kris)
1
Shiv
Student(rollNo = 1, name = Shiv)

You can see that the name was changed even though student is a let constant.

No need of mutating Keyword for Mutating Methods

Unlike struct, the Mutating Methods of class don’t need the mutating keyword, as Classes are Reference Types.

Example 5:

class Student: CustomStringConvertible
{
    var rollNo: Int
    var name: String
    var description: String
    {
        "Student(rollNo = \(rollNo), name = \(name))"
    }
    
    init(rollNo: Int, name: String)
    {
        self.rollNo = rollNo
        self.name = name
    }
    
    func changeRollNoAndName(newRollNo:Int, newName: String)
    {
        rollNo = newRollNo
        name = newName
    }
}

let student = Student(rollNo: 1, name: "Kris")
print("Original Value: \(student)")
student.changeRollNoAndName(newRollNo: 2, newName: "Shiv")
print("After calling changeRollNoAndName() Method: \(student)")

Output 5:

Original Value: Student(rollNo = 1, name = Kris)
After calling changeRollNoAndName() Method: Student(rollNo = 2, name = Shiv)

Equality and Identity Logic

Unlike Structures, the Identity Operators (=== and !==) can be used with Classes. Identity Operators compare the memory uniqueness of two class instances. They don’t check if the values are equal. They instead check if the two instances point to the same reference in the memory. In order for Equality Operators (== and !=) to work, the Class has to conform to the Equatable Protocol. However, unlike Structures, it is not optional to override the == function. It must be redefined inside the class for the equality operators to work. The equality operators don’t care about the memory address. They rather care about the properties of the instances.

Example 6:

class Student: CustomStringConvertible, Equatable
{
    var rollNo: Int
    var name: String
    var description: String
    {
        "Student(rollNo = \(rollNo), name = \(name))"
    }

    init(rollNo: Int, name: String)
    {
        self.rollNo = rollNo
        self.name = name
    }

    func changeRollNoAndName(newRollNo:Int, newName: String)
    {
        rollNo = newRollNo
        name = newName
    }
    
    static func == (lhs: Student, rhs: Student) -> Bool
    {
        lhs.name == rhs.name && lhs.rollNo == lhs.rollNo
    }
    
    deinit
    {
        print("Student: \(name) is being deinitialized...")
    }
}

let student1 = Student(rollNo: 1, name: "Kris")
let student2 = student1
let student3 = Student(rollNo: 2, name: "Kris")
print("Student 1: \(student1)")
print("Student 2: \(student2)")
print("Student 3: \(student3)")
print("student1 === student2: \(student1 === student2)")
student2.rollNo = 2 // Changing rollNo of student 2
print("Student 1: \(student1)") // Student 1 rollNo also changed. Call by reference behaviour
print("Student 2: \(student2)")
print("student2 === student3: \(student2 === student3)") // Even though Same value but different reference, so false
print("student2 == student3: \(student2 == student3)") // Even though different reference, same value is present. so == returns true
print("student1 == student2: \(student1 == student2)")
print("student1 == student3: \(student1 == student3)")
let student4 = Student(rollNo: 3, name: "Ram")
print("Student 4: \(student4)")
print("student4 != student1: \(student4 != student1)")// true as rollNo not and Name equal

Output 6:

Student 1: Student(rollNo = 1, name = Kris)
Student 2: Student(rollNo = 1, name = Kris)
Student 3: Student(rollNo = 2, name = Kris)
student1 === student2: true
Student 1: Student(rollNo = 2, name = Kris)
Student 2: Student(rollNo = 2, name = Kris)
student2 === student3: false
student2 == student3: true
student1 == student2: true
student1 == student3: true
Student 4: Student(rollNo = 3, name = Ram)
student4 != student1: true

Inheritance using Classes

Unlike Structures, Classes can inherit properties and methods from one another. The Class which inherits from another class is called as Subclass/Child Class and the Class whose members are inherited is called the Super Class/Parent Class(It is also known as Base Class, if it doesn’t inherit from other classes). Classes in Swift can call and access methods, properties, and subscripts belonging to their superclass and can provide their own overriding versions of those methods, properties, and subscripts to refine or modify their behavior. Swift helps to ensure your overrides are correct by checking that the override definition has a matching superclass definition. Classes can also add property observers to inherited properties in order to be notified when the value of a property changes. Property observers can be added to any property, regardless of whether it was originally defined as a stored or computed property.

Is-A Relationship

Inheritance should be used only when there exists an Is-A Relationship between Two Classes. For example:

  1. Car is a Vehicle.
  2. Student is a Person.
  3. Dog is an Animal. Here, Car can inherit members from Vehicle, Student from Person and so on.

The Syntax of Inheritance is as follows: Syntax :

class Subclass : Superclass
{
   //Class Members
}

The Colon (:) Symbol is used to indicate Inheritance (In terms of Swift, we can even compare it with Type Annotations. We can infer that the SubClass is of Superclass Type). The override keyword is used for overriding the Non-private instance methods, type methods, instance properties, type properties, initializers and subscript of the super class. The super keyword is used for accessing the Non-private members of the super class from the subclass. Always, the overridden members are given priority when called from the child classes. A simple example is given below:

Example 7:

class Animal: CustomStringConvertible
{
    var description: String
    {
        "Animal(isMammal: \(isMammal))"
    }
    
    var isMammal: Bool
    
    init(isMammal: Bool)
    {
        self.isMammal = isMammal
    }
    
    func makeSound()
    {
        fatalError("This method must be overridden by subclasses for it to work!!!") // Abstract Methods can be implemented like this
    }
}

class Dog: Animal
{
    override var description: String
    {
        "Dog(isMammal: \(isMammal), isStrayDog: \(isStrayDog))"
    }
    
    var isStrayDog: Bool
    
    override func makeSound()
    {
        print("Woof!!!")
    }
    
    init(isStrayDog: Bool)
    {
        self.isStrayDog = isStrayDog
        super.init(isMammal: true)
    }
}

var animal: Animal = Animal(isMammal: false)
print(animal)
print(type(of: animal))
animal = Dog(isStrayDog: false) // Works since Dog is a subclass of Animal.
animal.makeSound()
print(animal)
print(type(of: animal))

Output 7:

Animal(isMammal: false)
Animal
Woof!!!
Dog(isMammal: true, isStrayDog: false)
Dog

In the above example, Animal is the base class from which subclass Dog is derived. Animal has a method makeSound() which becomes valid only when it is overridden by subclasses. Conceptually, makeSound() is meant to be abstract. However, Swift doesn’t support abstract methods inside classes. So, that behaviour can be somewhat mimicked by throwing an error when the method is called from an Animal instance. But, this is not the right way to achieve abstract methods. Swift provides Protocols for the purpose. We will see about Protocols later. Note that, the super.init call of Dog class is done after initializing Dog’s properties only (Unlike Java, where super() is called first most of the times). This is done so to ensure that the Dog’s properties are initialized first, so that any overridden method can access them without any issues. As you can see in the example, a variable of type Animal (Base class) is allowed to hold either instance of itself or an instance of its subclass. Even reassigning to subclass reference is accepted. However, the vice versa is not true because a subclass may define additional properties and methods. Moreover, when a base class variable is assigned with subclass reference, only methods and properties available commonly in both subclass and superclass can be accessed. To access other members, downcasting must be done (It will be discussed in detail in Type Casting Subtopic).

Types of Inheritance supported by Swift

Swift supports the following Inheritance Types:

Like Java, Swift too doesn’t support Multiple Inheritance due to the Diamond Problem, which causes ambiguity in which overridden method should be called from grandchild class instances. Instead, it can be mimicked by use of Protocol Conformance.

Overriding Methods

Methods of super class can be overridden by subclass to provide new implementations to the methods. The only restriction is that the overriding methods must have an equal or lower restrictive access level than the super class method. Else, the compiler will throw an error. For example, an internal method can be overridden as public or open but not as private or fileprivate method. An instance of Method overriding is shown above in Example 7, where the makeSound() Method is overridden in the subclass.

Overriding Property Getters and Setters and Property Observers

You can provide a custom getter (and setter, if appropriate) to override any inherited property, regardless of whether the inherited property is implemented as a stored or computed property at source. The stored or computed nature of an inherited property isn’t known by a subclass—it only knows that the inherited property has a certain name and type. You must always state both the name and the type of the property you are overriding, to enable the compiler to check that your override matches a superclass property with the same name and type. You can present an inherited read-only property as a read-write property by providing both a getter and a setter in your subclass property override. You can’t, however, present an inherited read-write property as a read-only property.

NOTE : If you provide a setter as part of a property override, you must also provide a getter for that override. If you don’t want to modify the inherited property’s value within the overriding getter, you can simply pass through the inherited value by returning super.someProperty from the getter, where someProperty is the name of the property you are overriding.

In the Example 7 above, the description Property is overridden in the Dog class. Even Property Observers like didSet or willSet can be overridden in the subclass. But, both custom setters and property observers can’t be overridden simultaneously.

Static Methods vs Class Methods

Swift allows to define class func methods, which are available at Class Level, rather than at Instance level. It is available exclusively for Classes in Swift and and cannot be used by Structures and Enumerations. So, what is the difference between static func and class func ? class func uses Dynamic Dispatch and hence supports Overriding. However, static func can’t be overridden in subclass. static func is equivalent to final class func as the final keyword prevents the method from getting overridden.

Example 8:

class A
{
    static func nonOverridableFunc() // Equivalent of final class func
    {
        print("Non-overridable static function")
    }
    
    class func overridableFunc()
    {
        print("Overridable class function (Now in class A)")
    }
    
    final class func nonOverridableFunc2()
    {
        print("Non-overridable class function mimicking static function")
    }
}

class B: A
{
    override class func overridableFunc()
    {
        print("Overridable class function (Now in class B)")
    }
}

A.nonOverridableFunc()
A.overridableFunc()
B.overridableFunc()
A.nonOverridableFunc2()

Output 8:

Non-overridable static function
Overridable class function (Now in class A)
Overridable class function (Now in class B)
Non-overridable class function mimicking static function

Designated and Convenience Initializers

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

Classes tend to have very few designated initializers, and it’s quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.

Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in Automatic Initializer Inheritance below.

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type. Convenience Initializers are denoted by attaching the keyword convenience before init.

You don’t have to provide convenience initializers if your class doesn’t require them. Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.

Example 9:

class Message
{
    var content: String
    var sender: String
    var receiver: String
    
    init(content: String, sender: String, receiver: String)
    {
        self.content = content
        self.sender = sender
        self.receiver = receiver
    }
    
    convenience init(content: String)
    {
        self.init(content: content, sender: "Anonymous", receiver: "Anonymous")
    }
    
    func sendMessage()
    {
        print("\(content) sent by \(sender)")
        print("\(content) received by \(receiver)")
    }
}

Message(content: "Hello !", sender: "Kris", receiver: "Shiv").sendMessage()
Message(content: "Stay safe !").sendMessage()

Output 9:

Hello ! sent by Kris
Hello ! received by Shiv
Stay safe ! sent by Anonymous
Stay safe ! received by Anonymous

Two-phase Initialization

Class initialization in Swift is a two-phase process. In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.

The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they’re initialized, and prevents property values from being set to a different value by another initializer unexpectedly.

Swift’s compiler performs four helpful safety-checks to make sure that two-phase initialization is completed without error (Taken from Official Documentation):

Safety check 1

A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer. As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all of its own properties are initialized before it hands off up the chain.

Safety check 2

A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.

Safety check 3

A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.

Safety check 4

An initializer can’t call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete. The class instance isn’t fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.

Here’s how two-phase initialization plays out, based on the four safety checks above:

Phase 1
Phase 2

Automatic Initializer Inheritance

Subclasses don’t inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met. In practice, this means that you don’t need to write initializer overrides in many common scenarios, and can inherit your superclass initializers with minimal effort whenever it’s safe to do so.

Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:

Rule 1

If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

These rules apply even if your subclass adds further convenience initializers.

Required Initializers

Swift allows to write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer. You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain. You don’t write the override modifier when overriding a required designated initializer.

NOTE : You don’t have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initializer.

Example 10:

class Person
{
    var name: String
    var age: Int
    
    required init(name: String, age: Int)
    {
        self.name = name
        self.age = age
    }
}

class Student: Person
{
    var rollNumber: Int
    
    required init(name: String, age: Int)
    {
        self.rollNumber = Int.random(in: 1...100)
        super.init(name: name, age: age)
    }
}

var student = Student(name: "Shri", age: 21)
print(student.rollNumber)

Output 10:

71

Access Levels’ impact on Inheritance

The following Table shows the impact of Access Levels on Inherited and Overridden Members:

Access Level of Members in Super Class Can Members be Inherited Allowed Access Level(s) of Overridden Non-Stored Properties and Methods in Subclass
private NO Not Applicable
fileprivate YES within same source file. NO in other source files. fileprivate, internal, public, open within same module. Cannot be overridden in other modules.
internal YES within any source file in the same module. NO in other modules. internal, public, open within same module. Cannot be overridden in other modules.
public YES within same module or any module that imports the defining module. internal, public, open within same module. Cannot be overridden in other modules.
open YES within same module or any module that imports the defining module. internal, public, open

The following table shows the impact of Access Levels on Inheritance of classes:

Access Level of Super Class Allowed Access Level(s) of Subclass
private private, fileprivate within same module. Cannot be Inherited in other modules.
fileprivate private, fileprivate within same module. Cannot be Inherited in other modules.
internal private, fileprivate, internal within same module. Cannot be Inherited in other modules.
public private, fileprivate, internal, public within same module. Cannot be Inherited in other modules.
open private, fileprivate, internal, public, open in any module.

Impact of Access Levels on Object creation using var or let

The specifying class’ access level also affects the object creation of the class using var or let. The following table shows the access levels which objects must have:

Access Level of Class Allowed Access Level(s) of Object
private private, fileprivate
fileprivate private, fileprivate
internal private, fileprivate, internal
public private, fileprivate, internal, public
open private, fileprivate, internal, public

Preventing Overriding and Inheritance

Subscripts

Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. You use subscripts to set and retrieve values by index without needing separate methods for setting and retrieval. For example, you access elements in an Array instance as someArray[index] and elements in a Dictionary instance as someDictionary[key]. You can define multiple subscripts for a single type, and the appropriate subscript overload to use is selected based on the type of index value you pass to the subscript. Subscripts aren’t limited to a single dimension, and you can define subscripts with multiple input parameters to suit your custom type’s needs.

Syntax :

subscript(index: Int) -> Int {
    get {
        // Return an appropriate subscript value here.
    }
    set(newValue) {
        // Perform a suitable setting action here.
    }
}

Example 11:

class MultiplicationTables
{
    var multiplier: Int
    
    init(of multiplier: Int)
    {
        self.multiplier = multiplier
    }
    
    subscript(index: Int) -> Int //Read-only subscript
    {
        index * multiplier
    }
}
let elevenTables = MultiplicationTables(of: 11)
for x in 10...20
{
    print("\(x) * 11 = \(elevenTables[x])") // Using Subscript syntax [] to get value
}

Output 11:

10 * 11 = 110
11 * 11 = 121
12 * 11 = 132
13 * 11 = 143
14 * 11 = 154
15 * 11 = 165
16 * 11 = 176
17 * 11 = 187
18 * 11 = 198
19 * 11 = 209
20 * 11 = 220

Subscripts can take any number of input parameters, and these input parameters can be of any type. Subscripts can also return a value of any type.

Like functions, subscripts can take a varying number of parameters and provide default values for their parameters, as discussed in Variadic Parameters and Default Parameter Values. However, unlike functions, subscripts can’t use inout parameters.

A class or structure can provide as many subscript implementations as it needs, and the appropriate subscript to be used will be inferred based on the types of the value or values that are contained within the subscript brackets at the point that the subscript is used. This definition of multiple subscripts is known as Subscript Overloading.

While it’s most common for a subscript to take a single parameter, you can also define a subscript with multiple parameters if it’s appropriate for your type.

Example 12:

class RandomMatrix
{
    var rows: Int, columns: Int, values: [Int]
    
    init(rows: Int, columns: Int)
    {
        self.rows = rows
        self.columns = columns
        values = Array(repeating: 0, count: rows * columns)
        for i in 0..<(rows * columns)
        {
            values[i] = Int.random(in: 1...100)
        }
    }
    
    func indexIsValid(row: Int, column: Int) -> Bool
    {
        row >= 0 && row < rows && column >= 0 && column < columns
    }
    
    subscript(row: Int, column: Int) -> Int
    {
        get
        {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return values[(row * columns) + column]
        }
        set
        {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            values[(row * columns) + column] = newValue
        }
    }
}

let squareMatrix = RandomMatrix(rows: 4, columns: 4)
for i in 0..<4
{
    for j in 0..<4
    {
        print(squareMatrix[i, j],terminator: " ") // Using multiple parameter subscript
    }
    print()
}

Output 12:

37 71 99 67 
25 44 24 53 
36 53 25 35 
91 59 61 75 
Type Subscripts

The subscripts which we have seen so far are Instance subscripts. We can also define Type Subscripts of Classes, Structures and Enumerations using the static keyword or class keyword (For classes only).

Example 13:

class MultiplicationTables
{
    var multiplier: Int
    
    private init(of multiplier: Int)
    {
        self.multiplier = multiplier
    }
    
    subscript(index: Int) -> Int //Read-only subscript
    {
        index * multiplier
    }
    
    static subscript(multiplier: Int) -> MultiplicationTables //Read-only static subscript
    {
        MultiplicationTables(of: multiplier)
    }
}

print("Tables of 9 and 10: ")
for x in 9...10
{
    print("\(x) Tables")
    for y in 1...10
    {
        print("\(x) * \(y) = \(MultiplicationTables[x][y])")
    }
    print()
}

Output 13:

Tables of 9 and 10: 
9 Tables
9 * 1 = 9
9 * 2 = 18
9 * 3 = 27
9 * 4 = 36
9 * 5 = 45
9 * 6 = 54
9 * 7 = 63
9 * 8 = 72
9 * 9 = 81
9 * 10 = 90

10 Tables
10 * 1 = 10
10 * 2 = 20
10 * 3 = 30
10 * 4 = 40
10 * 5 = 50
10 * 6 = 60
10 * 7 = 70
10 * 8 = 80
10 * 9 = 90
10 * 10 = 100

Now that we have seen about Classes in Swift, let’s move on to see about Enumerations in Swift.

← Back to Index
← Structures Enumerations →