⇒ 두 클로저를 구분하는 기준은 클로저가 언제 실행되느냐
비동기 함수의 경우 클로저를 비동기 작업으로 호출해두고 함수가 먼저 종료되는 경우가 많음
클로저는 (필요한 경우) 자신이 호출된 함수를 캡처하여 해당 함수 내에서 선언된 데이터들을 사용 (캡처)
→ 함수가 아니라 다른 곳에서 클로저를 사용하더라도 동일함.
❗️ 클로저가 비동기 작업을 실행하기 전에 함수가 종료된다면?
→ 클로저가 캡처할 수 있는 실체가 사라지므로 작업이 불가능해짐
⇒ 이러한 불상사를 막기 위해 비동기 함수 내에서 escaping 클로저를 사용하도록 강제한 것이 아닐까.
(non-escaping 클로저를 사용하면 컴파일 에러가 발생)
Understanding Swift Performance - WWDC16

클래스는 정체성을 나타내거나 간접 저장(indirect storage)에 유리
→ 두 특징이 필요 없다면 성능 측면에서는 구조체를 사용하는 것이 유리
+) 구조체는 의도치 않은 상태 공유에서 자유로움
(클래스처럼 전혀 다른 곳에서 참조하지 않으므로 값의 변경을 알기 쉬움)


Static Dispatch
: 어떤 동작인지 이미 알고 있으므로 런타임에 바로 함수 실행 가능
: 인라이닝(inline)과 같은 최적화 가능
Dynamic Dispatch
: 런타임 시점에 어떻게 동작할지 인지
: 인라이닝과 같은 최적화 불가
1개의 Static 디스패치와 1개의 Dynamic 디스패치를 비교하면 성능에 큰 차이점은 없음
→ 최적화가 가능 여부에 따라 전체적인 코드의 성능에 차이
(최적화가 가능한 Static 디스패치가 더 빠름)
클래스는 기본적으로 모든 메소드가 Dynamic 디스패치로 동작하지만, final 키워드로 상속이 없음을 명시한다면 내부 메소드가 Static으로 동작할 수 있음

➡︎ 위와 같은 특징들을 고려하여 높은 성능의 코드를 구현할 수 있도록 고민하기

프로토콜 필수 함수의 (해당 프로토콜을 채택한)타입별 구현을 담아두는 테이블
프로토콜 타입의 배열을 만들 때 사용하는 컨테이너
protocol Drawable { func draw() }
struct Point: Drawable {
var x, y: Double
func draw() { ... }
}
struct Line: Drawable {
var x1, y1, x2, y2: Double
func draw() { ... }
}
위와 같은 코드에서 [Drawable] 타입의 배열을 만들고 싶음
→ Swift는 한 배열에서 모든 요소들이 같은 형식으로 저장되기를 원함. 서로 크기가 다른 객체들을 어떻게 한 배열에 담을 수 있을까?
⇒ 이때 Existential Container를 사용
가. 값의 저장
프로토콜 타입의 배열은 Existential Container 형태로 저장
컨테이너의 일부 공간은 initial Value Buffer로 존재
인스턴스의 크기에 따라 컨테이너 내부에 직접적으로 저장될지, 주소 형태로 저장될지 결정됨
valueBuffer보다 작을 경우: 직접 저장valueBuffer보다 클 경우
파란 네모는 valueBuffer 영역을 의미
Point 인스턴스valueBuffer보다 작기 때문에 컨테이너 내부에 바로 저장Line 인스턴스valueBuffer보다 크기 때문에 인스턴스의 주소를 저장 나. VWT(Value Witness Table)
Existential Container는 VWT를 통해 자신에게 저장된 값의 수명을 관리
→ VWT에는 값의 수명 관리를 위한 함수들이 저장되어 있음
→ 저장된 타입 별로 VWT를 가짐(타입별 크기가 다르기 때문)
allocate : 메모리 할당
→ allocate 함수는 힙 메모리에 값이 저장될 공간을 할당하고, 컨테이너에 주소를 저장
copy : 값 복사
→ allocate가 할당한 공간에 실제 인스턴스의 값을 복사
→ 혹은, 컨테이너의 valueBuffer에 직접적으로 복사
destruct: 값 삭제

→ 객체의 수명이 다 했을 때 호출
→ 복사했던 값들을 삭제
→ (valueBuffer보다 큰 경우) 값으로의 RC를 감소
deallocate: 할당 해제

→ 힙 메모리에 할당했던 공간을 해제
크기가 작은 값일 때 (valueBuffer보다 작을 떄)
→ 비교적 빠른 코드
크기가 큰 값일 때 (valueBuffer보다 클 떄)
프로토콜 타입보다 정적인 다형성
func foo<T: Drawable>(local :T) {
bar(local)
}
func bar<T: Drawable>(local: T) { ... }
let point = Point()
foo(point) // 여기서 T는 Point 타입으로만 사용
→ 호출 시 사용한 하나의 타입만을 사용함
지역 변수로의 저장
프로토콜 타입처럼 valueBuffer를 두고 버퍼보다 작으면 바로 저장, 크면 힙에 있는 주소를 저장
foo(Point), foo(Line)이라면, Point타입 버전, Line 타입 버전과 같이 구분)🧐 타입이 많아지면 버전이 많아지니까 코드가 커지는 게 아닌지?
⇒ 당장 사용하지 않는 코드는 컴파일러가 공격적을 최적화하므로 최적화 후 코드가 작아짐!
➡︎ 런타임에 동적인 경우를 최소화하는(가변성을 최소화하는) 알맞은 타입을 골라 사용할 것!