Class and Struct in Swift - Similarities and Differences

Class and Struct - Similarities and Differences

Introduction

Both classes and structs are the main building blocks of your program codes. They help us organize and manage our code into blocks which are more intuitively and more easy to use. In Objective-C, classes and structures are very different. However, this isn't true for Swift. For example, you can define properties and methods to your classes and structs in Swift. Besides that, structs in Swift also conform to protocols. So, you may be curious: What are the differences between classes and struct? In which situations will we use classes and struct? These are the fundamental language questions commonly asked in iOS interviews. Today, we’ll clarify how classes and structs behave differently from each other, and explain about when to use a class and when to use a struct.

Similarities

Classes and Structs in Swift have many similar things such as:

  1. Allow to define properties to store values (//1), define methods to provide functionality (//2), define subscripts to provide access to their values using subscript syntax.
  2. Allow to define initializers to set up their initial state. (//3)
  3. Allow to to expand their functionality beyond a default implementation. //4
  4. Allow to conform to protocols to provide standard functionality of a certain kind. //4
struct Bird{
    var code: Int //1
    var name: String //1
    
    init(code: Int, name: String) { //3
        self.code = code
        self.name = name
    }
    
    func introduce(){ //2
        print("I am \(name).")
    }
}

protocol Flyable{
    func fly()
}

extension Bird: Flyable { //4
    func fly() {
        print("I can fly.”)
    }
}

In this blog, I won’t analysis details about the similar things between them because most developer know and use everyday. I will focus on the differences between them.

Differences

Classes have additional capabilities that structures do not:

Inheritance

Classes support inheritance, whereas structs don’t. Inheritance is an indispensable feature in OOP. It enables one class to inherit the characteristics of their parent class. The following codes will illustrates that thing.

class Vehicle{
    var manufacturer: String?
    let passengerCapacity: Int
    
    init(passengerCapacity: Int) {
        self.passengerCapacity = passengerCapacity
    }
}

class Car: Vehicle {
    var fuelType: String?
}

let car = Car(passengerCapacity: 4)

In the above example, the Vehicle class is the parent or superclass of the Car class. This means that the Car class inherits all properties and behaviors of the Person class. The last line illustrates this. We initialize a Car instance by invoking the initializer defined in the Person class. Error occurs when a struct inherits from another.

Reference Types vs. Value Types

A value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function. Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead. In Swift, Structs are value types, while Classes are reference types. The following examples will illustrate those concept.

struct Location{
    var longitude: Double
    var latitude: Double
    
    init(longitude: Double, latitude: Double) {
        self.longitude = longitude
        self.latitude = latitude
    }
}

var location1 = Location(longitude: 1.23, latitude: 1.23)
var location2 = location1

location1.longitude = 4.56

print(location1.longitude) //4.56
print(location2.longitude) //1.23

We define a struct, Location, to encapsulate data to save longitude and latitude of a location. Then, we declare a variable called location1 and sets it to a Location instance initialized with the longitude and latitude. We declares a variable called location2 and sets it to the current value of location1. Because Location is a structure, a copy of the existing instance is made, and this new copy is assigned to location2. Even though location1 and location2 now have the same longitude and latitude, they are two completely different instances in the memory. After that, we change the longitude property of the location1 to 4.56 and print the longitude property of the location1 and location2. The longitude property of location1 shows that it has value changed to be 4.56, but the longitude property of the location2 still has the old value of 1.23.

Now, let’s repeat this example with Location class.

class Location{
    var longitude: Double
    var latitude: Double
    
    init(longitude: Double, latitude: Double) {
        self.longitude = longitude
        self.latitude = latitude
    }
}

var location1 = Location(longitude: 1.23, latitude: 1.23)
var location2 = location1

location1.longitude = 4.56

print(location1.longitude) //4.56
print(location2.longitude) //4.56

In the above example, we instantiate a Location instance, set its properties, assign location1 to location2, and update the longitude property of location1. Because classes are passed by reference, location1 and location2 actually both refer to the same Location instance in the memory. The longitude property of location2 changed with the longitude property of the location1.

Identity Operators

Because of the difference between value type and reference type, we should be careful when comparing between them. There are two concept, we need to concern: “identical to” and “equal to”. “identical to” (represented by three equals signs, or ===) does not mean the same thing as “equal to” (represented by two equals signs, or ==):

  1. “Identical to” means that two constants or variables of class type refer to exactly the same class instance.
class Car{}
let toyota = Car()
let lexus = toyota

toyota === lexus //true

let honda = Car()
honda === toyota //false
  1. “Equal to” means that two instances are considered “equal” or “equivalent” in value, for some appropriate meaning of “equal”, as defined by the type’s designer.
10 == 10 // true
"same string" == "same string"     // true
"one string" == "different string" // false

Deinitializer

Deinitializers enable an instance of a class to free up any resources it has assigned. 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 D {
    deinit {
        //Deallocated from the heap, tear down things here
        print("Deallocated from the heap")
    }
}

var d:D? = D()
d = nil //Deallocated from the heap

We define a class D and instantiate an D instance. Next, we set the optional d variable to nil, meaning “no D instance.” At the point that this happens, the d variable’s reference to the D instance is broken, and so it is deallocated in order to free up its memory. Just before this happens, its deinitializer is called automatically, and print to the scene. To understand which situation should you use that, you can read more about Deinitialization in The Swift Programming Language (Swift 4) document at link: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Deinitialization.html#//apple_ref/doc/uid/TP40014097-CH19-ID142

Choosing Between Classes and Structs

Struct instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.

We consider creating a struct when:

  1. Data structure is simple, having few properties.
  2. The encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
  3. The properties stored by the structure are themselves value types
  4. The struct does not need to inherit properties or behavior from another existing type.

Now, let’s see a struct that Apple defined:

public struct CGPoint {
    public var x: CGFloat
    public var y: CGFloat

    public init()
    public init(x: CGFloat, y: CGFloat)
}

The reason for define struct for CGPoint are :

  1. The data structure is simple.
  2. The properties are value type.
  3. It doesn’t need to inherit any classes.

Cautious In Swift, many basic data types likes: String, Array, and Dictionary are implemented as structs. This means them are copied when they are assigned to a new constant or variable, or when they are passed to a function or method. This behavior is different from Foundation: NSString, NSArray, and NSDictionary are implemented as classes, not structures. Strings, arrays, and dictionaries in Foundation are always assigned and passed around as a reference to an existing instance, rather than as a copy.

Conclusion

That’s all about classes and structs. I hope this tutorial has shown you how differences between classes and structs and when to use them. If you have any questions, feel free to leave them in the comments below, or email at [email protected].