이전에 Method Swizzling 에 대해 알아본 적이 있다.
[Swift] Method Swizzling
Swift에서 같은 이름의 Method를 호출하면서 상황에 따라 기능을 다르게 할 수 있다. 이를 Method Swizzling이라고 하는데, 간단히 알아보자. 예를들어, calFunction을 호출하는데 어쩔 때는 + 기능을 수행하
littlemoom.tistory.com
함수의 포인터를 활용해서 함수가 호출하는 코드의 위치를 바꾸는 방식이었는데
Objective-C와 관련이 있다고 했다.
오늘은 Objective-C와 Swift에서 함수가 호출되는 방식과 어떻게 활용해야하는지 알아보자.
Swift에서 함수가 호출되는 방식은 총 세가지다.
1. Static Dispatch
2. Dynamic Dispatch
3. vtable
1. Static Dispatch
Dispatch라는 개념을 먼저 이해하자.
Dispatch는 작업, 메세지, 이벤트 등의 리소스를 할당하는 과정을 의미한다.
그렇다면 Static Dispatch는 정적 할당이라는 의미이다.
컴파일 타임에 호출될 함수의 위치를 결정하게 된다.
호출 위치가 고정되어 있다는 것은 런타임에 함수를 결정할 필요가 없기 때문에 함수의 실행 속도가 증가한다.
어떤 경우에 Static Dispatch가 적용될지 알아보자.
Value Type
주로 Value Type의 Method에 적용되는데, struct나 enum이다.
struct ValueType {
func staticDispatchFunction() {
// code...
}
}
해당 코드를 컴파일 한다면, 컴파일러는 상속이 발생할 일이 없는 구조체의 함수이기 때문에
컴파일 순간에 함수 코드의 호출 정확한 위치가 정해지게 된다.
Final
Static Dispatch가 적용되는 두번째 경우는 Reference Type의 Method에 final 키워드를 사용할 때이다.
final 키워드를 사용하면, 상속시에 override 할 수 없기 때문에 Static Dispatch가 적용된다.
class ReferenceType {
final func staticDispatchFunction() {
// code...
}
}
Protocol Extension
세번째로는 Protocol Extension이 있다.
protocol MethodProtocol {
func staticDispatchFunction()
}
extension MethodProtocol {
func staticDispatchFunction() {
// code...
}
}
이처럼 Extension으로 작성된 Method는 Static Dispatch가 적용된니다.
단, 채택에 의해 정의된 Method는 Dynamic Dispatch가 적용될 수도 있다.
etc. 와 공통점
Global Function의 경우에도 Static Dispatch가 적용된다.
Reference Type임에도 private, fileprivate Method의 경우와
의미상 사용이 특정 모듈 내로 제한된 경우에 Static Dispatch가 작용될 수도 있다.
Class의 Extension을 통해 정의된 메소드도 Static Dispatch가 적용되는데,
일반적으로는 override 가능 여부로 어떤 방식이 적용될지 쉽게 유추해볼 수 있다.
2. Dynamic Dispatch
Dynamic Dispatch는 컴파일이 아닌 런타임에 함수 호출이 결정되는 방식이다.
protocol와 class의 상속 등을 가능하게 하는 개념 중 하나이다.
단, 함수 호출 위치가 정확하게 정해지지 않았다는 의미이므로,
성능적으로는 Static Dispatch에 비해서 런타임에 함수를 찾는 오버헤드가 발생한다.
Objective-C가 주로 Dynamic Dispatch을 사용한다.
Objective-C는 Method 호출할 때 Method를 식별하기 위해 Selector를 사용한다.
Selector를 사용하여 해당 Selector 맞는 메소드 구현(Method Implementation(IMP))을 찾는다.
이 과정에서 Method Dispatch Table을 활용하게 된다.
Method Dispatch Table는 Class와 상위 Class의 모든 Method에 대한 참조를 가지고 있다.
이 table에서 실제 구현 코드를 찾아 실행하게 된다.
아래 링크는 IMP에 대한 문서이다.
IMP | Apple Developer Documentation
A pointer to the start of a method implementation.
developer.apple.com
잠시 코드로 예시를 들어보자.
// ObjectiveC_Class.h
#import <Foundation/Foundation.h>
@interface ObjectiveC_Class : NSObject
- (void)dynamicDispatchFunction;
@end
// ObjectiveC_Class.m
#import "ObjectiveC_Class.h"
@implementation ObjectiveC_Class
- (void)dynamicDispatchFunction {
// code ..
}
@end
이와 같은 Objective-C 코드가 있다고 하자
이때 클래스 변수 선언와 함수 호출은 아래와 같이 작성되 것이다.
ObjectiveC_Class *object = [[ObjectiveC_Class alloc] init];
[object dynamicDispatchFunction];
이러한 함수 호출은 실제 런타임에
objc_msgSend(object, @selector(dynamicDispatchFunction:), nil)
로 변환되어 호출된다. selector를 사용하여 실제 IMP를 찾는 것이다.
objc_msgSend | Apple Developer Documentation
Sends a message with a simple return value to an instance of a class.
developer.apple.com
만약 구현의 포인터가 가리키는 위치를 바꾸면 다른 구현을 호출하게 되는 것이고,
이러한 방식을 바로 Method Swizzling인 것이다.
다시 본 주제로 돌아오서 Swift에서는 어떤 경우에 Dynamic Dispatch를 사용할까?
Reference Type
대표적으로는 Class의 Method가 있다.
클래스의 상속과 Method Overriding에서 중요한 개념이다.
class ReferenceType {
func dynamicDispatchFunction() {
// code...
}
}
Protocol
Protocol Method도 Dynamic Dispatch를 사용한다.
채택한 인스턴스 및 타입에 따라 함수의 구현이 달라진다고 생각해보면 이해하기 쉽다.
Protocol은 Protocol Witness Table을 사용하게 되며 채택한 인스턴스 및 구조체에 구현된 메소드를 포인터로 지정한다.
Objective-C API
앞서 설명했듯 Objective-C는 주로 Dynamic Dispatch를 사용하고 Swift는 Objective-C 런타임을 활용한다.
따라서 Objective-C API를 사용할 때 Dynamic Dispatch를 적용한다.
Selector를 활용하여 함수의 구현을 지정하는 방식으로 흔히 볼 수 있는 코드이다.
uiButton.addTarget(self, action: #selector(objcFunction(_:)), for: .touchUpInside)
해당 코드를 사용했다면, 분명 함수 앞에는 @objc annotations이 추가되었을 것이다.
@objc func objcFunction(_ sender: UIButton) {
// code...
}
etc.
의도적으로 @objc annotations을 사용한 경우 Dynamic Dispatch가 적용될 수 있다.
기본적으로 Static Dispatch가 적용되는 Class의 extension에서도
@objc을 사용할 경우나 메소드를 override하는 경우엔 Dynamic Dispatch가 적용될 수 있다.
KVO(Key-Value Obsering) 등 Objective-C 런타임에 의존된 도구 및 기술을 사용한다면,
Dynamic Dispatch가 적용될 수 있다.
3. vtable
해당 개념에 대해 알아보다보니 vtable에 대해 접하게 되었다.
Swift에서 사용하는 vtable은 Virtual Method Table로 함수 포인터를 가진 테이블이다.
Dynamic Dispatch로 런타임에 호출되는 함수를 찾을 때이 vtable를 참조하게 된다.
vtable 자체가 메소드 호출에 있어 오버헤드를 최적화하기도 한다.
마무리
중요한 점은 언제 함수의 호출 위치가 의미상 고정되는가 이다.
물론 어떤 방식을 사용할지는 컴파일러가 최적화 과정에서 최종 결정하는 것이다.
하지만 일반적으로 적용되는 경우를 알고 있다면,
적절한 타입과 설계로 상황에 맞는 적용 방식을 이끌어내어 성능을 최적화할 수 있을 것이다.
참고한 글 + 더 알아보면 좋은 글
Swift Method Dispatching — a summary of my talk at Swift Warsaw
Two weeks ago, I attended Swift Warsaw as a speaker, holding a presentation on “Swift Runtime — Swift Method Dispatching”. I promised to summarise the talk in an article, so hereby I keep my promise.
blog.allegro.tech
'iOS > Swift' 카테고리의 다른 글
[Swift] Swift 5.10 Release (0) | 2024.03.25 |
---|---|
[Swift] Protocol Composition (0) | 2024.01.09 |
[Swift] HOF(Higher Order Function) (2) (0) | 2022.08.03 |
[Swift] HOF(Higher Order Function) (1) (0) | 2022.08.03 |
[Swift] await, async (0) | 2022.06.30 |