[iOS]구조체 안에 클래스가 있으면 생기는일.jpg

Youth·2024년 3월 20일
4

TIL

목록 보기
17/21

iOS라고 하기엔 swift이야기를 하러온 킴스캐슬입니다
갑자기 이런 주제를 고르게된 이유는 간단한데요

아는 친구가 개발하다가 예상한대로 돌아가지 않는 코드때문에 저한테 이것저것 물어봤는데 이야기를 하다가 제가 이런이야기를 했었거든요

구조체안에 클래스 왠만하면 쓰지말라던데...?

뭔가 확실한 이유가 있는게 아니라 예전에 viewModel을 struct로 만들어야하는지 class로 만들어야하는지 고민해본적이 있는데 그때 stackoverflow에서 누가 구조체안에 클래스객체 넣으면 reference때문에 예상치않은 동작을 할수가있어요~라고 이야기한게 어렴풋이 생각이 나서 저렇게 말을 했던것같네요

말하면서도, 말하고나서도 저렇게 말한게 찜찜하기도 했고 순간 호기심을 자극하더라고요? 대체 구조체안에 클래스가 있으면 무슨일이 일어날까?라는 주제가 말이죠

그래서 이번 포스팅은 두가지 문제를 풀면서 답을 내리는 그런 포스팅으로 결정했습니다

문제소개

1번 문제

class RefType {}
 
struct ValueType {
    let one = RefType()
    let two = RefType()
    let three = RefType()
}

let a = ValueType()
let b = a
let c = b
let d = c

print(CFGetRetainCount(a.one)-1)

위 코드에서 어떤 값이 출력될까요? 같은 말로 a라는 구조체안에있는 one이라는 변수가 가리키고있는 객체의 reference count는 몇일까요?라고 할 수 있겠네요

2번문제

let refArray = [RefType(), RefType(), RefType()]

let e = refArray
let f = e
let g = f
let h = g
let i = h

print(CFGetRetainCount(refArray[0])-1)

위 코드에서 어떤 값이 출력될까요? 같은 말로 array에 들어있는 첫번째 class객체의 reference count는 몇일까요?라고 할 수 있겠네요

1을 뺀 이유는 GFGetRetainCount라는 메서드를 실행하기위해서는 객체를 참조해야 하기때문에 1을 빼줘야 실제 객체의 reference count를 구할수있다고합니다

아마도 위 두 문제의 정답을 정확히 알고 계시면서 그 이유를 정확하게 말씀하실수있는 분들은 굳이굳이 이 글을 읽으실 필요는 없겠네요 ㅎㅎ 혹시나 읽으신다면 제 논리의 틀린부분을 찾아주시면 감사하겠습니다:)

1번문제 분석하기

1번문제를 보면 struct안에 class객체의 주소(reference)를 참조하고있는 변수 one, two, three가 있습니다. 그리고 그 구조체를 a라는 변수에 할당해줍니다

그리고 a라는 변수에 할당된 구조체를 b라는 변수에 할당해주고 이 b라는 변수에 할당된 구조체를 c라는 변수에 또 할당해줍니다. 그리고 또 한번더 반복합니다

즉 이상황은 구조체안에 클래스객체가 들어있을때 구조체가 복사되는경우를 가정한 문제라고 할 수 있습니다

자 그러면 우리는 여기서 한가지 알고 넘어가야하는 부분이 있습니다. 제가 헷갈렸던 부분인데요. 구조체를 다른 변수에 할당하면 어떤일이 벌어질까요?

구조체는 value type이니까 아마도 복사후에 그 값을 할당하게될겁니다. 그럼 복사할때 새로운 구조체가 생성되는걸까요? 저는 처음에 구조체를 copy한다고 했을때 새로운 구조체가 생성된다라고 생각했었습니다

간단한 코드로 실험을 해봅시다

struct MyStruct {
    let value = 0
    init() {
        print("구조체가 생성되었습니다")
    }
}

let first = MyStruct()
let second = first

print(first.value == second.value)

이 코드에서 제 가정(value type은 값을 복사할 때 객체가 새로생성될것이다)이 맞다면 first에 Mystruct를 생성해서 할당할때 한번 print문(구조체의 이니셜라이저)이 출력될거고 first를 copy해서 second에 할당하는순간 copy할때 동일한 속성을 가진 객체가 새로생성될것이기때문에 print(구조체의 이니셜라이저)문이 한번더 출력될겁니다

당연히 구조체자체를 copy했기때문에 맨아래 print문은 true가 나오겠죠

실제로 해당코드를 playground에서 실행해보면 "구조체가생성되었습니다"라는 출력문이 단 한번만 호출됩니다. 결론적으로 value type의 값을 copy할때는 새로생성하는것이아니라 같은 객체가 하나 더 생기는 메커니즘입니다

자 그러면 다시 1번문제로 돌아갈텐데 딱 아래만큼의 코드를 그림으로 표현해보겠습니다

class RefType {}
 
struct ValueType {
    let one = RefType()
    let two = RefType()
    let three = RefType()
}

let a = ValueType()

ValueType이라는 객체를 생성하는순간 initalize에서 각 내부 변수에 RefType이라는 class객체를 새로생성해서 reference를 할당하게될겁니다

이런 그림이 되겠죠?
one이라는 변수에 heap의 주소1에 할당된 RefType이라는 객체의 주소를 할당해줄겁니다 two three도 각각 heap에 만들어진 객체의 주소를 할당받겟죠

여기서 아래코드가 실행되면 어떻게될까를 고민해봐야합니다

let b = a

a라는 value type을 copy했기때문에 내부에 있는 property가 전부 복사될겁니다 만약에 "새로생성"되는거였다면 말이달라졌겠지만 그게 아니라는걸 확인했으니 아래와같은 그림이될겁니다

b라는 valu type의 구조체가 a로부터 copy될때 내부에 property인 one two three가 그대로 복사될거고 그말은 one이 가지고있는 주소가 그대로 할당된 상태일거고 two도 three도 마찬가지일겁니다 heap영역에 있는 주소1에 있는 노란색의 RefType의 입장에서는 struct가 복사될때마다 자기주소를 참조하고있는 property가 하나씩 늘어나게됩니다

결국 1번 문제에서 출력되는 값은 4가됩니다 struct가 4번복사되기때문이죠

2번문제 분석하기

1번문제를 보느라 2번문제가 뭐였는지 잊어버리셨을수있을거같아서 코드를 다시 가져와보겠습니다

let refArray = [RefType(), RefType(), RefType()]

let e = refArray
let f = e
let g = f
let h = g
let i = h

print(CFGetRetainCount(refArray[0])-1)

이 문제에서 숫자 몇이 출력될까요?
이렇게 생각하실수도있습니다

음...이번에는 구조체에있는게아니라 array안에 class가 있는 구조네?
근데 swift에서 Array는 value type이니까 1번이랑 똑같이 value type에 reference type이들어있는 같은상황아닌가? 그리고 swift에서 Array는 struct였던거같은데?

이것도 1번이랑 똑같지 않을까?

제가 처음에 이 문제를 봤을 때 비슷한 생각을 했습니다

그러면 한가지 질문을 추가로 해보겠습니다. 아래의 문장은 참일까요 거짓일까요?

Q. value type은 stack영역에 저장되고 reference type은 heap영역에 저장된다

일반적으로는 맞다고 할수도있지만 swift에서는(제가 다른언어 경험이 없어서 다른언어는 모르겠네요ㅠㅠ) value type중에서도 collection type은 stack이 아닌 heap에 저장된다고 합니다
(이유는 구글에 검색하면 정말 많은 자료가 나오니 한번 검색해보시면 좋을거같아요 ㅎㅎ 이번글은 그 주제가 메인이아니기때문에 넘어가겠습니다)

그래서 collection type중 하나인 array는 heap영역에 저장되고 그 array를 할당받은 property는 주소값을 가지고 있게됩니다

이런 그림이라고 볼 수있겠네요

결국 문제에서 묻는건 주소1에있는 노란색 RefType의 reference count가 몇인지를 묻는데 만약에 이 그림에서 값이 계속 복사된다고 해도 주소4에있는 array주소를 가지고있는 property만 늘어나게되고 주소1에있는 객체의 reference count는 전혀 늘어나지 않습니다

그렇기 때문에 이 코드를 실행해보면 1이라는 값이 출력되게됩니다

Array가 Reference Type이랑 뭐가다른거죠?

이렇게 보면 이런의문이 들수도있습니다 대체 그럼 Array를 왜 struct로 만든거지?, 암만봐도 value type으로 동작하지 않는데?

만약에 Array가 heap에 저장되는 상황에서 reference type으로 동작하게되면 아래와 같은 그림에서 주소4에 있는 첫번째 element를 remove하면 어떻게될까요?

결국 e나 f나 같은 주소를 바라보고 있으니 [주소2, 주소3]이 된 array를 바라보게되구나라고 생각하실수도있지만 위 그림에서 주소4에 있는 array가 변하게된 순간 주소값을 확인해보면

이런식으로 참조 관계가 변하게됩니다
만약에 정말 heap에 저장된 value type인 array가 reference type처럼 동작했다면 이런 관계가 되진않았을겁니다. array의 경우엔 매번 값이 복사되는 value type이지만 그렇게되면 비효율적으로 메모리를 사용하게될수있기때문에 heap에저장되고 array가 변하지 않으면 같은 주소를 참조하고 만약에 array가 변하면 그때서야 복사하는 Copy On Write방식으로 동작하게됩니다

메모리의 효율성을 위해서 heap에 저장되었지만 value type의 특성인 copy된다는 특징을 효율성있게 사용하는 방법이라고 할수잇습니다

근데 Array는 구조체고 COW방식으로 동작하면 1번문제에서의 구조체도 구조첸데 왜 여기서는 COW로 동작하지 않았나요?

라고 물어보신다면 swift에서 COW는 value type중에서도 collection type에서만 작동하는 방식이라고합니다! 그러니까 우리가 만든 구조체인 사용자지정구조체는 COW로 동작하지 않고 오직 stack에 할당되고 copy되는 방식으로 동작하게되는거죠

마무리

처음에 1번 코드를 봤을때 저는 당연히 value type이고 그말은 복사될때마다 아얘 독립된 객체가 생기는거니까 1이겠지~라고 생각했었거든요. 물론 제가 좀 이부분에 있어서 너무 얕게 알고있어서 그랬다는 생각은 들지만 오히려 그런생각이 드니 분명히 이부분을 헷갈리는 사람들도 좀 있을수도있겠다는 생각이들어서 이참에 정리를 한번 해보기로 했던것같습니다

2번 코드를봤을때는 Array는 heap에저장되며 COW방식으로 동작한다는걸 적용시키지 못해서 1이 출력될거라는 예측과 계산을 하지못했습니다(아직...갈길이 멀군요ㅎㅎ) 분명히 COW가 뭔지 개념을 설명할수있고 Array은 value type이지만 heap에 저장된다는사실을 알고는 있었지만 실제로 이 두개념을 종합해서 파악해야하는 순간에는 그 개념들을 잘 연관지어서 보지못했던것같습니다

구조체안에 클래스객체를 넣었을때 예상치못한 동작을할수도있다는 말은 구조체를 정말 구조체자체로 보면 복사될때 내부에있는 클래스객체가 새로 생성될것처럼 생각할수있어서 예상치못한 동작이나 참조가생길수도 있다는 말이었나싶습니다...이렇게 제대로 참조관계를 분석해보니 혹여나 구조체안에 클래스를 쓸일이 있어도 이런상태겠군하고 머릿속에 그릴수있겠네요. 구조체안에 클래스를 쓸일이 있을지는 모르겟지만 말이죠

오늘 포스팅은 반성문을 쓰는 느낌으로 작성했습니다ㅎㅎ
제대로 공부하고 누군가 말해준 답변을 아무 검증없이 믿고 진짜인것처럼 받아들이면 안되겠다는 반성을 하게되었습니다. 누군가가 해준답변을 제가 아는것마냥 말하는 상황이 얼마나 빈수레인지를 느꼈네요 ㅎㅎ... 늘 를 생각하며 공부해야겠다는 다짐을 다시하면서 이번 포스팅을 마무리하겠습니다

그럼 20000!

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

2개의 댓글

comment-user-thumbnail
2024년 3월 21일

1번은 맞췄는데 2번은 틀렸네요
Collection Type은 내부에 아이템이 동적으로 바뀔 수 있고 아이템의 개수도 많을 수 있어서 Heap에서 관리하는 것 같습니다
String도 기본적으로는 Stack이지만 긴 문자열은 Heap에 저장한다고 하니까요

클래스를 품고 있는 구조체는 새로 생성되지 않는다는거 기억해야겠네요

1개의 답글