1. 클래스와 구조체

  • 같은 데이터들을 가지고 있는 데이터 묶음을 정의해두는 틀
  • 동일한 종류의 값들 (이름, 나이, 성별 등)을 가지고 있는 객체들을 생성할 때, 공통으로 사용되는 부분을 미리 정의해놓는것
  • 즉, 객체를 생성하기 위한 설계도라고 할 수 있다
  • 사용자 정의 타입 : String, Int와 같은 타입을 사용자가 정의한다 → Optional 처리 가능 -프로퍼티, 메서드로 구성
  • 데이터 모델링 시 주로 사용
  • .(점문법)으로 내부의 멤버( 프로퍼티, 메소드)에 접근
//데이터 묶음을 정의 (사용자가 직접 type 생성)
class Vehicle {

    //프로퍼티(변수)
    var wheelCount: Int = 4
    var name: String = "car"

    //메서드(함수)
    func engineSound() {
      //메서드에서 해당 class/struct내의 프로퍼티에 접근할 수 있음
      print("\(name)이 달린다 부아아앙~")
      
      // 메서드 정의문 내에는 메서드(함수) 실행문 존재할 수 있다!
      turnRight()
    }
    
    func turnRight() {
      print("오른쪽으로 회전")
		}

    //클래스 내부에는 직접 메서드(함수) 실행문이 올 수 없다.
    turnRight()  //컴파일 에러
}

//인스턴스 생성
var v1 = Vehicle()
var v2 = Vehicle()

//점문법으로 인스턴스의 멤버에 접근
v2.name = "autobicycle"
v2.wheelCount = 2
v1.engineSound()


//앱 개발 시 데이터를 다룰 때 거의 필수적으로 사용 & 네트워크 통신 시에 (json형태와 유사)도 사용 
struct AccountData: Codable{
    
    var category: AccountCategory = .none
    var title: String = ""
    var account: String = "0"
    var date: Date = Date()
    var is_expenditure: Bool = true
    
    init(category: AccountCategory, title: String, account: String, is_expenditure: Bool) {
        self.category = category
        self.title = title
        self.account = account
        self.is_expenditure = is_expenditure
    }
}

2. 메모리 구조

클래스

  • 참조 타입 : 변수나 상수에 값을 할당을 하거나 함수에 인자로 전달할 때 그 값이 복사되지 않고 참조 (메모리 참고 하고 있다)
  • 인스턴스 데이터를 에 저장
  • 힙을 가르키는 변수(메모리 주소 값)는 스택에 저장 → 복사 시 힙을 가르키는 메모리 주소 값을 전달
  • ARC시스템 (참조 카운트) 을 통해 메모리 관리
  • 소멸자가 있음
//class의 인스턴스 복사 예제
class Person {
    var name: String
    var nickName: String
    
    init(name: String, nickName: String) {
        self.name = name
        self.nickName = nickName
    }
}

var p1 = Person(name: "수민", nickName: "daisy")

//s1에 Person클래스의 인스턴스 s의 값 복사 (힙에 있는 s를 가르키는 메모리의 주소가 전달됨)
var p2 = p1

//s1과 s가 힙의 같은 인스턴스를 가르키고 있으므로 s1의 멤버를 수정하면 s의 멤버도 변한다
p2.nickName = "sunny"

//"sunny" 출력
print(p1.nickName)

구조체

  • 값 타입 : 함수에서 상수나 변수에 전달될 때 그 값이 복사되서 전달
  • 인스턴스 데이터를 스택에 저장
  • 복사 시 다른 메모리 공간을 생성하여 복사본을 넣는다
  • 데이터를 스택에 저장하므로, 스택 프레임 종료 시, 메모리에서 자동으로 제거된다
//struct의 인스턴스 복사 예제
struct Person {
    var name: String
    var nickName: String
}

var p1 = Person(name: "sumin", nickName: "daisy")

//서로 다른 메모리 공간에 값만 복사가 되었다 
var p2 = p1

//복사된 값의 프로퍼티를 수정하면 메모리 주소를 다르므로 그 인스턴스의 값만 변한다
p2.nickName = "marco"

//"daisy" 출력
print(p1.nickName)

3. let과 var

  • class
    • let으로 인스턴스를 선언할 경우 힙에 있는 메모리 주소가 변할 수 없는 값이 된다
    • 메모리 주소 복사 불가능
  • struct
    • let으로 인스턴스를 선언할 경우 그 스택에 있는 인스턴스의 프로퍼티들이 let으로 선언된 것처럼 취급된다
class PersonClass {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

struct AnimalStruct {
    var name: String
    var age : Int
}

//***************<class>***************//
let person1 = PersonClass(name: "clamp", age: 22)
var person2 = PersonClass(name: "marco", age: 18)

//person1 = person2 -> error! person1은 let으로 선언되었기 때문에 메모리 주소를 변경할 수 없음

//person1의 메모리 주소가 복사
person2 = person1
//"clamp" 출력
print(person2.name)

//person1과 person2는 현재 메모리 주소를 공유하고 있음
person1.name = "daisy"
//"daisy" 출력
print(person2.name)

//***************<struct>***************//
let animal1 = AnimalStruct(name: "panda", age: 5)
var animal2 = AnimalStruct(name: "rabbit", age: 3)

//animal1 = animal2 -> error! animal1의 속성들은 let으로 취급되므로 변경 불가능

//animal2의 메모리 공간내에 있는 프로퍼티의 값이 바뀐다
animal2 = animal1
//"panda" 출력
print(animal2.name)

4. 초기화

💡 모든 저장 프로퍼티를 초기화 해야된다!
생성자 종료 시점에서는 모든 저장 프로퍼티의 초기값이 저장되어 있어야 함!!

  • 생성자 (init)
    • 인스턴스를 만들 때 사용하는 특별한 메서드이다 → 저장 프로퍼티 초기화가 목적
    • func 키워드 생략
    • Optional Type의 프로퍼티인 경우, nil값으로 자동으로 초기화된다
    • self : 클래스나 구조체 내에서 자기 자신의 인스턴스를 가르킨다
    • 오버로딩 지원
  • struct
    • 구조체에서는 초기화시 프로퍼티를 선언할 수 있는 생성자 자동으로 생성하여 제공
class PersonClass {
    var name: String
    var age: Int
		var weight: Double?
    
		//init 함수를 작성해주거나, 프로퍼티 선언과 동시에 초기화를 해주거나, optional값으로 지정해야된다
    init(name: String, age: Int) {
				//클래스나 구조체 내에서 자기 자신의 인스턴스를 가르킴
        self.name = name
        self.age = age
    }
}

struct AnimalStruct {
    var name: String
    var age : Int
}

let person1 = PersonClass(name: "clamp", age: 22)
print(person1.weight) //nil출력 (Optional type이므로 자동 초기화)

let animal1 = AnimalStruct(name: "panda", age: 5)
  • 식별 연산자
    • 두 개의 참조가 같은 인스턴스를 가르키고 있는지 비교
      • ===
      • !==

5. 언제 사용할까?

  • 구조체
    • 단순 캡슐화 구현할 경우
    • 인스턴스들을 참조하는 것보다 복사하는 것이 유리할 경우
    • 프로퍼티나 메소드 상속이 필요 없을 경우
    • 애플에서는 구조체 사용을 권장
    • stack에 데이터를 저장하기 때문에 메모리 사용이 가볍고 용이함
  • 클래스
    • 상속의 구조가 필요할 경우
    • 인스턴스들을 참조하는 것이 유리할 경우

6. 클래스와 구조체의 뷰 구현

  • UIkit
    • UIView는 제약과 배경, 정렬방법등과 같은 매우 많은 프로퍼티와 메서드를 가지고 있는 클래스의 서브클래스
    • UIkit에서의 모든 View는 이러한 UIView를 또 상속받은 서브클래스이므로
    • 뷰 생성 시 그 안에 너무 많은 프로퍼티들이 담김
    • 참조 타입 -> 클래스의 뷰를 생성함으로 어떤 곳에서 참조를 한다면 내부 값을 쉽게 바꿀 수 있음
    • 그러므로, 항상 모든 뷰의 상태를 체크하고 변화를 감시해야함
  • swiftUI
    • 구조체로 view를 생성
    • 상속되는 값 없이 생성하려는 구조체만큼의 크기를 가짐
    • 배경 및 색상과 같은 프로퍼티 -> ViewModifier 사용 (속성 부여)
    • 즉, 뷰 자체가 UIKit에 비해 매우 가벼움
    • 값 타입 -> 해당 뷰를 정의한 경우 그 정의한 곳에서만 사용되므로, 그 부분만 신경쓰면 된다
    • 메모리 관리에서도 매우 효율적.
//swiftUI내에서 기본으로 제공되는 모든 view component들은 구조체 형식으로 정의되고 사용된다
struct MainScrollView: View{
    
  @StateObject var dataManager: AccountDataManager = AccountDataManager.shared
  @State var acCategory: AccountCategory = .none

    var body: some View{
        VStack{
            List{
                ForEach(Array(dataManager.getList(Category: acCategory).enumerated()), id:\.offset) {idx, data in
                    AccountRow(accountData: data)
                }.onDelete(perform: deleteItem)
                
            }.padding()
            GetCategorySelectionArea(selectedCategory: $acCategory)
        }
    }
    
    func deleteItem(at offsets: IndexSet) -> () {
        dataManager.acDataList.remove(atOffsets: offsets)
        UserDefaults.standard.set(try? PropertyListEncoder().encode(dataManager.acDataList), forKey: AccountDataManager.ACCOUNT_DATA_LIST_KEY)
    }
}