Swift : Struct vs Class
많은 iOS 개발자들이 struct (with protocol)은 좋은 것이고, class 는 사용을 지양해야 할 나쁜 것이라는 편견을 가지고 있는 것 같아서, 이에 대한 내용을 한 번 정리해보고자 한다.
iOS 개발을 언제부터 시작했는지, 조금 더 풀이하면
- Objective-C
- Swift
중에서 어떤 language 로 개발을 시작했는지에 따라서 의견이 어느정도 갈릴 것 같기는 하지만, 그것과는 별개로 왜 이런 오해(?)가 시작되었는지에 대해서 이야기해보고 싶다.
다만 오늘 이야기하는 내용들은 개발자들끼리도 서로 의견이 많이 상충되는 것들이고, 필자 본인의 생각이 정답이라고 말하고 싶은 생각은 없다.
하지만 무엇이 옳다 그르다를 떠나서 이 내용에 대한 본인의 의견을 주장하기 전에 적어도 아래 내용들에 대해서 고민을 한 번씩 해보면 어떨까 하는 생각이다.
1. 모든 것의 시작은 Apple 공식 문서에서
`swift 로 개발할 때 class 는 쓰지 않는 것이 좋다`라고 이야기하는 분들이 주로 언급하는 내용이 Choosing Between Structures and Classes 라는 애플 공식 문서이다.
위 문서에서 발췌한
- Use structures by default.
라는 내용이 아마 많은 분들을 오해하게 만든 문장이 아닐까 생각된다. (실제로도 저 문장을 언급하면서 이 이야기가 시작되는 경우가 많다 ^^;;;)
그럼 저 문장 주변 내용을 한 번 자세히 짚어보자.
위에 빨간색 박스처리한 내용들을 보면, 애플에서 struct 를 기본적으로 사용하라고 이야기한 내용은 data model 에 관한 이야기이다.
한국 iOS 개발자들이 주로 사용하는 디자인 패턴인 MVVM 의 관점에서 이야기하면
- View 도 아니고
- ViewModel 도 아니고
- Model 에 대한 이야기
인 것이다.
현재 회사에 입사 후 본인이 처음 코드를 분석할 때 가장 이해가 안되었던 부분이 ViewModel 을 struct 로 만들어 사용하고 있다는 것이었다.
물론 MVVM 이라는 패턴이 아주 rough 한 형태의 제안만 던지는 것이고 그 구현체는 회사마다 다를 수 밖에 없다는 것을 인정한다.
그래서 실제로 ViewModel 을 struct 로 정의하여 사용하는 회사들도 많을 것이라 생각한다.
잡설이 길어지므로, 위 문서의 하단에 있는 다른 내용들을 한 번 살펴보자.
위의 내용을 보면 Identity 즉 해당 object instance를 사용하는 사용처들이 instance 의 동일성을 보장해야 할 경우에는 struct 가 아니라 class 를 사용하라고 이야기하고 있다.
struct 는 기본적으로 copy 방식으로 동작을 하기 때문에, 이미 할당된 struct 를 다른 변수에 assign 하여 사용하여 복사본이 여러 벌 생성되고 이 복사본들이 코드 내에서 각각 사용될 때는 instance 의 동일성이 보장되지 않기 때문에 identity 가 중요할 경우에는 class 를 사용하라는 이야기이다.
instance 의 동일성, 이라고만 하면 대부분의 iOS 개발자들이 이해할 수 있을 것이라고 생각하지만 혹시라도 내용이 잘 와닿지 않는 분들은 아래의 playground 코드와 실행결과를 보면 쉽게 이해가 될 것이다.
위 코드를 실행하면 결과는 아래처럼 출력된다.
ModelAsStruct 는 instance 의 동일성이 보장되지 않고, ModealAsClass 는 instance 의 동일성이 보장되고 있다.
2. Struct 내부에서 property 로 Class 사용
사실 가장 문제가 되는 내용이기도 하고 오늘의 글을 쓰고 싶었던 이유이기도 한 내용이 ‘Struct 내부에서 사용하는 Class’ 에 대한 것이다.
- struct 내부에 class 형태의 property 가 존재하고
- 해당 struct 로 만든 instance 가 복사되어 여러 곳으로 전파된 경우에 (assign 하는 방식으로)
- 위의 class property 는 언제 deinit() 되는 것일까?
한 번 위 내용에 대해서 생각해보자, 바로 답이 떠오르는 분도 있을 것이고 아리송한 분들도 있을 것이다.
그렇다면 코드로 한 번 확인을 해보자.
아래 형태로 샘플 코드를 만들어보고자 한다.
- Model : class 형태의 property 를 가지고 있는 struct
- ViewController : 이 녀석이 Initial View Controller 이다.
- SubViewController1 : ViewController 가 push 할 ViewController
- SubViewController2 : SubViewController1 이 push 할 ViewController
- SubViewController1 은 init 시에 Model 을 하나 생성해서 property 로 할당
- SubViewController2 는 init 시에 SubViewController1 의 Model instance 복사본을 전달받음
- VC → SubVC1 → SubVC2 로 navigation-push 를 진행했다가
- VC 까지 navigation-pop 두 번으로 돌아옴
Model 의 구성과 코드는 아래와 같다.
- Model 자신은 struct
- name 이라는 value-type(String) property 를 가지고 있음
- classProperty 라는 reference-type(Class) property 를 가지고 있음
- ClassInStruct 는 deinit 이 호출될 때 print 를 실행한다 (byebye~)
ViewController 의 구성과 코드는 아래와 같다.
- ViewController 는 최초 앱이 시작되면 호출되는 VC
- pushSubVC1() 이라는 함수를 호출하면
- SubViewController1 이라는 VC 를 생성하여 navigation-stack 에 push 한다.
SubViewController1 는 구성과 코드는 아래와 같다.
- init 시점에 model 이라는 이름으로 Model instance를 생성하여 property 에 저장
- pushSubVC2() 이라는 함수를 호출하면 model 의 복사본인 passedModel 을 생성하고 model, passedModel 내용을 console 에 출력
- SubViewController2 라는 VC 를 생성한 후 passedModel 전달한 후, navigation-stack 에 push 한다.
SubViewController2 는 구성과 코드는 아래와 같다.
- init 시점에 전달받은 Model instance를 자신의 model property 에 저장
자 이제 이 섹션의 제일 처음에 이야기했던
- VC → SubVC1 → SubVC2 로 navigation-push 를 진행했다가
- VC 까지 navigation-pop 을 두 번 진행하여 돌아옴
하는 과정을 진행할 것인데, ClassInStruct 는 과연 몇 번 그리고 어느 시점에 deinit 될까~~~~~~~~~~~~~~~~요?
위와 같이 model, passedModel 이 각각 별개의 instance 임에도 불구하고, model, passedModel 이 모두 메모리에서 해제된 이후에 classProperty 는 한 번만 deinit 되는 것을 확인할 수 있다!!!
많은 iOS 개발자들이 struct 를 쓰는 것이 class 를 사용하는 것보다 무조건 좋다고 생각하고, struct 는 assign 한 이후 여기저기에 전달해서 사용해도 메모리 내에서 아무런 문제도 없고 신경쓸 사항도 없다…라고 생각하는 경우가 많은데, 그건…
- struct 내부에 value-type 만 있을 경에에만 해당
되는 이야기이다.
struct 내부에 class property 가 존재하는 경우
- 이 class property 가 memory-capture 되지는 않는지
- class property 가 예상하는 기간보다 더 오래 memory 내에서 deinit 되지 않은 채로 남아있지는 않을지
여러가지 고려할 사항들이 생기고, struct 에는 class 와 달리 deinit() 함수가 존재하지 않기 때문에 struct 내부의 class property 로 인한 memory leak 이 발생했을 때 원인을 추적하기가 정말 어렵다.
또한 위에서 이야기한 것처럼 struct 자체에 대한 identity 가 중요한 경우에는 struct 를 class 로 변경하여 사용하는 방법을 고려해야 한다.
class 는 나쁜 것이 아니라 단순히 data-model 로서 사용하기에는 closure 내에서의 memory-capture 등 신경써주어야 할 것들이 많기에, 여러가지 면에서 struct 의 사용을 우선적으로 권장한다는 것이 Apple 공식문서에서 이야기하고자 했던 것이 아닐까 한다.
struct 와 class 는 다 자기나름 대로의 효용이 존재하고, 뭐가 좋고 뭐가 나쁘다는 것은 개발자들의 잘못된 편견이라고 생각한다.
더 재밌는 것은 우리가 항상 아무렇지 않게 사용하는 UIViewController 와 같은 Apple Framework 에서 제공하는 정말 많은 기능들이 class 로 만들어져 있는데, 그것들은 과연 나쁜 것일까? 잘못 만들어져 있는 것인가?
오늘은 Struct vs Class 라는 주제를 가지고 본인이 생각하던 것들을 이야기했는데, 뭐… 사실… 정답은 없다고 생각하고 , 개발자들 모두 각자 이에 대한 자신만의 생각이 있을 것이다.
다만 무언가를 좋다, 나쁘다 라고 확정된 생각을 가지기 전에, 사람들이 왜 무언가는 좋고 무언가를 나쁘다고 생각하게 되었는지에 대해 한 번 쯤은 돌아볼 수 있기를 바란다.
여기까지 읽어주셔서 감사하고,
모든 iOS 개발자에게 행복 있기를~