[Swift] Struct, Class, Actor

ganeshrajugalla
8 min readSep 3, 2023

Struct, Class, Actor

Value VS Reference

Value Type:

  1. Struct, Enum, String, Int, etc.
  2. Stored in Stack
  3. Faster than reference types
  4. Thread safe
  5. When you assign or pass value type a new copy of data is created.

Reference type:

  1. Class, Function, Actors
  2. Stored in Heap
  3. Slower than value types. However, synchronization is guaranteed (if the value of the reference type is changed at a specific reference pointer, synchronization is guaranteed at other reference pointers as well)
  4. Not Thread safe (Except Actor)
  5. When you assign or pass reference type a new reference to the original instance will be created (pointer).

Stack:

  1. Stores Value Types
  2. variables allocated on the stack are stored directly in the memory, and access to this memory is very fast
  3. Each thread has its own stack

Heap:

  1. Stores Reference types
  2. Shared across threads

Struct, Class, Actor related code

Struct

import Foundation
import SwiftUI

struct CustomStrut{
var title:String
// Default Initializer from Swift
}

struct CustomStruct2{
var title:String

func makeNewStruct(_ title:String) -> CustomStruct2{
return CustomStruct2(title: title)
}

mutating func updateTitle(_ newTitle:String){
self.title = newTitle
}
}

struct CustomStruct3{
private(set) var title:String
// private(set) means set only property within the struct

init(title: String) {
self.title = title
}

func getTitle() -> String{
return title
}

mutating func setTile(_ title: String){
self.title = title
}
}

extension StructClassActor{

func customStructTest1(){
print("customStructTest1")
let objectA = CustomStrut(title: "Starting Tile!")
print("objectA: \(objectA.title)")
print("Pass the value of objectA to objectB")
var objectB = objectA
print("objectB: \(objectB.title)")
// ObjectA : Starting Title
// ObjectB : Starting Title
objectB.title = "Second Title"
// let title -> var title, let objectB -> var objectB. complier changes it
print("objectB: Title Changed!")
print("objectA: \(objectA.title)")
print("objectB: \(objectB.title)")
// objectA: Starting Tile!
// ObjectB : Second Title -> ObjectA <-> ObjectB not referencing themselves. (: Value Type)

/*
customStructTest1
objectA: Starting Tile!
Pass the value of objectA to objectB
objectB: Starting Tile!
objectB: Title Changed!
objectA: Starting Tile!
objectB: Second Title
*/
}

func customStruct2Test(){
print("customStruct2Test")
var struct1 = CustomStrut(title: "Title1")
print("Struct1: \(struct1.title)")
struct1.title = "Title2"
print("Struct1: \(struct1.title)")

// Struct1: Title1
// Struct1: Title2

var struct2 = CustomStruct2(title: "Title1")
print("Struct2: \(struct2.title)")
struct2 = CustomStruct2(title: "Title2")
// totally new struct assigned
print("Struct2: \(struct2.title)")

// Struct2: Title1
// Struct2: Title2

var struct3 = CustomStruct2(title: "Title1")
print("Struct3: \(struct3.title)")
struct3 = struct3.makeNewStruct("Title2")
print("struct3: \(struct3.title)")
// Totally new Structure
//Struct3: Title1
//struct3: Title2

var struct4 = CustomStruct2(title: "Title1")
print("Struct4: \(struct4.title)")
struct4.updateTitle("Title2")
print("Struct4: \(struct4.title)")
//Struct4: Title1
//Struct4: Title2

/*
customStruct2Test
Struct1: Title1
Struct1: Title2
Struct2: Title1
Struct2: Title2
Struct3: Title1
struct3: Title2
Struct4: Title1
Struct4: Title2
*/

}

func customStruct3Test(){
var struct1 = CustomStruct3(title: "Title1")
print("Struct1: \(struct1.getTitle())")
struct1.setTile("Title2")
print("struct1 \(struct1.getTitle())")

// Struct1: Title1
// Struct1 Title2

// get, set method to handle data inside struct
}
}

  • Structure that copies the value itself → Creates a new structure when passing or assigning a value
  • When changing the value of a variable in a structure → you must specify that you are changing it mutatingthrough a keyword.self
  • How to return a structure containing new stored properties: (1). mutatingHow to change the value of a stored property declared as a variable using a keyword (2). How to assign a new structure and overwrite a specific value declared as a variable

mutatinginoutIt seems to be in the same context as changing this to !

The reason why you can change the value of a stored property declared as an instance variable after receiving a specific text instance letas a constant: A letconstant means that the pointer pointing to the class does not change, and the internal value is (if declared as a variable) ) Because it can be accessed and changed through the address value.

Class

import Foundation

// Class
class CustomClass{
var title: String

init(title: String) {
self.title = title
}

func updateTitle(_ title:String){
self.title = title
}
// Does not have to set "mutating" keyward inside Class
}

extension StructClassActor{
func customClassTest1(){
print("customClassTest1")
let objectA = CustomClass(title: "Starting Tile!")
print("objectA: \(objectA.title)")
print("Pass the REFERENCE of objectA to objectB")
let objectB = objectA
print("objectB: \(objectB.title)")
// objectA: Starting Tile!
// objectB: Starting Tile!
objectB.title = "Second Title"
// let title -> var title. complier changes it: not 'let' objectB. (let objectB remains same)
print("objectB: Title Changed!")
print("objectA: \(objectA.title)")
print("objectB: \(objectB.title)")
// objectA: Second Title!
// ObjectB : Second Title -> ObjectA === ObjectB referencing same place (: Reference Type)
print(objectA === objectB)
// true (=== operator to check the reference)

/*
customClassTest1
objectA: Starting Tile!
Pass the REFERENCE of objectA to objectB
objectB: Starting Tile!
objectB: Title Changed!
objectA: Second Title
objectB: Second Title
true
*/
}

func customClassTest2(){
print("customClassTest2")
let class1 = CustomClass(title: "Title1")
print("Class1: \(class1.title)")
class1.title = "Title2"
print("Class2: \(class1.title)")
//Class1: Title1
//Class2: Title2

let class2 = CustomClass(title: "Title1")
print("Class2: \(class2.title)")
class2.updateTitle("Title2")
print("Class2: \(class2.title)")

}
}

  • Class that copies address values ​​→ Creates a new ‘pointer’ when assigned to a new variable
  • When changing the value of a variable within a class → mutatingNo keyword. This means that it can be changed as is.

Actor

import Foundation
import SwiftUI

actor CustomActor{
var title:String
init(title: String) {
self.title = title
}

func updateTitle(_ title:String){
self.title = title
}
}

extension StructClassActor{
func customActorTests(){
Task{
// Need to get async -> Call them inside 'Task' block
print("customActorTests")
let actor1 = CustomActor(title: "Title1")
await print("Actor1: \(actor1.title)")
// Actor1: Title1

// actor1.title = "Title2" -> Ban
// Actor-isolated property 'title' can not be mutated from a non-isolated context
await actor1.updateTitle("Title2")
await print("Actor2: \(actor1.title)")
// Actor1 title : Actor2

/*
customActorTests
Actor1: Title1
Actor2: Title2
*/

}
}
}
  • actorBecause the internal properties actor-isolatedare properties, they cannot be changed in general ways → Access actorthrough functions declared inside the class self, and specific property values ​​can be changed → awaitThrough this, you can check whether the asynchronous operation has finished and Taskexecute it internally.

Struct VS Class VS Actor

Struct

  1. Based on VALUES
  2. Can be mutated
  3. Stored in the Stack

Class

  1. Based on REFERENCE (INSTANCES)
  2. Stored in Heap
  3. Inherit from other classes

Actor

  1. Based on REFERENCE (INSTANCES)
  2. Stored in Heap
  3. Thread safe

Unlike classes, structures cannot be inherited. However, through the use of protocol + extension

When to use structs, classes, actors

STRUCTS: Data Model, Views

CLASS: ViewModels

ACTOR: Shared “Managers” and “Data store”

ARC

  • Automatic Reference Counting: The number of times a class type is used is automatically counted and becomes the standard for determining memory allocation.
  • When ARC becomes 0, the class is automatically deleted from memory ( deinitcan be checked directly through ), enabling efficient memory use.

ARC related code

import Foundation
import SwiftUI


//MARK: - ARC

class StrongClass1{
var title:String
var strongClass2: StrongClass2?
var weakClass: WeakClass?
init(title: String, strongClass2: StrongClass2? = nil, weakClass: WeakClass? = nil) {
self.title = title
self.strongClass2 = strongClass2
self.weakClass = weakClass
print("\(title) is initialized")
}

deinit{
print("\(title) is deinitialized")
}

}

class StrongClass2{
var title:String
var strongClass1: StrongClass1?

init(title: String, strongClass1: StrongClass1? = nil) {
self.title = title
self.strongClass1 = strongClass1
print("\(title) is initialized")
}

deinit{
print("\(title) is deinitialized")
}

}

class WeakClass{
var title:String
weak var strongClass1: StrongClass1?

init(title: String, strongClass1: StrongClass1? = nil) {
self.title = title
self.strongClass1 = strongClass1
print("\(title) in initialized")
}

deinit{
print("\(title) is deinitialized")
}
}

extension StructClassActor{
func classTest4(){
var class1: StrongClass1? = StrongClass1(title: "Strong Class1")
var class2: StrongClass2? = StrongClass2(title: "Strong Class2")
//Strong Class1 is initialized
//Strong Class2 is initialized

class1?.strongClass2 = class2
class2?.strongClass1 = class1
class1?.strongClass2 = nil
class2?.strongClass1 = nil
class1 = nil
class2 = nil
//Strong Class1 is deinitialized
//Strong Class2 is deinitialized

class1 = StrongClass1(title: "Strong Class1")
class2 = StrongClass2(title: "Strong Class2")
//Strong Class1 is initialized
//Strong Class2 is initialized

class1?.strongClass2 = class2
class2?.strongClass1 = class1
class1 = nil
class2 = nil
//Strong Class1 is initialized
//Strong Class2 is initialized
// Still Strong reference cycle retained -> Memory leaking
}

func classTest5(){
var class1:StrongClass1? = StrongClass1(title: "Strong Class1")
var class2:WeakClass? = WeakClass(title: "Weak Class")
//Strong Class1 is initialized
//Weak Class in initialized
class1?.weakClass = class2
class2?.strongClass1 = class1
class1 = nil
class2 = nil
// Strong Class1 is deinitiated
// Weak Class is deinitiated
// strongClass1 inside class2(Weak Class instance) -> nil, then ARC -> 0, then class1 -> deinit
// weak reference -> make its non-having strong reference less than strong reference

}
}
  • Situations where a strong reference cycle may occur → A weak reference nillets you know in advance that this will eventually become a reference, so you can lessen the worry about memory leaks.
  • Even with strong references nil, ARC can be adjusted by reducing counting through appropriate allocation before use. It is also important to note that the chain can be broken by using strong and weak references together.

MVVM

  • View, view model, and model design patterns → Design patterns suitable for views that re-render into structures, view models, and models referenced through classes.
  • View: Continuously re-creating a new view struct according to the initializer → COW (Copy On Write), a method that is much more suitable than a class view implementation due to the very high speed of the struct itself.
  • View Model: The value @StateObjectobserved in the raw view ObservableObjectcontinues to maintain its value even during view re-rendering. If @ObjservedObjectyou are observing withDeinit
  • Model: If you are using the service class from multiple views and multiple points of view, actorYou can ensure synchronization throughout the class.

MVVM related code

import Foundation
import SwiftUI

actor StructClassActorDataManager{

init() {
print("StructClassActorDataManager Init")
}

deinit {
print("StructClassActorDataManager Deinit")
}

func getDataFromDatabase() {

}

}

class StructClassActorViewModel: ObservableObject{

@Published var title:String = "MVVM"
let manager: StructClassActorDataManager

init(manager: StructClassActorDataManager) {
self.manager = manager
print("StructClassActorViewModel Init")
}

deinit {
print("StructClassActorViewModel Deinit")
}

}

struct StructClassActorView: View{

@StateObject private var viewModel = StructClassActorViewModel(manager: StructClassActorDataManager())
// -> ObservableObject class: Init ...
// Even if this entire struct View would be re-rendered, its StateObject would be same
// @ObservedObject private var viewModel = StructClassActorBootCampViewModel()
// -> ObservableObject class: Init and Deinit ...
let isActive:Bool

init(isActive:Bool){
self.isActive = isActive
print("View Init, isActive : \(isActive)")
/*
View Init, isActive : true
View Init, isActive : false
View Init, isActive : true
View Init, isActive : false
... -> Whenever isActive toggled, this View has been initiated.
*/
}

var body: some View{
ZStack{
Color.black.ignoresSafeArea()
Text(viewModel.title)
.font(.headline)
.foregroundColor(.white)
.fontWeight(.bold)
.frame(maxWidth: .infinity,maxHeight: .infinity)
.background(isActive ? .pink : .purple)
}
}
}

struct StructClassActorHomeView: View{
@State private var isActive:Bool = false
var body: some View{
StructClassActorView(isActive: isActive)
.onTapGesture {
isActive.toggle()
}

//MARK: - @StateObject
/*
View Init, isActive : false
StructClassActorDataManager Init
StructClassActorViewModel Init
View Init, isActive : true
View Init, isActive : false
View Init, isActive : true
View Init, isActive : false
View Init, isActive : true
View Init, isActive : false

*/

//MARK: - @ObservedObject

/*
StructClassActorDataManager Init
StructClassActorViewModel Init
View Init, isActive : false
StructClassActorDataManager Init
StructClassActorViewModel Init
View Init, isActive : true
StructClassActorViewModel Deinit
StructClassActorDataManager Deinit
StructClassActorDataManager Init
StructClassActorViewModel Init
View Init, isActive : false
StructClassActorViewModel Deinit
StructClassActorDataManager Deinit
StructClassActorDataManager Init
StructClassActorViewModel Init
View Init, isActive : true
StructClassActorViewModel Deinit
StructClassActorDataManager Deinit
*/
}
}
  • HomeViewThe view, which is a child view, is continuously regenerated as StructClassActorthe parameter values ​​required for the initializer change → view re-renderingisActive
  • StructClassActorViewModel ObservableObjectBecause the view is subscribed @StateObjectto is observed, even if view rendering occurs, it does not occur InitAfter the initial stage → the existing reference value can be maintained as is.Deinit

--

--