@objc
와 Selector
, 그리고 바인딩 개념 정리프로그래밍 언어에서 바인딩(binding)은 함수나 메서드 호출이 언제 결정되는지를 말하는 중요한 개념입니다. 크게 정적 바인딩(Static Binding)과 동적 바인딩(Dynamic Binding)으로 나눌 수 있습니다.
@objc
와 동적 바인딩Swift는 기본적으로 정적 바인딩을 사용하는 언어입니다. 즉, Swift에서 메서드 호출은 기본적으로 컴파일 시점에 결정됩니다. 이는 성능을 높이기 위한 방식으로, 호출할 메서드를 미리 결정하므로 정적 디스패치(Static Dispatch)가 이루어집니다.
그러나 Swift에서도 동적 바인딩이 필요한 경우가 있습니다. Objective-C 런타임과 상호작용하거나 런타임에 메서드를 동적으로 결정해야 할 때입니다. 이때 @objc
와 Selector
가 중요한 역할을 합니다.
@objc
의 역할@objc
는 Swift 메서드를 Objective-C 런타임에 노출시키는 키워드입니다. Swift에서 기본적으로는 정적 바인딩을 사용하지만, @objc
키워드를 붙이면 해당 메서드는 Objective-C의 동적 디스패치를 따르게 됩니다. Objective-C는 런타임에 메서드를 동적으로 호출하는데, 이 방식을 사용하기 위해 Swift에서도 @objc
를 사용해야 합니다.
@objc func someMethod() {
print("Objective-C에서 호출할 수 있는 메서드")
}
위 코드에서 @objc
는 Objective-C 런타임에서 이 메서드를 호출할 수 있도록 만듭니다. Objective-C는 기본적으로 모든 메서드를 동적 바인딩으로 처리하므로, 런타임에 호출할 메서드를 결정하게 됩니다.
Selector
의 역할Selector
는 Objective-C에서 특정 메서드를 동적으로 참조하는 방식입니다. 메서드 이름을 런타임에 문자열처럼 참조하는 방식으로, Swift에서는 #selector
구문을 사용합니다.
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
@objc func buttonTapped() {
print("Button was tapped!")
}
여기서 #selector(buttonTapped)
는 런타임에 호출될 메서드를 결정하기 위해 사용됩니다. Objective-C의 메시지 전달 시스템(objc_msgSend()
)을 통해 메서드 호출이 런타임에 이루어지며, 해당 메서드가 @objc
로 선언되어 있으면 Objective-C 스타일의 동적 디스패치가 가능합니다.
Objective-C는 기본적으로 모든 메서드 호출이 동적 바인딩으로 이루어집니다. 이 시스템은 메시지 전달 방식(message-passing)으로 동작하며, 런타임에 어떤 메서드를 호출할지 결정됩니다. Objective-C에서는 메서드 호출이 곧 메시지를 객체에 보내는 방식으로 구현되며, 이 메시지 전달 과정은 objc_msgSend()
라는 함수로 처리됩니다.
@interface Animal : NSObject
- (void)sound;
@end
@implementation Animal
- (void)sound {
NSLog(@"Generic Animal Sound");
}
@end
@interface Dog : Animal
@end
@implementation Dog
- (void)sound {
NSLog(@"Woof");
}
@end
Animal *animal = [[Dog alloc] init];
[animal sound]; // 런타임에 Dog의 sound() 호출 (동적 바인딩)
위 코드에서 [animal sound]
는 동적 바인딩에 의해 Dog
클래스의 sound()
메서드가 호출됩니다. Objective-C의 메시지 전달 시스템은 런타임에 객체의 실제 타입을 확인하고, 해당하는 메서드를 호출하는 구조입니다.
C++에서는 가상 함수(virtual function)를 통해 동적 바인딩을 구현합니다. 가상 함수는 런타임에 객체의 실제 타입을 기반으로 호출될 메서드를 결정합니다. 이를 위해 C++은 가상 함수 테이블(vtable)을 사용하여 객체의 메서드 호출을 관리합니다.
class Animal {
public:
virtual void sound() {
std::cout << "Generic Animal Sound" << std::endl;
}
};
class Dog : public Animal {
public:
void sound() override {
std::cout << "Woof" << std::endl;
}
};
Animal* animal = new Dog();
animal->sound(); // 런타임에 Dog의 sound() 호출 (동적 바인딩)
C++의 가상 함수는 런타임에 객체의 타입을 확인하고, 그에 맞는 메서드를 호출하는 방식으로 동작합니다. virtual
키워드를 통해 메서드가 가상 함수 테이블에 등록되고, 런타임에 이를 통해 올바른 메서드를 선택하게 됩니다.
C++에서의 가상 함수와 Objective-C의 동적 바인딩은 모두 다형성(polymorphism)을 구현하기 위한 동적 디스패치 메커니즘을 사용합니다. 두 언어 모두 객체의 실제 타입에 따라 런타임에 호출될 메서드를 결정합니다.
objc_msgSend()
)을 사용하여, 런타임에 메서드를 동적으로 결정합니다.따라서, C++의 가상 함수와 Objective-C의 동적 디스패치 방식은 개념적으로 동일하며, 두 언어 모두 런타임에 메서드를 결정하고 호출하는 구조를 갖추고 있습니다.
@objc
와 Selector
가 필요한 이유UIKit은 iOS에서 UI 요소를 다루기 위한 기본 프레임워크이며, Objective-C 기반으로 작성되어 있습니다. Swift는 비록 현대적이고 강력한 언어이지만, 여전히 UIKit과 같은 Objective-C 기반 프레임워크와 상호작용해야 합니다. 이 상호작용에서 중요한 역할을 하는 것이 바로 @objc
와 Selector
입니다.
UIKit에서 자주 사용하는 패턴인 Target-Action 패턴, NotificationCenter, Gesture Recognizer, Timer 등은 모두 Objective-C의 메시지 전달 시스템을 기반으로 동작합니다. 이 시스템에서는 메서드를 동적으로 호출할 수 있어야 하고, 이를 위해 Swift에서도 @objc
와 Selector
가 필요합니다.
UIKit에서 버튼이나 슬라이더 같은 UI 요소에 이벤트 처리를 연결할 때 Target-Action 패턴을 사용합니다. 이때, Swift에서 메서드를 동적으로 참조하려면 @objc
와 Selector
가 필수적입니다.
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
@objc func buttonTapped() {
print("Button was tapped!")
}
여기서 @objc
는 해당 메서드를 Objective-C 런타임에 노출시키고, #selector
는 해당 메서드를 런타임에 참조할 수 있도록 합니다. 이를 통해 버튼이 클릭될 때 동적 디스패치를 통해 buttonTapped
메서드가 호출됩니다.
UIKit의 Gesture Recognizer나 Timer도 마찬가지로 Objective-C의 메시지 전달 방식을 따릅니다. Swift에서 이를 사용하려면, 이벤트가 발생할 때 호출할 메서드를 동적으로 참조하기 위해 @objc
와 Selector
가 필요합니다.
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
@objc func handleTap() {
print("View tapped")
}
이 코드는 UITapGestureRecognizer가 터치 이벤트를 감지했을 때, handleTap
메서드를 동적 디스패치로 호출하는 방식입니다. 이를 위해 @objc
가 필수적으로 사용됩니다.
특징 | Swift | Objective-C | C++ |
---|---|---|---|
기본 바인딩 방식 | 정적 바인딩 (컴파일 시점 결정) | 동적 바인딩 (런타임 시점 결정) | 정적 바인딩 (기본), 가상 함수로 동적 바인딩 지원 |
동적 바인딩 적용 방법 | @objc 와 dynamic 사용 | 기본적으로 모든 메서드 동적 바인딩 | virtual 키워드를 사용하여 동적 바인딩 지원 |
메서드 호출 방식 | 정적 디스패치 기본, @objc 로 동적 디스패치 | 메시지 전달 시스템(objc_msgSend() ) | 가상 함수 테이블(vtable) 사용 |
다형성 지원 | 가능 (@objc 로 다형성 구현) | 가능 (기본적으로 다형성 지원) | 가능 (virtual 키워드로 다형성 구현) |
Swift는 기본적으로 정적 바인딩을 사용하는 언어지만, UIKit과 같은 Objective-C 기반 프레임워크와 상호작용할 때는 동적 바인딩이 필요합니다. 이를 위해 Swift에서는 @objc
키워드와 Selector
를 사용하여 런타임에 메서드를 동적으로 호출할 수 있도록 합니다.
UIKit에서 사용하는 Target-Action 패턴, Gesture Recognizer, Timer, NotificationCenter 등 다양한 기능이 Objective-C의 동적 디스패치를 필요로 하기 때문에, Swift 개발자도 이 개념을 이해하고 적절히 사용할 필요가 있습니다.
Objective-C의 동적 바인딩은 C++의 가상 함수(virtual function)와 개념적으로 동일합니다. 두 언어 모두 런타임에 객체의 실제 타입에 따라 호출할 메서드를 결정하는 동적 디스패치 방식을 사용하여 다형성을 구현합니다. C++의 가상 함수는 가상 함수 테이블(vtable)을 사용하고, Objective-C는 메시지 전달 시스템(objc_msgSend()
)을 사용하지만, 다형성(polymorphism)을 제공하는 메커니즘은 동일합니다.
이러한 동적 디스패치와 바인딩 방식은 Swift와 Objective-C의 상호 운용성, 그리고 UIKit과 같은 기존 프레임워크와의 호환성을 보장하기 위해 중요한 요소입니다. Swift는 정적 바인딩을 기본으로 하여 성능을 최적화하면서도, 필요한 경우 동적 바인딩을 통해 다양한 상황에 유연하게 대응할 수 있도록 설계되었습니다.