iOS/iOS 지식

[Swift] Struct와 Class (1) - Class 안에 Class, Struct 안에 Class

홍루피 2024. 10. 5. 15:53

https://blog.datepop.co.kr/ios-interview-2024-1/  

위의 글을 보고 해당 주제에 대해 작성하는 글임을 밝힘

 

아래코드는 해당 블로그에 작성된 코드이고, 약간 수정해서 공부해봄

구조체와 클래스의 차이가 뭐에요? 라고 물어보면 한가지만 정확하게 말할 수 있음

구조체는 값타입이고 스택에 저장됨. 클래스는 참조타입이고 힙에 저장됨.

그러면 또 이런 질문을 해보겠음. 그럼 클래스 안에 구조체가 있으면? 구조체 안에 클래스가 있으면 그건 뭔가요?

일단 1탄에서는 클래스 - 클래스, 구조체 - 클래스에 대해서 다뤄보도록 하겠음

final class EmptyClass {}

struct HugeStruct {
    var emptyClass = EmptyClass()
    var emptyClass2 = EmptyClass()
    var emptyClass3 = EmptyClass()
    var emptyClass4 = EmptyClass()
    var emptyClass5 = EmptyClass()
    var emptyClass6 = EmptyClass()
    var emptyClass7 = EmptyClass()
    var emptyClass8 = EmptyClass()
    var emptyClass9 = EmptyClass()
    var emptyClass10 = EmptyClass()
}

class HugeClass {
    var emptyClass = EmptyClass()
    var emptyClass2 = EmptyClass()
    var emptyClass3 = EmptyClass()
    var emptyClass4 = EmptyClass()
    var emptyClass5 = EmptyClass()
    var emptyClass6 = EmptyClass()
    var emptyClass7 = EmptyClass()
    var emptyClass8 = EmptyClass()
    var emptyClass9 = EmptyClass()
    var emptyClass10 = EmptyClass()
}

실행시간 측정

func createBunchOfReferencesOfClass() {
    let clock = ContinuousClock()
    
    let time = clock.measure {
        var array = [HugeClass]()
        let object = HugeClass()
        for _ in 0..<100000000 {
            array.append(object)
        }
    }
    
    print(#function, time)
}

func createBunchOfCopiesOfStruct() {
    let clock = ContinuousClock()

    let time = clock.measure {
        var array = [HugeStruct]()
        let object = HugeStruct()
        for _ in 0..<100000000 {
            array.append(object)
        }
    }
    
    print(#function, time)
}

createBunchOfReferencesOfClass() 20.57859175 seconds
createBunchOfCopiesOfStruct() 32.240995708999996 seconds

- 클래스 안에 클래스 구조: 20초

- 구조체 안에 클래스 구조: 32초

클래스 안에 클래스가 들어있는 구조가 더 빠르게 실행되는 것을 확인할 수 있음

근데 좀 이상하다는 생각이 들었음. 클래스와 구조체의 할당되는 영역에 대해서 확인해보면

 

클래스 

- 힙을 사용하고, 힙은 동적할당을 통해 공간을 제공함. 이 때 힙에서 사용하지 않은 공간을 찾고 삽입하는 과정에서 시간이 더 걸림

구조체

- 스택을 사용. LIFO의 단순한 구조. 정적 할당이며 스택 포인터로 메모리의 할당과 해제가 이루어짐. O(1)의 시간복잡도가 걸림 

 

통상적으로 클래스가 힙을 사용하고, 비용이 비싸면서 속도가 더 느리다고 알고 있음.

그러면 위의 결과는 잘못된게 아닌가 생각할 수 있음. 근데 구조체를 사용하면 과연 힙 할당이 안 일어날지 생각해봐야함.

구조체 안에 참조타입을 프로퍼티로 가지고 있다면 Reference Counting이 발생함.

이 레퍼런스들은 구조체에 있는 레퍼랜스 개수에 비례하는데, 하나보다 많은 레퍼런스를 가지게 된다고 하면?

동일 상황일 경우 클래스보다 레퍼런스 카운팅 오버헤드가 더 많이 걸리게 되기 때문에 시간이 더 걸리는 것임

 

WWDC에서 소개한 사진을 보면 참조타입에 대한 레퍼런스 카운트가 구조체 생성시마다 늘어나는 것을 볼 수 있음

여기서 몰랐던 사실 한가지는 String도 참조카운팅이 발생한다는 것인데 실제 문자 내용을 힙에 간접 저장한다고 함

이 사실을 위의 구조체 안에 클래스가 있는 코드에 대입해보면

현재 구조체 개수가 1억개, 1억개의 구조체 * 10개의 레퍼런스 카운트 => 10억개의 레퍼런스 카운트

 

그러면 클래스 안에 구조체가 있는 코드에 대입해보면

현재 클래스 개수가 1억개, 1억개의 클래스 * 1개의 레퍼런스 카운트 => 1억개의 레퍼런스 카운트

그렇기 때문에 반드시 구조체가 빠를거라는 생각은 옳지 않다는 것이고,

구조체를 사용할 때 참조 타입 프로퍼티를 최소한으로 줄이는 개선이 필요하다고 함

 

두번째는 레퍼런스 카운트에 대한 코드임

final class EmptyClass {}

final class ClassOfClasses {
    let emptyClass = EmptyClass()
    let emptyClass2 = EmptyClass()
    let emptyClass3 = EmptyClass()
}

let classOfClasses = ClassOfClasses()
let reference = classOfClasses
let reference2 = classOfClasses
let reference3 = classOfClasses
print(CFGetRetainCount(classOfClasses))
print(CFGetRetainCount(classOfClasses.emptyClass))
print(CFGetRetainCount(classOfClasses.emptyClass2))
print(CFGetRetainCount(classOfClasses.emptyClass3))

뭐가 출력될지 생각해봄

1. 인스턴스 생성으로 인해 classOfClasses의 rc 1 증가

2. 다른 변수에 인스턴스가 대입으로 rc 3 증가(reference1~3)

3. CFGetRetainCount 함수를 사용함으로써 1 증가

총 5개

 

그러면 classOfClasses의 RC는 5인데, 그럼 그 안의 클래스들은 어떨까?

5
2
2
2

결과가 2로 나오는 것을 알 수 있음. 프린트되면서 올라간 거 치면 1이란 이야기임

여기서 알 수 있는 건 감싸고 있는 클래스의 RC가 올라가도, 프로퍼티의  RC는 별도로 올라간다는 것임

 

emptyClass프로퍼티의 참조카운트를 올리고, 프린트를 다시 찍어봄

let reference4 = classOfClasses.emptyClass
print(CFGetRetainCount(classOfClasses))
print(CFGetRetainCount(classOfClasses.emptyClass))

이제 어떻게 나올까보면 기존 classOfClasses의 참조카운트는 여전히 5이지만

emptyClass의 참조카운트는 3으로 증가한 것을 볼 수 있음

 

참고

https://blog.datepop.co.kr/ios-interview-2024-1/  

https://hasensprung.tistory.com/181

https://corykim0829.github.io/swift/Understanding-Swift-Performance/#

https://developer.apple.com/videos/play/wwdc2016/416/

https://ios-development.tistory.com/692