일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 주간 달력
- Switch
- error해결
- 파스칼표기법
- class
- weekly calendar
- On branch is up to date with ' '
- MyLife
- 다짐글
- 내_삶
- 제어전송문
- avaliable
- Git
- 글또9기
- ios 개발 강의
- K디지털기초역량훈련
- IOS
- uikit
- struct
- 회고
- copy-on-write
- unrecognized selector sent to class
- SWIFT
- AnyObject
- 글또
- 연관값
- 생명주기
- 코드스니펫
- actionSheet
- 바이트디그리
- Today
- Total
seong_hye, the developer
iOS) 클래스(Class) vs 구조체(Struct) 제대로 알아보기 본문
이번에 기술 면접을 보게 되면서 현재 내가 어떤 점이 부족한지 알게되는 기회가 되었다.
그 중에서 클래스와 구조체가 어떤 차이가 있고 어떨 때 사용되는지에 대한 질문을 해주셨다.
형식적으로 차이에 대해 알고 있었지만
실제로 코드에서 활용시 어떻게 차이가 나는지를 알고 있지 않다는 점을 알게 되었다.
https://programming-seonghye.tistory.com/15
이 경험을 바탕으로 한 번 클래스와 구조체에 대해 제대로 알아보고 정리해보려 한다.
클래스와 구조체에 대해 비교해보자
클래스와 구조체는 Swift에서 데이터를 모델링하고 저장하는 데 사용되는 두 가지 기본적인 틀이라고 볼 수 있다.
둘의 차이를 표로 간단히 정리해서 알아보자
클래스(Class) | 구조체(Struct) | |
타입 | 참조 타입 (주소 전달) | 값 타입 (복사 전달) |
메모리 형식 | Heap에 값 저장 -> ARC로 관리 |
Stack에 값 저장 -> 메모리에서 자동 제거 |
상속 가능 여부 | 상속 가능 | 상속 불가능 |
클래스의 경우
- 참조 타입
참조 타입으로 값을 복사할 때 인스턴스 자체를 복사해서 전달하는 것이 아닌
인스턴스의 주소를 복사해서 전달하게 된다.
- Heap에 값 저장
메모리의 Heap 부분에 복사한 주소 값을 저장하게 된다.
Heap은 동적으로 크기가 할당되며 런타임에 메모리를 할당하고 해제할 수 있다.
- 상속 가능
클래스에서만 가능한 기능으로 상속을 지원하고
객체 지향 프로그래밍의 주요 역할을 한다.
구조체의 경우
- 값 타입
참조 타입으로 값을 복사할 때 인스턴스 자체를 복사해서 전달한다.
별도의 메모리 공간이 할당되고 해당 값이 복사된다.
- Stack에 값 저장
메모리의 Stack 부분에 복사한 값을 저장하게 된다.
Stack은 고정된 크기의 메모리 블록을 사용하며 빠른 데이터 접근이 가능하다.
- 상속 불가능
상속은 클래스에서만 가능하기에
구조체는 상속이 불가능하다.
메모리 할당 방식의 차이로 인한 성능 차이
struct의 값 타입은 Stack에 저장되지만
class의 참조타입의 경우 Heap에 값을 저장하고
이를 가리키는 주소값을 Stack에 저장한다.
값에 접근할 때
Stack에 저장된 값은 직접적인 접근이 가능하지만
Heap의 경우 주소를 통해서 접근하는 역참조를 해야 한다.
역참조의 경우 메모리 범위를 검사하고
가상 메모리 주소를 물리 메모리 주소로 변환하고 그 때 페이지 테이블을 검사해야 한다.
또한 다시 사용할 것을 대비해서 캐시에 저장하는 작업이 이루어져야한다.
=> Heap의 경우 오버헤드에 걸릴 가능성이 더 높아진다.
어느 정도 시간 차이가 날까?
import Foundation
struct StructSample {
var id: Int
}
class ClassSample {
var id: Int
init(id: Int) {
self.id = id
}
}
let structSample = StructSample(id: 0)
var classSample = ClassSample(id: 0)
var totalStructTime = 0
var totalClassTime = 0
for sample in 1...10 {
print("\(sample)/10 test")
let structStart = DispatchTime.now()
for _ in 0...10000 {
var structCopy = structSample
structCopy.id += 1
}
let structEnd = DispatchTime.now()
let classStart = DispatchTime.now()
for _ in 0...10000 {
let classCopy = classSample
classCopy.id += 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("")
}
print("struct 평균 소요시간 : \(totalStructTime / 10)(ns)")
print("class 평균 소요시간 : \(totalClassTime / 10)(ns)")
print("struct가 \(totalStructTime - totalClassTime / 10)(ns)만큼 더 빨랐습니다.")
--
테스트 결과
10000번 값에 접근 한 결과 평균적으로 struct의 속도가 20000000(ns)정도만큼 더 빨랐다.
약 2퍼센트의 성능차이가 난다는 것을 알 수 있다.
오버헤드가 작지 않다는 것을 알 수 있는 결과였다.
참조 카운트에 따른 성능 차이
class의 경우 ARC를 활용해 메모리를 관리한다.
이 과정 속에서도 오버헤드가 발생하게 된다.
참조 타입 안에 있는 참조 타입은 그대로 해당 Heap 메모리 영역에서 관리되지만
값 타입안에 있는 참조 타입은 각각 Heap에 할당되며 각각의 ARC가 관리하게 된다.
Class 속 Class의 ARC 값은 어떻게 나타날까?
import Foundation
class ClassSample {
var id: Int
init(id: Int) {
self.id = id
}
}
class ClassSample2 {
var sample1 = ClassSample(id: 0)
var sample2 = ClassSample(id: 1)
var sample3 = ClassSample(id: 2)
}
let classSample = ClassSample2()
let class1 = classSample
let class2 = classSample
let class3 = classSample
let result = CFGetRetainCount(classSample)
let result1 = CFGetRetainCount(classSample.sample1)
let result2 = CFGetRetainCount(classSample.sample2)
let result3 = CFGetRetainCount(classSample.sample3)
print("class 참조 횟수 : \(result)번")
print("class.sample1 참조 횟수 : \(result1)번")
print("class.sample2 참조 횟수 : \(result2)번")
print("class.sample3 참조 횟수 : \(result3)번")
--
결과
class의 경우 4번 참조할 것이라 예상했다.
하지만 5번 참조하는 결과를 가져왔고 생성과 동시에 참조하는 것이 아닐까 생각하고 있다.
아래와 같은 경우 참조하게 되어 5번 참조하는 모습을 보여준다.
class ClassSample2 { // +1 = 1
...
}
let classSample = ClassSample2() // +1 = 2
let class1 = classSample // +1 = 3
let class2 = classSample // +1 = 4
let class3 = classSample // +1 = 5
class 속 class의 경우 아래와 같은 경우 참조하게 되어 2번 참조되는 모습을 보여준다.
var sample1 = ClassSample(id: 0) // +1 = 1
let classSample = ClassSample2() // +1 = 2
그렇다면 Struct안에 있는 Class의 ARC는 값은 어떻게 나타날까?
import Foundation
class ClassSample {
var id: Int
init(id: Int) {
self.id = id
}
}
struct StructSample {
var sample1 = ClassSample(id: 0)
var sample2 = ClassSample(id: 1)
var sample3 = ClassSample(id: 2)
}
let structSample = StructSample()
let struct1 = structSample
let struct2 = structSample
let struct3 = structSample
let result1 = CFGetRetainCount(structSample.sample1)
let result2 = CFGetRetainCount(structSample.sample2)
let result3 = CFGetRetainCount(structSample.sample3)
print("struct.sample1 참조 횟수 : \(result1)번")
print("struct.sample2 참조 횟수 : \(result2)번")
print("struct.sample3 참조 횟수 : \(result3)번")
--
테스트 결과
5번이라는 결과가 나오게 된다.
사실 처음에는 3번 나올 줄 알았다...
sample1을 예시로 하면
var sample1 = ClassSample(id: 0) // +1 = 1
let structSample = StructSample() // +1 = 2
let struct1 = structSample // +1 = 3
let struct2 = structSample // +1 = 4
let struct3 = structSample // +1 = 5
위와 같이 계산되어 5라는 결과가 나왔다는 걸 알 수 있다.
struct의 경우 참조를 하지 않기 때문에 참조 값을 가져올 수 없었으며
만약 가져오려고 하는 경우 오류가 발생하는 모습을 알 수 있었다.
class는 불릴때마다 reference count가 오르게 되기 때문에
발생할 수 있는 강한 참조와 같은 문제와 성능 개선을 위해 주의할 필요가 있다.
여기서 하나 더!
String은 class일까? Struct일까?
이건 다음 시간에 제대로 알아보도록 하겠다.
참고
https://developer.apple.com/videos/play/wwdc2016/416
https://hasensprung.tistory.com/181
https://betterprogramming.pub/classes-vs-structs-basics-and-memory-management-4707714d82e7
https://www.guru99.com/stack-vs-heap.html
'IOS' 카테고리의 다른 글
iOS) SwiftLint는 무엇일까? (0) | 2024.03.17 |
---|---|
Design) MVVM으로 넘어가는 이유가 뭘까?( vs MVC) (0) | 2024.01.21 |
8/23 프로젝트 회고 (0) | 2023.08.24 |
swift 내용 정리 (0) | 2022.12.13 |
동기 비동기 차이에 대하여 (0) | 2022.11.10 |