DEV Community

Neeraj Gupta for DSC CIET

Posted on

Structs and Classes in Swift

Brief Introduction About Swift

Swift is a language developed by Apple which is made using modern approach, include safety features and software design patterns. As the name suggests swift is fast, safe and easy to use. It is basically made a replacement for c-based family(C, C++ and Objective-C). It can be used to make apps and can be also used for cloud services and it is among the fastest growing languages.

What are Structures and Classes

Structures and classes are general-purpose, flexible constructs that become the building blocks of your program’s code. You define properties and methods to add functionality to your structures and classes using the same syntax you use to define constants, variables, and functions.

Unlike other programming languages, Swift doesn’t require you to create separate interface and implementation files for custom structures and classes. In Swift, you define a structure or class in a single file, and the external interface to that class or structure is automatically made available for other code to use.

What we can do in Structures

So, we can define :

  1. Variables
  2. Methods
  3. Subscripts are shortcuts for accessing the member elements of a collection, list, or sequence. Like we say there is array and as array is a collection of similar types ["Neeraj", "Gupta", "programmer"], so we can access it using name_of_array[index_number]. Accessing like this is called subscripting.
  4. Initializers or what we call constructors in other languages using init(){} here we can define some values which we always need to be intialized itself when making an instance of structure or class.
  5. Conform to protocols to provide standard functionality of a certain kind. At time protocols in out of scope of this post and we will discussing it later but for now consider it as a certain set of rules that the adopting struct or class has to follow.

Additional features we get in classes over structures

  1. Inheritance enables one class to inherit the characteristics of another.
  2. Type casting enables you to check and interpret the type of a class instance at runtime.
  3. Deinitializers enable an instance of a class to free up any resources it has assigned.
  4. Reference counting allows more than one reference to a class instance.

Examples

Structures

We define structures using struct keyword.

//General Syntax
struct structure_name {
 body
}
Enter fullscreen mode Exit fullscreen mode
struct Person {
// we have to either initialize values of variables defined in structure or class or make it optional
    var firstName : String = "Neeraj"
    var lastName : String = "Gupta"

    func move(_ name : String) -> String {
        return "Person is now moving"
    }
//Above we defined a method with in/out parameter and return type string

}

let personOne = Person() // we made an instance of Person Structure
print(personOne.firstName) // we can access the variables or methods or any collection in structure or class using period/dot(.)
print(personOne.lastName) //Gupta
print(personOne.move(personOne.firstName)) // Person is now moving

var personTwo = personOne // here we made a copy of first instance and in structures copy made is not a reference to the structure from which it is copied means they will not point to same memory block in memory which you will understand soon

print(personTwo.firstName) //Neeraj
print(personTwo.lastName) // Gupta
print(personTwo.move(personTwo.firstName)) // Person is now moving

personTwo.firstName = "Deepak" // as firstName is a variable so we can change it's value. If it had been constant we wouldn't had been able to change it's value and xcode would had thrown error saying "Cannot assign property value as firstName is let constant"

print("First Name of Person One is \(personOne.firstName)") // First Name of Person One is Neeraj
print("First Name of Person Two is \(personTwo.firstName)") //First Name of Person One is Deepak
Enter fullscreen mode Exit fullscreen mode

Here is a clean code snippet for you to try and tinker

struct Person {
    var firstName : String = "Neeraj"
    var middleName : String?
    var lastName : String = "Gupta"

    func move(_ name : String) -> String {
        return "Person is now moving"
    }

}

let personOne = Person()
print(personOne.firstName)
print(personOne.lastName)
print(personOne.move(personOne.firstName))

var personTwo = personOne

print(personTwo.firstName)
print(personTwo.lastName)
print(personTwo.move(personTwo.firstName))

personTwo.firstName = "Deepak"

print("First Name of Person One is \(personOne.firstName)")
print("First Name of Person Two is \(personTwo.firstName)")
Enter fullscreen mode Exit fullscreen mode

Main point to note here was that in structures we make a seperate copy and they occupy seperate memory block in memory and Apple recommends to use structures and use classes where they are actually needed.

Also, we have one more important point to cover in strucutres and we will see it with another code snippet

Strcutres are immutable by default so we cannot change value inside struct normally.

struct Person2 {
    var firstName : String = "Neeraj"
    var middleName : String?
    var lastName : String = "Gupta"

    //first we have to use `mutating` keyword to tell swift that we want to destroy this copy of structure and make a new copy with particular value. So in structures when changing a value swift will destroy old copy and make a new one
    mutating func move(_ name : String) -> String {
        self.firstName = name // inside functions or closures we have to provide self keyword to tell swift that this variable belongs to parent of this method
        return "Person is now moving"
    }

}
var personThree = Person2();
print(personThree.firstName) // Neeraj
personThree.move("Deepak"); // Here in this method value of firstName will change
print(personThree.firstName) // Deepak
Enter fullscreen mode Exit fullscreen mode

Clean Code Snippet ( Try tinkering with it)

struct Person2 {
    var firstName : String = "Neeraj"
    var middleName : String?
    var lastName : String = "Gupta"

    mutating func move(_ name : String) -> String {
        self.firstName = name
        return "Person is now moving"
    }

}


var personThree = Person2();
print(personThree.firstName)
personThree.move("Deepak");
print(personThree.firstName)
Enter fullscreen mode Exit fullscreen mode

Classes

Classes are almost similar but with some additional powers and also as we saw structures make a seperate copy but in classes copy made is reference type which means copy made will point to same memory block. This idea is derived from C langauge family. If you are familiar with C you may have seen yourself using pointers there the one which we used to define with * to tell that this is pointer and it will point to same memory location.

This is how we define a class using class keyword. Everything is same as structure we just have to replace struct with class keyword.

class Person {
    var firstName : String = String()
    var lastName : String = String()
}

let candidate = Person()
candidate.firstName = "Neeraj"
candidate.lastName = "Gupta"
print("Candidate's First Name \(candidate.firstName)")
print("Candidate's Last Name \(candidate.lastName)")
Enter fullscreen mode Exit fullscreen mode

Let's see how classes are different from strcutres

class Person {
    var firstName : String = "Neeraj"
    var lastName : String = "Gupta"

    func move() -> String {
        return "Person is now moving"
    }
}
// hope you now understand the above code snippet as it is same as structure but the only difference is of class keyword

//Let's focus on how they are seperate from structure

let personOne = Person() // we made an instance of class
print(personOne.firstName) // Neeraj
print(personOne.lastName) // Gupta
print(personOne.move()) // Person is now moving

var personTwo = personOne // Here we made a copy of class which are pointing to same memory means change in one instance is gonna effect other instance

print(personTwo.firstName) // Neeraj
print(personTwo.lastName) // Gupta
print(personTwo.move()) // Person is now moving

personTwo.firstName = "Deepak" // Changed value of firstName

print("First Name of Person One is \(personOne.firstName)") // First Name of Person One is Deepak
print("First Name of Person Two is \(personTwo.firstName)") // First Name of Person One is Deepak
Enter fullscreen mode Exit fullscreen mode

Clean Code Snippet for you to try

class Person {
    var firstName : String = "Neeraj"
    var middleName : String?
    var lastName : String = "Gupta"

    func move() -> String {
        return "Person is now moving"
    }
}

let personOne = Person()
print(personOne.firstName)
print(personOne.lastName)
print(personOne.move())

var personTwo = personOne

print(personTwo.firstName)
print(personTwo.lastName)
print(personTwo.move())

personTwo.firstName = "Deepak"


print("First Name of Person One is \(personOne.firstName)")
print("First Name of Person Two is \(personTwo.firstName)")
Enter fullscreen mode Exit fullscreen mode

**Interesting point to note is Classes are mutable as they point to a memory location and not make a new copy every time so we can change value inside class

This was the main difference between structures and classes that structures are not reference types but classes are

Initializers

Initializers are used to provide some values prior to making an instance of class or structure but as we used above we can provide default values to classes or structures. We have one advantage of initializers that we can define multiple initializers and use any while making instance. Let's see in an example below

Also we saw above that swift automatically creates a initializer for structures

class Color {
    var red, blue, green: Double
    init(red: Double, blue: Double, green: Double) {
        self.red = red
        self.blue = blue
        self.green = green
    }

    init(white: Double) {
        self.red = white
        self.blue = white
        self.green = white
    }
}

let magenta = Color(red: 1.0, blue: 0.0, green: 1.0)

print(magenta.red)//1.0
print(magenta.blue) //0.0
print(magenta.green) //1.0
let white = Color(white: 0.5)

print(white.red) //0.5
print(white.blue) //0.5
print(white.green) //0.5

Enter fullscreen mode Exit fullscreen mode

Above we defined multiple intializers and depending on number of parameters we pass we can access different initializers.

**A point to note here that swift do not allow reinitialization in classes and structures. Here is an example

struct Person {
    let name : String = "Neeraj"
    init() {
        name = "Deepak" // Here swift will give error that name should be initialized once. So, either intialize it once or make the property variable/mutable by replacing 'let' with 'var'
    }
}

let user = Person()
user.name
Enter fullscreen mode Exit fullscreen mode

So, this was all about designated initializer. We have two more types one is convenience init and failable init which was introduced in swift 5.

Deinitializers

A deinitializer is called immediately before a class instance is deallocated. You write deinitializers with the deinit keyword, similar to how initializers are written with the init keyword. Deinitializers are only available on class types.

class Bank {
    static var coinsInBank = 10_000
    static func distribute(coins numberOfCoinsRequested: Int) -> Int {
        let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
        coinsInBank -= numberOfCoinsToVend
        return numberOfCoinsToVend
    }
    static func receive(coins: Int) {
        coinsInBank += coins
    }
}

class Player {
    var coinsInPurse: Int
    init(coins: Int) {
        coinsInPurse = Bank.distribute(coins: coins)
    }
    func win(coins: Int) {
        coinsInPurse += Bank.distribute(coins: coins)
    }
    deinit {
        Bank.receive(coins: coinsInPurse)
    }
}

var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// Prints "A new player has joined the game with 100 coins"
print("There are now \(Bank.coinsInBank) coins left in the bank")
// Prints "There are now 9900 coins left in the bank"

playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// Prints "PlayerOne won 2000 coins & now has 2100 coins"
print("The bank now only has \(Bank.coinsInBank) coins left")
// Prints "The bank now only has 7900 coins left"

playerOne = nil
print("PlayerOne has left the game")
// Prints "PlayerOne has left the game"
print("The bank now has \(Bank.coinsInBank) coins")
// Prints "The bank now has 10000 coins"
Enter fullscreen mode Exit fullscreen mode

Discussion (0)