본문 바로가기
Swfit/짧은 정리

addTarget(_:action:for:)의 target과 selector

by GGShin 2025. 9. 18.

UIButton이나 UIDatePicker와 같이 UIControl을 상속하는 객체들은 `addTarget(_:action:for:)` 메서드를 사용할 수 있습니다. 이 메서드는 UIControl 객체가 어떠한 동작을 수행하게 할 지 명시해줄 때 이용합니다.

 

메서드 시그니쳐는 아래처럼 생겼습니다.

@MainActor
func addTarget( _ target: Any?,
		action: Selector,
		for controlEvents: UIControl.Event

 

여기에 나온 target과 action의 Selector가 어떠한 것들을 의미하는 지 알아보겠습니다.

 

Target

 Target 파라미터로 넘겨주는 객체는 action이 호출되는 객체입니다. 조금 더 쉽게 말해보면, action 파라미터로 넘겨진 것(Selector)을 찾을 때, target에 넘겨준 객체 내에서 찾는다는 것입니다. 

 

한 번 쉬운 예제로 확인해보겠습니다.

import UIKit

class ViewController: UIViewController {
    private let classA = A()

    private let btn: UIButton = {
        let btn = UIButton()
        btn.setTitle("Button", for: .normal)
        btn.translatesAutoresizingMaskIntoConstraints = false
        return btn
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(btn)        
        //...
    }

    @objc func doSomething() {
        print("ViewController is doing something.")
    }
}

class A {
    @objc func doSomething() {
        print("A is doing something.")
    }
}

 

위에는 두 개의 클래스가 있고, 각 클래스에 동일한 method signature를 가진 'doSomething()' 메서드가 정의되어 있습니다.

이제 btn라는 UIButton 객체에 addTarget을 적용해보겠습니다.

 

class ViewController: UIViewController {
    //...
    override func viewDidLoad() {
        //...
        btn.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
    }

    @objc func doSomething() {
        print("ViewController is doing something.")
    }
}

 

처음에는 target 파라미터에 self를 넘겨주었습니다. self니까, ViewController 객체가 넘어가겠죠? 

이렇게 설정한 다음 앱을 실행시켜서 버튼을 눌러보면, 아래 문구가 출력됩니다.

 

ViewController is doing something.

 

 

이번에는 target 파라미터로 doSomething()이 정의되어 있던 classA 객체를 넘긴 다음 동일하게 실행해보겠습니다.

 

class ViewController: UIViewController {
	private let classA = A()
    //...
    override func viewDidLoad() {
        //...
        btn.addTarget(classA, action: #selector(doSomething), for: .touchUpInside)
    }
}

class A {
    @objc func doSomething() {
        print("A is doing something.")
    }
}

 

이번에는 어떤 결과가 나올까요?

결과를 보면 A 클래스에 정의되어 있는 doSomething()이 호출된다는 것을 알 수 있습니다.

 

A is doing something.

 

 

즉, target으로 넘겨주는 객체에 정의된 method를 찾아서 실행해주는 것입니다. 

일반적으로는 self를 많이 쓰겠지만, 그래도 target이 어떠한 역할인지 잘 알고 쓰는 게 좋겠죠?

 

다시 한번 메서드 시그니쳐를 보면 target이 Optional임을 알 수 있습니다. 그렇다는 것은 nil을 넣을 수 있다는 것이겠죠. 

만약에 nil값을 넘겨주면, UIKit이 responder chain을 조회해서 action에 응답하는 객체를 찾아낸다고 합니다.

 

예시 코드의 경우 어떤 결과가 나오는 지 한 번 보겠습니다.

 

class ViewController: UIViewController {
    //...
    override func viewDidLoad() {
        //...
        btn.addTarget(nil, action: #selector(doSomething), for: .touchUpInside)
    }
}

//Prints: ViewController is doing something.

 

nil을 target으로 넘겨주었더니, UIKit이 적절한 객체를 찾아서 해당되는 메서드를 호출해주었고 그 객체는 ViewController 였습니다. 아마도 responder chain에서 가장 가까운(?) 객체를 찾는 것이 아닌가 싶습니다. 

 

Selector

Selector는 어떠한 메서드가 호출될 지 '메서드의 이름'을 이용해서 구분하는 역할을 합니다. Objective-C에서 사용되던 방식이고, Swift에서는 obj-c의 selector를 만들 때 'Selector'라는 structure를 사용합니다. 'Selector' 인스턴스를 만들기 위해서는 '#selector'라는 표현을 이용해주면 됩니다.

(참고: https://developer.apple.com/documentation/swift/using-objective-c-runtime-features-in-swift#Use-Selectors-to-Arrange-Calls-to-Objective-C-Methods)

 

그런데 모든 형태의 method가 다 selector가 될 수 있는 것은 아닙니다. 아래와 같은 형식의 시그니처를 갖는 메서드만 넘겨줄 수 있습니다!

 

// Valid signatures
@IBAction func doSomething() 
@IBAction func doSomething(sender: UIButton)
@IBAction func doSomething(sender: UIButton, forEvent event: UIEvent)

(참고: https://developer.apple.com/documentation/uikit/uicontrol#Respond-to-user-interaction)

 

다른 형식의 method를 넘겨주면 런타임에서 에러가 발생하게 됩니다. 

조금 더 알아보기

만약 아래와 같이 시그니처는 다르지만 이름은 동일한 method가 있을 때, 어떻게 하면 정확히 원하는 method를 selector로 전달해 줄 수 있을까요?

 

class MyClass {
    @objc func myFunction() {}
    func myFunction(x: String) {}
    let selector: Selector = #selector(myFunction) // ambiguous use of myFunction
}

(유사한 내용: https://stackoverflow.com/a/56753167)

만약에 위와 같이 사용하게 되면, Swift는 어떠한 메서드를 선택해야 할 지 몰라서 에러가 발생하게 됩니다. 

 

그럴 때는 아래처럼 'as' 오퍼레이터를 이용해서 어떤 메서드인지 확실하게 명시해주면 됩니다.

 

#selector(myFunction as () -> Void)

 

Apple 문서에서도 관련된 내용을 찾을 수 있었습니다.

If you need to disambiguate between overloaded functions, use parenthesized expressions along with the 'as' operator to make the #selector expression refer unambiguously to a specific overload.

 

만약에 overload된 메서드들을 구분해야 할 때,  'as' 오퍼레이터와 괄호 표현을 이용해서 명시해주면 된다고 하네요.

 

class MyViewController: UIViewController {
    @objc func buttonTapped() {
        print("Tapped with no parameter")
    }

    @objc func buttonTapped(_ sender: UIButton) {
        print("Tapped with sender: \(sender)")
    }
}

 

buttonTapped라는 동일한 이름의 메서드가 두 개 있을 때, 아래와 같은 방식으로 내가 어떤 것을 사용하고 싶은 지 알려주면 된다는 내용입니다.

 

#selector(buttonTapped as () -> Void)
#selector(buttonTapped as (UIButton) -> Void)

 

 

 

반응형