SH0123 / BookAndMe

[책과 나의 조각] iOS 앱
MIT License
1 stars 0 forks source link

[기록] Repository Pattern을 사용한 것에 대하여 #2

Open SH0123 opened 9 months ago

SH0123 commented 9 months ago

배경

과정

고민사항

내 생각 및 여전한 고민 사항들

DataSource

Singleton vs 공유 인스턴스

Usecase

SH0123 commented 9 months ago

Reference

오브젝트

Repository Pattern

Struct vs Class

SH0123 commented 8 months ago

Reference Counting으로 인한 오버헤드 간단 테스트

WWDC 영상에서 Struct와 Class를 사용하는데 있어서 Reference Count로 인해 오버헤드가 발생하고 성능에서 차이가 발생함을 알 수 있었다. 그 중에서도 heap 영역에 할당되는 property를 가지고 있는 객체의 경우 Struct와 Class중 어떤 것으로 객체를 생성하면 좋을지에 대한 내용이 있었고 이에 대해 직접 확인하고 싶었다.

아래가 WWDC 영상 내용이다.

Struct로 구현할 경우 Label 객체가 복사되기 때문에 heap 영역에 할당되는 Property인 font에 대해서 reference counting 연산을 Class로 구현하는 경우보다 두배는 더 해줘야해서 오버헤드가 발생한다는 것이다.

class InnerClass {
    var id: Int
    var name: String
}

class OuterClassPropertyOne {
    let classInsatnce: InnerClass

    deinit {
        print("deinit outer class")
}

struct OuterStructPropertyOne {
    let classInstacne: InnerClass
}

레퍼런스 카운트가 실제로 어떻게 변하는지 아래의 코드를 통해 살펴보자

Struct 객체

var innerClass = InnerClass(id: 1, name: "class")
print(CFGetRetainCount(innerClass))
var instance1: OuterStructPropertyOne? = OuterStructPropertyOne(classInstacne: innerClass) // struct 객체 생성
print(CFGetRetainCount(innerClass))
var instance2: OuterStructPropertyOne? = instance1 // struct copy
print(CFGetRetainCount(innerClass))
instance1 = nil // deinit
print(CFGetRetainCount(innerClass))
instance2 = nil // deinit
print(CFGetRetainCount(innerClass))
2
3 // instance1
4 // instance2
3 // instance1 deinit
2 // instance2 deinit

Class 객체

var innerClass = InnerClass(id: 1, name: "class")
print(CFGetRetainCount(innerClass))
var instance3: OuterClassPropertyOne? = OuterClassPropertyOne(classInsatnce: innerClass) // class 객체 생성
print(CFGetRetainCount(innerClass))
var instance4: OuterClassPropertyOne? = instance3 // class reference copy
print(CFGetRetainCount(innerClass))
instance3 = nil // deinit
print(CFGetRetainCount(innerClass))
instance4 = nil // deinit
print(CFGetRetainCount(innerClass))
2
3 // instance3
3 // instance4 copy reference : retain count overhead 없음
3 // instance 3 deinit
deinit outer class 
2 // instance 4 deinit

위의 코드와 실행결과를 reference count 중심으로 비교해보자.

struct : 2->3->4->3->2 class : 2->3->3->3->2

struct에서 reference count가 1만큼씩 더 증가했다가 감소한다. 이 만큼 retain, release 작업이 일어났을 것이고 이 작업의 동기화를 위해서 lock과 같은 작업들이 발생했을 것이다. 즉 이로인한 overhead가 발생했을것으로 보인다.

property가 static let으로 선언된 경우, Struct 객체

struct OuterStructPropertyOne {
    static let classInstacne: InnerClass = .init(id: 3, name: "three")
}

var instance1: OuterStructPropertyOne? = OuterStructPropertyOne()
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
var instance2: OuterStructPropertyOne? = instance1
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
instance1 = nil
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
instance2 = nil
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
2
2
2
2

property가 static let으로 선언된 경우, Class 객체

class OuterClassPropertyOne {
    static let classInstacne: InnerClass = .init(id: 3, name: "three")

    deinit {
        print("deinit outer class")
    }
}

var instance3: OuterClassPropertyOne? = OuterClassPropertyOne()
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
var instance4: OuterClassPropertyOne? = instance3
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
instance3 = nil
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
instance4 = nil
print(CFGetRetainCount(OuterStructPropertyOne.classInstacne))
2
2
2
deinit outer class
2

이번에는 이전에 했던 실험과 다른 결과를 보인다. static으로 선언한 레퍼런스 변수가 프로그램이 종료될 때 까지 할당 해제되지 않고 heap 영역에 존재하기 때문에 위와같이 struct나 class나 같은 결과를 보인다 근데 static으로 선언한 레퍼런스 변수는 reference counting을 하지 않는것인가? 라는 궁금증이 생기는 결과였다.

결론

instance의 property로 heap 영역에 할당하는 인자가 많아질수록 struct로 구현한 instance에는 class로 구현한 instance보다 reference count retain, release 작업으로 인한 overhead가 훨씬 많이 발생한다. 하지만 property가 static let으로 선언되어 프로그램의 생명 주기와 함께 생성과 소멸을 함께하는 객체라면 struct나 class나 동일하다.

즉, 무작정 struct가 class보다 낫다는 판단 보다는 상황에 따라 비교해보며 성능을 향상시킬 수 있는 코드를 작성하면 좋다