https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#protocols
고성능 Swift 코드는 Swift 프로그램의 품질을 개선하고 코드를 오류가 덜 발생하고 읽기 쉽게 만드는 데 도움을 준다고 한다.
한마디로 컴파일 시에 성능을 향상시키기 위해 코드 작성 시에 개발자가 해줄 수 있는 부분에 대해 애플이 문서로 작성해 알려주고 있는 거다.
Swift에서 왜 클래스보다 구조체를 쓰라고 하는지, 물론 원본을 참조하게 되면 발생하는 문제들도 있지만 그 외에의 이유에 대해서 알게 되어 그동안 내가 작성했던 그런 키워드, 코드 하나하나가 모든 부분에 영향을 주고 다 의미가 있구나를 다시금 알게 됐다.그리고 이 부분들을 WWDC에서 이미 알려주고 있다니, 한국인이 알려주면 안될까..?ㅠㅠ
또,, 이런 부분까지 고려해야 한다니.. 끊임없는 공부.. 언제까지 공부해야 되나..? 으... toe na wal...
아모튼
Enable Optimization
Swift 코드는 최적화할 수 있다.
최적화하기 위해 처음 할 일이 활성화하는 건데, 그 단계를 3가지로 제공한다.
1. -Onone : 최소한의 최적화 + 디버그 정보 보존
2. -O : production 코드를 위한 것 + 적극적인 최적화 + 디버그 정보 손실
3. -Osize : 성능보다 코드 크기 우선
Build Setting에서 Optimization을 검색해서 설정을 확인해볼 수 있는데 기본값으로 설정이 되어 있어 따로 설정해주지 않아도 된다.
개발 시에는 디버그 정보가 중요하지만 출시 후에는 디버그 영역이 필요 없어서 날려도 된다.
testflight를 통해 간혹가다 디버그 영역을 통해서 디버깅 정보 수집하는 경우도 있다.
Whole Module Optimization 전체 모듈 최적화
WMO는 컴파일 시에 어떻게 성능을 향상시켜 볼 수 있을까에 대한 지점이다.
원래 컴파일 시에 각각 파일 단위로 컴파일을 한다. 그런데 WMO를 적용하면 하나의 전체 프로젝트를 하나로 인식해서 컴파일한다.
이 과정에서 각각의 파일이 서로 어떻게 의존하고 관계되어 있는지를 컴파일 과정을 통해 확인을 할 거다.
예를 들어, Diary 프로젝트에서 HomeVC과 WriteVC이 서로 어떤 연관관계가 있는지 파악하는 거라고 볼 수 있다.
이 경우 컴파일 시간이 오래 걸리지만 더 빠르게 실행될 수 있다는 장점이 있다.
<Reduce Dynamic Dispatch>
런타임 성능을 향상시키기 위한 방법으로는 class와 관련된 동적 디스패치 줄이기가 있다.
동적 디스패치를 이해하기 전에
우선 우리는 프로그램의 해당 연산이 어떤 메서드에 의해 언제 어디서 호출되는지 결정할 수 있다는 것을 알아야 한다.
컴파일 시에 호출되는지 / 런타임 시에 호출되는지를 결정할 수가 있는데 우리는 이걸 Method Dispatch라고 부른다.
긍까, 그 코드의 실행이 컴파일 타임에 이루어지는지 / 런타임에 이루어지는지에 따라 최적화가 결정된다고 보면 된다.
2가지 종류가 있다.
-1️⃣ 컴파일 시에 함수가 호출되는 것이 Static Dispatch (Direct - call)
-2️⃣ 런타임 시에 함수가 호출되는 것이 Dynamic Dispatch (Indirect - call)
Static Dispatch 는 기본적으로 런타임에서 판단할 필요가 없어서 성능 상 이점이 있고, 값/참조 타입 모두 지원을 한다.
열거형/구조체 등은 이에 속한다고 보면 된다.
Dynamic Dispatch 는 런타임 때 어떤 메서드가 실행이 결정되기 때문에 성능 상 오버헤드가 발생한다.
왜 컴파일 때 결정을 못하냐면, 재정의 때문이다. 재정의의 가능성으로 인해
- 상위 클래스의 메소드를 참조해야 하는지
- 하위 클래스의 메소드를 참조해야 하는지
그 확인 작업을 런타임에서 확인하게 되는 거다.
참조타입만 지원하고 클래스의 경우 이에 속한다.
그리고 동적 디스패치 줄이자는 것은 바로 런타임 시 함수 호출함에 있어 이 동적 디스패치를 줄여 런타임 성능을 향상시키자는 것을 말한다.
예시를 통해 설명하자면,
여기 HomeVC의 configureUI()는
BaseVC에서 선언되어 HomeVC이 BaseVC을 상속받고 있기 때문에 오버라이딩 후 기능을 추가해 재정의해서 사용하고 있다.
그렇다면, BaseVC과 HomeVC이 서로 참조하고 연결되는 관계이기에
컴파일 시에는 configureUI()라는 메소드가 해당 함수가 어디서 호출되는지 알 수 없고, 런타임 시에 파악되어 내부 성능에 영향을 주게 된다.
따라서, 우리는 런타임 시에 결정되는 이 configureUI()가 만약 쓰이지 않는다면
- private을 붙이거나,
- class 앞에 final을 붙여서 static하게 바꿔 성능을 향상시킬 수 있게끔해야 한다.
이렇게 우리는 WMO를 위해 이미 프로젝트에서 몇 가지 코드에서 적용해주고 있는 게 있다.
첫 번째로, Access Control.
<외부 파일에서 액세스할 필요가 없는 경우 private / fileprivate 사용>
프로퍼티나 메소드 선언 시에 fileprivate이나 private을 앞에 붙여 접근 제어를 거는 거다.
이걸 통해서 컴파일 시에 한 줄 한 줄의 코드를 읽으며 다른 파일과 각각의 연관관계가 있는지 인식하고, private일 경우에 서로 고려하지 않아도 되기에 WMO 시에 내부적으로 성능이 좋아진다.
+ 추가로, 모듈과 모듈 간의 관계에서는 internal을 통해 선언되었다면 자연스럽게 외부에서 접근이 불가능하기에 컴파일러가 알아서 모듈 외부에서 final이라고 유추한다.
두 번째로, final.
<재정의할 필요가 없다는 것을 알고 있는 경우 final 사용해 상속을 방지해 사용>
상속하지 않는 클래스 앞에 final 키워드를 붙여서 static dispatch로 사용하는 방법이다.
private과 마찬가지로 해당 클래스의 메소드의 재정의가 없다는 것을 컴파일 시에 인식할 수 있게 된다.
<Protocol>
프로토콜과 관련해서도 적혀있고, 그동안 코드에도 쓰고 있던 부분이라 추가해서 적자면,
프로토콜을 선언 시에 어느 클래스까지 적용할 수 있게 할 건지 제한을 둬야 한다.
문서의 말을 빌리자면, 프로토콜 채택을 클래스로만 제한할 수 있는데, 클래스 전용으로 표시하는 것의 이점이 컴파일러가 클래스만 프로토콜을 채택한다는 지식을 기반으로 프로그램을 최적화할 수 있다는 것이다. 만약, 이 지식이 없으면 컴파일러는 구조체도 프로토콜을 채택할 수 있다고 가정해야 해서 비용이 많이 들 수 있는 중요치 않은 구조체를 유지하거나 해제해야 하는 준비가 필요하다는 거다.
예를 들어, 내가 SendCategoryDelegate라는 프로토콜을 만들고, 이 프로토콜은 OnboardingViewController만 채택할 수 있다고 제한을 두겠다며 아래와 같이 써야 한다는 거다.
또는 클래스만 채택할 수 있다고 한다면 아래처럼 써야 한다.
이렇게 클래스에게 제한을 두는 이유는, 클래스에는 ARC라는 개념이 적용되기 때문인 것 같다. 참조의 개념, 메모리 관리 개념이 있어서.. 그런 것 같다. ARC.. 어려웡...
'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글
[Swift] Codable (2) | 2022.08.29 |
---|---|
[Swift] Error Handling (4) | 2022.08.26 |
[Swift] Realm 진짜.그냥.간단.정리 (0) | 2022.08.24 |
[iOS] Realm 기본 설계 + Realm에 저장하고 가져오기 (0) | 2022.08.23 |
[iOS] loadView() (0) | 2022.08.21 |