
Swift: Struct vs Class vs Actor, Value vs Reference Types, Stack vs Heap | Swift Concurrency #8


struct CustomStruct {
var title: String
// Default Initializer from Swift
}
struct CustomStruct2 {
var title: String
func makeNewStruct(_ title: String) -> CustomStruct2 {
return CustomStruct2(title: title)
}
mutating func updateTitle(_ title: String) {
self.title = title
}
}
struct CustomStruct3 {
private var title: String
init(title: String) {
self.title = title
}
func getTitle() -> String {
return title
}
mutating func setTitle(_ title: String) {
self.title = title
}
}
class CustomClass {
var title: String
init(title: String) {
self.title = title
}
func updateTitle(_ title: String) {
self.title = title
}
}
class CustomClass2 {
var title: String
init(title: String) {
self.title = title
print("\(title) is initiated")
}
deinit {
print("\(title) is deinitiated")
}
}
actor CustomActor {
var title: String
// to be updated
init(title: String) {
self.title = title
}
func updateTitle(_ title: String) {
self.title = title
}
}
extension StructClassActorBootCamp {
private func runTest() {
actorTest()
}
private func structTest1() {
let objectA = CustomStruct(title: "Starting Title")
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 Title
// ObjectB : Second Title -> ObjectA <-> ObjectB not referencing themselves. (: Value Type)
}
private func classTest1() {
let objectA = CustomClass(title: "Starting Title")
print("ObjectA : \(objectA.title)")
print("Pass the REFERENCE of objectA to objectB")
let objectB = objectA
print("ObjectB : \(objectB.title)")
// ObjectA : Starting Title
// ObjectB : Starting Title
objectB.title = "Second Title"
// let title -> var title. complier changes it: not 'let' objectB.
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
}
}
mutating 키워드를 통해 self를 바꾼다는 것을 명시해야 함mutating 키워드가 없음. 즉 그대로 변경 가능하다는 뜻. 특정 텍스트 인스턴스를
let, 상수로 받은 뒤 인스턴스의 변수 로 선언한 저장 프로퍼티 값을 변경 가능한 까닭:let이라는 상수는 곧 해당 클래스를 가리키고 있는 포인터가 변하지 않는다는 것을 의미할 뿐, 내부의 값은 (변수로 선언되었다면) 주소값을 통해 접근, 변경 가능하기 때문
extension StructClassActorBootCamp {
private func structTest2() {
var struct1 = CustomStruct(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.title : Title1
// struct2.title : Title2
struct2 = struct2.makeNewStruct("New Title")
print("Struct2 : \(struct2.title)")
// struct2.title : New Title
// Totally new Structure
struct2.updateTitle("Title3")
// Mutating function
print("Struct2 : \(struct2.title)")
// struct2.title : Title3
}
private func structTest3() {
var struct1 = CustomStruct3(title: "Title1")
var title = struct1.getTitle()
print(title)
// Title1
struct1.setTitle("Title2")
title = struct1.getTitle()
print(title)
// Title2
// get, set method to handle data inside struct
}
}
extension StructClassActorBootCamp {
private func classTest2() {
let class1 = CustomClass(title: "Title1")
print("Class1 : \(class1.title)")
// Class1 : Title1
class1.updateTitle("Title2")
print("Class1 : \(class1.title)")
// Class1 : Title2
// Does not have to set "mutating" keyward inside Class
}
}
mutating 키워드를 통해 변수로 선언한 저장 프로퍼티 값을 바꾸는 방법 (2). 새로운 구조체를 할당, 변수로 선언된 특정 값을 덮어 씌우는 방법
mutating이 곧inout으로 바꾸는 것과 같은 맥락인 것 같다!
extension StructClassActorBootCamp {
private func actorTest() {
Task {
// Need to get async -> Call them inside 'Task' block
var actor1 = CustomActor(title: "Actor1")
await print("Actor1 title : \(actor1.title)")
// Actor1 title : Actor1
// actor1.title = "Actor2" -> Ban
// Actor-isolated property 'title' can not be mutated from a non-isolated context
await actor1.updateTitle("Actor2")
await print("Actor1 title : \(actor1.title)")
// Actor1 title : Actor2
}
}
}
actor 내부의 프로퍼티는 actor-isolated 프로퍼티이기 때문에 일반적인 방법으로는 변경 불가능 → actor 클래스 내부에서 선언한 함수를 통해 self 접근, 특정 프로퍼티 값을 변경 가능 → await를 통해 비동기 동작이 끝났는지 확인할 수 있고 이를 Task 내부에서 실행mutated)ObservableObject 등 참조 방식을 통해 동일한 값의 현재 상태 감지 보장(뷰 모델, 매니저 클래스 등)await를 통해 동기화 보장 가능URLSession.shared 등) 적절. 다양한 곳에서 접근 가능한 클래스, 동기화 자동으로 보장 (await)구조체는 클래스와 달리 상속이 불가능하다. 하지만 프로토콜 + 익스텐션 활용을 통해
deinit을 통해 직접 확인 가능), 효율적 메모리 사용이 가능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 initiated")
}
deinit {
print("\(title) is deinitiated")
}
}
class StrongClass2 {
var title: String
var strongClass1: StrongClass1?
init(title: String, strongClass1: StrongClass1? = nil) {
self.title = title
self.strongClass1 = strongClass1
print("\(title) is initiated")
}
deinit {
print("\(title) is deinitiated")
}
}
class WeakClass {
var title: String
weak var strongClass1: StrongClass1?
init(title: String, strongClass1: StrongClass1? = nil) {
self.title = title
self.strongClass1 = strongClass1
print("\(title) is initiated")
}
deinit {
print("\(title) is deinitiated")
}
}
extension StructClassActorBootCamp {
private func classTest3() {
var class1: CustomClass2?
class1 = CustomClass2(title: "New Class")
// New Class is initiated
// Reference Count : 1
class1 = nil
// Reference Count : 0 -> Deinit. Deallocated from Memory
// New Class is deinitiated
}
private func classTest4() {
var class1:StrongClass1? = StrongClass1(title: "Strong Class1")
var class2:StrongClass2? = StrongClass2(title: "Strong Class2")
// Strong Class1 is initiated
// Strong Class2 is initiated
class1?.strongClass2 = class2
class2?.strongClass1 = class1
class1?.strongClass2 = nil
class2?.strongClass1 = nil
class1 = nil
class2 = nil
// Strong Class1 is deinitiated
// Strong Class2 is deinitiated
class1 = StrongClass1(title: "Strong Class1")
class2 = StrongClass2(title: "Strong Class2")
// Strong Class1 is initiated
// Strong Class2 is initiated
class1?.strongClass2 = class2
class2?.strongClass1 = class1
class1 = nil
class2 = nil
// Still Strong reference cycle retained -> Memory leaking
}
private func classTest5() {
var class1:StrongClass1? = StrongClass1(title: "Strong Class1")
var class2:WeakClass? = WeakClass(title: "Weak Class")
// Strong Class1 is initiated
// Weak Class is initiated
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
}
}
nil이 될 참조'임을 미리 알려주기 때문에 메모리 누수에 대한 걱정을 덜 수 있음nil 할당을 통한 카운팅을 줄이는 방법을 통해 ARC를 조정 가능, 강한 참조 및 약한 참조를 함께 씀으로써 고리를 끊을 수 있다는 것 역시 주의@StateObject로 뷰에서 관찰하고 있는 ObservableObject는 뷰 리렌더링 도중에도 값을 계속해서 유지. @ObjservedObject로 관찰하고 있다면 Deinit됨actor 클래스를 통해 동기화 보장 가능import SwiftUI
actor StructClassActorBootCampDataManager {
init() {
print("StructClassActorBootCampDataManager Init")
}
deinit {
print("StructClassActorBootCampDataManager Deinit")
}
func getDataFromDatabase() {
}
}
class StructClassActorBootCampViewModel: ObservableObject {
@Published var title: String = ""
let dataManager: StructClassActorBootCampDataManager
init(dataManager: StructClassActorBootCampDataManager) {
self.dataManager = dataManager
print("StructClassActorBootCampViewModel Init")
}
deinit {
print("StructClassActorBootCampViewModel Deinit")
}
}
struct StructClassActorBootCamp: View {
@StateObject private var viewModel = StructClassActorBootCampViewModel(dataManager: StructClassActorBootCampDataManager())
// -> 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 {
Text(viewModel.title)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
.background(isActive ? .red : .blue)
.onAppear {
runTest()
}
}
}
struct StructClassActorBootCampHomeView: View {
@State private var isActive: Bool = false
var body: some View {
StructClassActorBootCamp(isActive: isActive)
.onTapGesture {
isActive.toggle()
}
}
}
HomeView의 자식 뷰인 StructClassActorBootCamp 뷰는 이니셜라이저에 필요한 파라미터 isActive 값이 바뀜에 따라 계속해서 재생성 → 뷰 리렌더링StructClassActorBootCampViewModel ObservableObject는 @StateObject로 관찰되고 있기 때문에 뷰 렌더링이 일어난다 하더라도 초기 Init 이후 Deinit되지 않고 있음 → 기존에 참조한 값을 그대로 유지할 수 있음