[iOS] 다시 한번 정리하는 Struct와 Class

thinkySide·2024년 12월 20일
0
post-thumbnail

본 포스팅은 애플 디벨로퍼 아카데미 @POSTECH 테크 포럼 이벤트, '기술 글자랑 대회'의 게시글을 가져와 작성되었습니다.

🧐 공부하게 된 이유

최근 SwiftUI를 공부하며 구조체(struct)를 사용할 일이 많아졌다. 더불어 데이터 모델링 과정에서 Entity를 생성하다 문득 머릿속에 의문이 들었다.

struct로 데이터를 모델링할 때 저장 속성을 변수로 만들까 상수로 만들까?

내 나름대로의 기준이 있었지만,
(안바뀔거 같은거는 상수로 바뀔거 같은거는 변수로...)
그 기준이 어떤 근거로 왔는지 명확히 설명할 수는 없었다.

또한 변수로 선언하든 상수로 선언하든 사용에 있어 큰 차이를 경험해본적도 없었기에, 더더욱 추측 밖에는 할 수 없었다.

..라는 글을 작성하고 싶었는데 이것저것 파다보니 결국 왜 struct을 데이터 모델링에 사용해야 하는가? 라는 답이 선행되어야 할 것 같아 제목을 변경했다.

..라는 글로 적으려 했는데 너무 길어지는 것 같아 struct와 class의 차이점을 다시 한번 명확히 짚고가자 라는 주제를 선정하게 되었다. (나중에 꼭 적고 말거다)

😼 나 자신을 포함해 두 개념의 차이를 다시 한번 쉽게 이해하고 전달해보고자 하는 취지로 작성해보려 한다!

🏊 본문

1. 지금까지 어떻게 데이터 모델링을 하고 있었을까?

모델이라 하면 무엇이 떠오르는가? 보통은 요런 모양을 떠올린다.

/// 🐶 강아쥐
struct Dog {
    let name: String
    var age: Int
}

나만의 귀여운 강아지를 만들려고 한다면(표현하려 한다면) 이런 데이터 '모델'을 정의할 수 있을 것이다. 그렇다! 데이터 모델이라 하면 struct(구조체)로 만들어진 것을 떠올린다.

생각해보면 나는 iOS 개발을 처음 접했을 때부터 지금까지, 다양한 경험에 빗대어 관성적으로 struct를 사용하고 있었다.

왜 struct를 사용하고 있었을까 조금 더 깊이 고민해보자.

2. struct을 알려면 class를 알아야 한다

struct를 사용하는 맥락에 대해 이해하려면 자연스럽게 class와의 차이점에 대해 고민하게 된다.

귀여운 강아지와 고양이 예제를 이용해 빠르게 훑어보자!

/// 🐶 강아쥐
struct Dog {
    let name: String
    var age: Int
}

/// 😺 고앵이
class Cat {
    let name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

3. struct는 값 타입, class는 참조타입

Swift를 공부하면서 가장 많이 듣는 말 중 하나는

"struct는 값 타입, class는 참조타입이다." 라는 것이다.

하지만 예전이나 지금이나 이 문장은 한번에 와닿기에는 무리가 있는 것 같다. 단어 그 자체로서는 인지하고 있지만, 실제 의미에 대해서는 파악하기가 어려운,,, 느낌이다.

어찌되었든, 이렇게나 중요한 차이점을 표현해보고자 예제 코드를 아래 작성해두었고, 흐름은 다음과 같다.

  1. struct/class로 객체(인스턴스) 생성하기
  2. 새로운 변수 만들고 할당하기
  3. 기존 객체(인스턴스)의 저장 속성 업데이트
  4. print 함수를 통해 결과 확인
var mintol = Dog(name: "민톨", age: 1)
var mintolCopy = mintol
mintol.age += 1
print("[Struct] 민톨 복제본 나이: \(mintolCopy.age)")

var nabi = Cat(name: "나비", age: 1)
var nabiCopy = nabi
nabi.age += 1
print("[Class] 나비 복제본 나이: \(nabiCopy.age)")

결과는 어땠을까?

아니 분명! 같은 동작을 해줬는데 값이 다르다,,, 왜 이런 말도 안되는 것 같은 일이 벌어졌을까? 그림으로 간단하게 표현해봤다.

1번 동작: struct/class로 객체(인스턴스) 생성하기

  • struct : 인스턴스를 생성한다.
  • class : 객체를 생성한다.

struct은 필요할 때만 스택에 올라가고, 필요 없어지면 스택에서 자동으로 해제 된다. 그렇기에 class 처럼 힙에 메모리 주소를 저장할 필요가 없다. 즉, struct의 인스턴스는 값 그 자체를 의미한다.

반면, class는 힙에 메모리 주소를 저장해놓고 사용한다. 즉, class의 객체는 메모리 주소를 가리키고 있음을 의미한다.

⭐️2번 동작: 새로운 변수 만들고 할당하기⭐️

struct : 기존 민톨(값)을 복사해 새로운 변수에 담는다.

class : 기존 나비의 주소(참조)를 새로운 변수에 담는다.

결국 struct의 인스턴스는 값 그 자체로서 복사되어 새로운 변수에 저장되고, class의 객체는 동일한 메모리 주소가 새로운 변수에 저장되는 것이다!

struct과 class 예제가 동일해 보이지만 내부에서는 이러한 차이가 있던 것이다!

3번 동작: 기존 객체(인스턴스)의 저장 속성 업데이트

예상했던 것처럼, struct의 경우 두개의 값 중 원래 값만 바뀌었으니 복제본의 나이는 업데이트가 되지 않았다.

class의 경우 메모리 주소는 동일하게 하나를 가리키고 있으니 같은 값이 출력되고 있는 것이다.

4. Struct와 Class 특징 정리

위 예시를 통해 struct과 class의 가장 중요한 특징을 알아봤다. 이것저것 많은 특징 중에 내가 가장 강조하고 싶은 문장은 다음과 같다!

struct는 값타입, 값 그 자체를 의미한다.
class는 참조타입, 메모리 주소를 의미한다.

이제야 값타입, 참조타입이라는 모호했던 개념이 정리가 되는 느낌이다.

그리고 이를 더 명확하게 이해할 수 있는 예제를 가져와봤다.

struct Dog { 
    let name: String
    var age: Int 
}

class Cat { 
    let name: String
    var age: Int
	
    init(name: String, age: Int) {
	self.name = name
	self.age = age
    }
}

var mintol = Dog(name: "민톨", age: 3)
let hantol = Dog(name: "한톨", age: 26)

mintol.name = "밍톨"
mintol.age = 4

hantol.name = "두톨"
hantol.age = 27

이 코드의 컴파일 결과는 어떻게 될까? 값 그 자체 라는 맥락에서 struct의 인스턴스를 바라보고, 메모리 주소라는 맥락에서 class의 객체를 바라보자.



여기서 주목해야할 점은, 32번째 줄의 에러이다.
hantol의 age는 변수라 언뜻 보기엔 값을 변경할 수 있어 보이지만,
인스턴스(struct) 자체가 let으로 선언되어 있어 컴파일 에러가 난다.

당연하게도 값 그 자체인 인스턴스가 불변하기 때문이다.
그리고 자연스레 41번째 줄은 객체(class) 자체가 let임에도 불구하고 age 속성을 변경할 수 있는 이유는,
메모리 주소는 그대로 두고 안의 age 값만 변경하기 때문이다.

📓 정리하기

개발 공부를 하며 가장 크게 느낀 부분은, 개념적으로 이해하고 사용했을 때의 차이는 생각 외로 정말 크다는 것이다.

오늘도 다시 한번 딥다이브와 기본기의 중요성을 느낀다! 👍

profile
UX 한스푼 넣은 iOS 디발자 한톨 / Apple Developer Academy @POSTECH 3기

0개의 댓글