Swift에서 struct와 class는 비슷해보이지만, 언어 설계 방향과 코드 전체 구조적 특성, 확장성 등 핵심적인 차이를 가진다.
class의 경우 Objective-C와의 호환에 맞춰서 도입된 참조 타입(Reference type)이며, 상속, 런타임 타입 체크, ARC(자동 참조 카운트) 관리를 지원한다.
struct의 경우 값 타입(Value type)으로, 복사 시 데이터 전체를 새로 복제한다. 상속이 불가능하며, Swift에서 struct는 C의 struct와 달리 메서드, 프로퍼티, 확장, 프로토콜 채택 등 객체지향적 특성을 모두 가진다.
이 struct를 클래스와 동등한 1등 시민(First-Class Citizen)으로 두고 실제 표준 라이브러리와 SwiftUI 등 시스템 전반에 기본 설계 단위로 적극 활용한다.
이러한 점들을 어느정도 알고는 있지만, Swift로 개발하게 된 초기부터 Struct와 Class는 언제 어느 상황에서 선택해야 하는 걸까?라는 의문을 해소하는 과정에서 나온 또 다른 의문을 해결해보고자 한다.
Swift에선 왜 이리 Struct를 좋아하지?
우선 애플이 제공한 가이드라인에 따르면,
- 기본적으로 구조체(struct)를 사용한다.
- 다만, Objective-C와 상호 운용성이 필요한 경우 클래스를 사용한다.
- 모델링 중인 데이터의 Identity를 제어해야 하는 경우 클래스를 사용한다.
- 구조체와 프로토콜을 사용하여 상속 및 공유 동작을 모델링한다.
가이드라인에서도 볼 수 있듯 Swift는 struct 사용을 적극 권장한다.
참조가 필요한 특별한 경우에만 class를 쓰라고 한다.
값 자체가 의미, 복사·전달·독립성이 필요하면 struct를 쓰고 상속·다형성·라이프사이클이 중요하면 class를 쓰면 될 것 같다.
struct가 권장되는 이유 중 가장 큰 비중을 차지한 부분이 값 타입(Value type)이라는 점이었다.
값 타입(Value Type)과 복사
Swift에서 struct는 값 타입이다. 복사나 함수 전달, 변수 대입 시 값 자체가 새로운 인스턴스로 복제되어, 원본과 복제본이 완전히 독립적으로 동작한다.
이를 통해 데이터 공유 / 의도치 않은 변경(부수효과)이 원천적으로 방지된다.
불변성(immutability)
struct 인스턴스를 let으로 선언하면, 내부 프로퍼티가 var여도 모든 값이 불변이 된다.
변경 불가 상태 덕분에 실수, 멀티스레드·동시성 버그, 원하지 않은 사이드 이펙트를 예방하며, 안전한 코드 설계가 가능하다.
메모리 관리와 ARC 영향 없음
struct는 값 자체가 복사되고 ARC(Automatic Reference Counting)의 대상이 아니어서, 복잡한 참조 관리 / 순환 참조로 인한 메모리 누수 우려가 현저히 적다.
동시에 Swift의 copy-on-write 최적화가 적용되어 성능과 메모리 효율도 모두 우수하다.
* Copy on write : 데이터를 복사해서 사용하면서, 원본 데이터가 변경되기 전까지는 공유해서 사용하다가 원본 데이터가 변경되는 시점에서 복사하여 자신만의 독립적인 데이터를 가지게 되는 최적화 기법을 말한다.
멀티스레드 안전성(Thread Safety)
값 타입 struct는 각 스레드/함수에서 안전하게 복사·관리되므로, 동시 접근·데이터 꼬임·락 관리 등에서 자유롭다.
여러 컨텍스트에서 동일 데이터 조작시에도 예기치 않은 충돌이 원천적으로 예방된다.
* 한 클래스의 인스턴스 자원에 병렬로 접근해 어느 연산 처리를 해준다고 하면 특별한 조치 없이는 Race Condition이 날 확률이 높다. (여러 스레드에서 동일한 주소에 병렬로 접근을 하기 때문에) 해당 케이스에서 struct 기반이면 문제가 없다는거 아냐?라고 생각해봤을 땐, 매 연산마다 새로운 인스턴스로 복제해서 원하는 프로퍼티에 대한 연산을 하기 때문에 Race condition 자체는 걱정할 일은 없겠다. 다만 원하는 결과가 나오기는 힘들겠다..!
Swift의 struct 지향 설계는 여러모로 생태계에 녹아 있다.
대표적으로 SwiftUI에서 View는 Struct로 설계되어 있는데, 이러한 부분에서도 struct의 장점을 알 수 있다.
SwiftUI의 View는 왜 Struct로 선언할까?
다음 스유로 개발해볼 때 제대로 보겠지만, 간단하게 보면 기존 UIKit에서의 뷰들은 기본적으로 UIView를 상속받는 클래스다.
이렇게 되면 정말 간단한 서브뷰를 만들어 쓸 때에도, UIView를 상속받아 쓴다는 점으로 인해 이 UIView가 기본적으로 가지고 있는 프로퍼티를 모두 갖고 있게 되는 셈이다. 예를 들면 backgroundColor, frame 등등.. 이렇게 되면 보다 무겁거나 큰 객체가 생성된다..
반면 SwiftUI 내 struct 형태의 View는 상속을 받지 않는 시스템에 있기 때문에 앞서 필요하지 않은 프로퍼티 같은 부분을 굳이 갖고 있지 않아도 된다. 대부분 필요한 부분들을 경량화해 가져와 쓰도록 한다.
동시에 이 구조체라는 건 포인터를 사용하지 않기 때문에 메모리 관리 오버헤드가 없다. 동시에 앞서 말했듯 ARC에 의한 참조 카운팅이 수행되지 않는다. 이로서 Struct를 쓰도록 해 보다 더 가볍게 View를 유지할 수 있다는 장점이 있다.
이러한 점 외에도 array, dictionary, set 등 주요 컨테이너들도 struct 구조를 가지고 이를 통해 데이터 독립성, 불변성이 자동 보장된다.
Struct가 Class에 비해 빠르다?
메모리 면에서도 비교를 해봤다.
우선 생성 면에서도 struct는 스택 안에서만 관리되고 class는 힙안에 실제 인스턴스 데이터를 가지고 스택에서 이 데이터들이 있는 주소를 가리키면서 ARC로 참조 카운팅을 올리는 등 과정이 많이 달라 다수의 인스턴스 생성만으로도 시간 차이가 날 것 같았다.
import Foundation
struct StructData {
var x: Int
}
class ClassData {
var x: Int
init(x: Int) {
self.x = x
}
}
let beforeStruct = Date()
for i in 0..<1000 {
let _ = StructData(x: i)
}
let afterStruct = Date()
print("Struct 생성 시간: \(afterStruct.timeIntervalSince(beforeStruct))초")
let beforeClass = Date()
for i in 0..<1000 {
let _ = ClassData(x: i)
}
let afterClass = Date()
print("Class 생성 시간: \(afterClass.timeIntervalSince(beforeClass))초")
여러번 테스트를 해봤는데 미세하게 struct가 더 빨랐다.
관련 성능 테스트를 어느 블로그 보고 따라해봤는데
import Foundation
struct StructData {
var x: Int
var y: Int
}
class ClassData {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var totalStructTime = 0
var totalClassTime = 0
for idx in 1...10 {
print("\(idx)/10 테스트")
let structSample = StructData(x: 0, y: 0)
var classSample = ClassData(x: 0, y: 0)
let structStart = DispatchTime.now()
for _ in 0 ... 100000 {
var structCopy = structSample
structCopy.x += 1
}
let structEnd = DispatchTime.now()
let classStart = DispatchTime.now()
for _ in 0 ... 100000 {
var classCopy = classSample
classCopy.x += 1
}
let classEnd = DispatchTime.now()
print("Struct Time: \(Int(structEnd.uptimeNanoseconds) - Int(structStart.uptimeNanoseconds))(ns)")
totalStructTime += Int(structEnd.uptimeNanoseconds) - Int(structStart.uptimeNanoseconds)
print("Class Time: \(Int(classEnd.uptimeNanoseconds) - Int(classStart.uptimeNanoseconds))(ns)")
totalClassTime += Int(classEnd.uptimeNanoseconds) - Int(classStart.uptimeNanoseconds)
print("\n\n")
}
print("Struct 평균 소요시간: \(totalStructTime / 10)(ns)")
print("Class 평균 소요시간: \(totalClassTime / 10)(ns)")
print("Struct가 \((totalClassTime - totalStructTime) / 10)(ns) 만큼 더 빨랐습니다.")
출처: https://hasensprung.tistory.com/181
[Swift] Struct와 Class를 메모리 원리부터 자세하게 비교해보자
Understanding Swift Performance - WWDC16 - Videos - Apple Developer In this advanced session, find out how structs, classes, protocols, and generics are implemented in Swift. Learn about their relative... developer.apple.com WWDC Understanding Swift Perfor
hasensprung.tistory.com
연산이 들어갈 경우 좀 더 차이가 나게 된다.
스택에서 가리키는 힙 내 인스턴스에 대한 주소로 접근해 내부 값을 변경하는 시간이 미세하게 있을텐데 이 과정이 반복되면서 시간 차가 생기지 않을까 싶다. (struct 기반 방식에서는 스택 내부에서만 처리하면 되기 때문에)
결국 struct를 class와 비교해보았을 때 보다 좋은 점들은 대개 클래스와 다르게 값 타입(value type)이라는 특징에서 비롯됐다.
메모리적으로나 이 값 타입 자체의 불변성 등으로 좀 더 안정적인 개발이 가능케하는 요소라고 느껴졌는데, 다만 기존 Objective-C에서 많은 클래스들에 대해 아직 UIKit에서 연결지어 쓰는 경우가 많기 때문에 Class를 배제한다..? 느낌은 절대 아니었다.
구조체와 클래스 각각 그만의 장단점이 있었고 Swift는 이 구조체라는 녀석을 타 프레임워크나 언어에 비해 시스템 내에서 더 매력적으로 작동할 수 있게 구성을 해두어 이 안에서 그 장점을 최대한 이용하고자하는 인상을 받았다.
메모리적으로 좀 더 깊게 접근하면 v-table을 써야하고 그걸 최적화하는 방법도 있고 다양하게 볼 수 있는 주제면서, 이 두 부분에 대한 최적화가 이미 잘 되어있기도 하고 개발자 단에서 더 최적화를 할 수 있는 여지가 많기 때문에 이에 대해서도 알아두는 게 좋겠다.
https://subkyu-ios.tistory.com/29
[TIL / 25.03.20] 최적화(OptimizationTips) 2
1. Swift 컨테이너 타입을 효율적으로 사용하는 방법Array에서 값 타입 사용하기Swift에서 타입은 두 가지 카테고리로 나눌 수 있습니다. 값 타입과 참조 타입.값 타입은 struct, enum, tuple이 있고, 참조
subkyu-ios.tistory.com
이전 조에서 매주 인사이트 발표를 하던 당시에 swift.org 내 내용을 번역해 발표한 적이 있는데, 좀 더 연관지어 이해하기 쉽겠다.
추가 출처 (담에 좀 더 깊게 파면 볼 것들~)
https://artieee.tistory.com/15#google_vignette
[Swift] SwiftUI에서는 왜 뷰에 struct를 사용할까?
시나리오 : 프로젝트 생성시 interface를 SwiftUI로 선택하면 기본 뷰가 struct로 생성되어 있는 것을 확인할 수 있음. 기존에 Storyboard를 선택하고 생성하게 되면 class로 생성되어 있는데 왜 SwiftUI에서
artieee.tistory.com
https://didu-story.tistory.com/m/255
[Swift] Struct, Class 부시기 (차이점, class 와 ARC, stack할당, heap할당)
이전에 Struct와 Class에 대한 개념과 두 개의 차이점에 대해서 포스팅했던 적이 있다. 스위프트의 'ㅅ' 도 모를 때 정리했던 글이라 도저히 지금와서 생각했을때 도움이 되지 않...았기 때문에 struct
didu-story.tistory.com