seong_hye, the developer

iOS) 클래스(Class) vs 구조체(Struct) 제대로 알아보기 본문

IOS

iOS) 클래스(Class) vs 구조체(Struct) 제대로 알아보기

seong_hye 2024. 3. 27.

 

이번에 기술 면접을 보게 되면서 현재 내가 어떤 점이 부족한지 알게되는 기회가 되었다.

그 중에서 클래스와 구조체가 어떤 차이가 있고 어떨 때 사용되는지에 대한 질문을 해주셨다.

형식적으로 차이에 대해 알고 있었지만

실제로 코드에서 활용시 어떻게 차이가 나는지를 알고 있지 않다는 점을 알게 되었다.

https://programming-seonghye.tistory.com/15

 

Swift) 문법 정리 - 클래스(Class) vs 구조체(Struct)

대부분의 언어에서 말하길 클래스는 프로그래밍의 패러다임이라고 말한다. 패러다임 어떤 한 시대 사람들의 견해나 사고를 지배하고 있는 이론적 틀이나 개념의 집합체 클래스를 도입하면서

programming-seonghye.tistory.com

이 경험을 바탕으로 한 번 클래스와 구조체에 대해 제대로 알아보고 정리해보려 한다.


클래스와 구조체에 대해 비교해보자

 

클래스와 구조체는 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
Comments