[ swift ] addAction 에 대하여

sonny·2024년 12월 23일
3

TIL

목록 보기
80/133

팀원분이 addTarget말고 addAction으로 써보는게 어떠냐 라고 스쳐지나간 듯 들었던 이야기가 있었다.

그 당시 addAction은 모르겠고 빨리 코드를 작성해야 하니 아는 코드로 하느라 addTarget으로 그냥 작성해버렸는데..

addAction은 Swift에서 UIButton이나 UIControl의 액션을 설정할 때 사용하는 메서드라고 한다.

위에서 내가 헷갈렸 듯 addTarget과 유사한 역할을 하긴 하지만,

더 간결한 문법과 클로저 기반 방식을 지원한다는 점에서 차이가 있다고 한다.


addAction?

addActioniOS 14 이상에서 사용할 수 있는 UIControl 메서드인데, 버튼이나 다른 UI 요소에 액션을 추가할 때 사용된다고 한다.

클로저 기반으로 동작하고, UIAction 객체를 전달받아 사용된다.

기본적인 사용법

let button = UIButton()

button.addAction(UIAction { _ in
    print("Button tapped!")
}, for: .touchUpInside)

addTarget과의 차이점 분석 표

특징addTarget(_:action:for:)addAction(_:for:)
iOS 지원 버전iOS 2.0+iOS 14.0+
방식타겟-액션 패턴클로저 기반 UIAction 사용
사용법메서드 호출 시, 객체와 셀렉터를 전달클로저로 직접 동작 정의
가독성코드가 다소 장황할 수 있음더 간결하고 명확한 문법 제공
동작대상 객체(target)이 필요함클로저만으로 간단히 동작 정의 가능

addTarget 의 경우

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside) 
// #selector 쓰기 귀찮았던 사람 손

@objc func buttonTapped() {
    print("Button tapped!")
}

addAction 의 경우

button.addAction(UIAction { _ in
    print("Button tapped!")
}, for: .touchUpInside)

확실히 가독성 면에서 뛰어나고 불필요한 코드는 없앤 addAction이 더 좋아보인다.


addAction을 사용하는 이유는 무엇이냐

  1. 클로저 기반의 간결함

    • addTarget은 메서드를 따로 정의해야 하지만, addAction은 한 줄의 클로저로 동작을 정의할 수 있다.
  2. 더 나은 가독성

    • 액션 정의가 UI 코드와 더 가깝게 작성되다보니, UI와 동작을 더 쉽게 연결할 수 있다.
  3. 타겟-액션 패턴의 한계 극복

    • addTarget은 타겟 객체(target)에 강하게 묶여 있지만, addAction은 클로저를 사용하기 때문에 더 유연하게 활용할 수가있다.

그렇다면 어떤 상황에서 사용할까?

  • 단순한 액션 : 버튼 하나의 간단한 동작을 정의할 때는 addAction이 적합하다.

  • 복잡한 로직 : 동작을 여러 메서드로 분리하거나 리팩토링이 필요한 경우에는 addTarget을 사용할 수 있다.


추가 예제도 한번 보자.

전달된 파라미터 활용

addAction에서 클로저의 매개변수로 이벤트를 받을 수 있다.

button.addAction(UIAction { action in
    print("Button tapped at: \(action.timestamp)")
}, for: .touchUpInside)

여기서 action.timestamp 사용 했는데

action.timestamp는 버튼이 눌렸을 때의 타임스탬프(시간)를 나타내준다.

이 값은 TimeInterval 타입(1970년 1월 1일 이후의 초)인데,

Date 객체로 변환하거나 디버깅용으로 바로 출력할 수 있는 코드다.

그리고 버튼이 .touchUpInside 이벤트를 받았을 때 클로저 내부 코드가 실행된다.

UIControl.Event에 따른 액션 정의

여러 이벤트에 대해 addAction을 정의할 수 있다.

button.addAction(UIAction { _ in
    print("Touch started")
}, for: .touchDown) // 사용자가 화면에 손가락을 올리기만 해도 이 이벤트가 트리거됨.

button.addAction(UIAction { _ in
    print("Touch ended")
}, for: .touchUpInside) // 버튼의 영역 안에서 손을 떼야만 이 이벤트가 트리거됨.

클로저가 중첩되는 방식이라 복잡할 것 같은데...

아무래도 addAction은 클로저를 활용하기 때문에, 클로저 안에 로직이 복잡해지면 가독성이 떨어질 수 있긴하다.

특히 클로저 내부에서 또 다른 클로저를 사용하거나, 중첩된 로직을 작성하게 되면 코드가 더 복잡하고 읽기 어려워질 가능성이 있으므로 그 부분을 유의하며 작성해야한다.


왜 클로저 기반 방식이 어려울 수 있냐면,

  1. 중첩된 클로저

    • 클로저는 간결하지만 클로저 내부에서 추가 작업을 처리하거나 다른 클로저를 호출할 경우 코드가 복잡해진다.
    • 그리고 중첩 클로저는 이해하기 어렵고 디버깅도 번거로울 수 있다.
  2. Self 참조 문제

    • 클로저 내부에서 self를 참조해야 할 경우, 메모리 누수를 신경 써야 한다.
    • [weak self]를 항상 고려해줘야하기 때문에 간단한 액션에서조차 추가적인 코드를 작성해야 할 필요가 생긴다.
  3. 로직 분리의 어려움

    • 클로저는 코드의 분리가 어렵다. 모든 로직이 한곳에 모이면 가독성이 떨어지고 테스트하기 어려워진다..

addAction이 복잡할 때의 예시를 보자

button.addAction(UIAction { _ in
    // 첫 번째 작업
    fetchData { result in
        // 두 번째 작업
        processResult(result) { processedData in
            // 세 번째 작업
            print("Processed data: \(processedData)")
        }
    }
}, for: .touchUpInside)

위와 같은 경우 클로저가 계속 중첩되는 것을 보면, 역시나 코드가 복잡해지고 추적이 어렵다.


이럴 땐 어떻게 해결 하느냐

  1. 로직 분리
    클로저 내부에서 모든 로직을 처리하지 말고, 별도의 메서드로 분리하면 가독성이 좋아진다.

    button.addAction(UIAction { [weak self] _ in
        self?.handleButtonTap() 
    }, for: .touchUpInside)
    
    // 별도 메서드 
    func handleButtonTap() {
        fetchData { result in
            processResult(result) { processedData in
                print("Processed data: \(processedData)")
            }
        }
    }
  2. addTarget 사용 고려
    그래도 만약 클로저가 너무 복잡하다면.. addTarget 방식을 사용하는 것이 오히려 더 깔끔할 수 있다!

    button.addTarget(self, action: #selector(handleButtonTap), for: .touchUpInside)
    
    @objc func handleButtonTap() {
        fetchData { result in
            processResult(result) { processedData in
                print("Processed data: \(processedData)")
            }
        }
    }
  3. 클로저의 로직을 축소
    가능하면 클로저 내부에서 간단한 작업만 수행하도록 설계하는 것이 좋다.

    button.addAction(UIAction { _ in
        print("Button tapped!")
    }, for: .touchUpInside)

결론 정리

  • 간단한 동작 에서는 addAction을 사용하는게 좋을 것 같다. 이유는 클로저 기반이라 더 짧고 명확한 코드 작성이 가능하기 때문이다.
  • 복잡한 동작이 될 경우에 클로저가 길어지거나 로직이 복잡하다면, 로직 분리를 통해 코드 가독성을 높이거나 addTarget 방식을 사용하는 것이 더 적합할 수 있다.

addActionaddTarget은 상황에 따라 서로 보완적으로 사용할 수 있다는 걸 알았으니, 사용하게 된다면 적절한 선택을 하면 될 것 같다.

아 그리고,

  • addAction간결하고 클로저 기반으로, iOS 14 이상에서 사용하는 최신 방식이고,
  • addTarget은 여전히 유효해서 더 오래된 iOS 버전에서도 동작한다.

만약 iOS 14 이상만 타겟으로 잡는다면 addAction을 적극적으로 활용하는게 좋다고 한다.

iOS 14부터 addAction가 등장했는데, Apple이 권장하는 최신 설계 방식이라고 한다.

Apple은 최신 API를 점점 더 클로저 기반으로 설계하고 있다는데 아무래도 addAction을 사용하는게 Apple의 디자인 철학과 더 잘 맞는 앱을 개발할 수 있을 것 같다.


음...

오늘 addAction과 addTarget에 대해 공부해봤다.

확실히 두 방식의 차이점과 장단점을 잘 이해할 수 있었던 것 같다.

내가 나중에 보려고 정리해두자면,

addTarget

  • 전통적인 타겟-액션 패턴을 사용하고, 특정 메서드(@objc)와 연결해야함.
  • 좀 더 구체적인 제어가 가능하고, 복잡한 로직을 다룰 때 유용함.
  • 하지만 코드가 좀 장황한 편이고, 메서드와 액션을 분리해야 하기 때뮨에 UI 코드와 동작이 분리될 수 있음.

addAction

  • 클로저 기반으로 간결하고 직관적인 코드 작성을 가능하게 해줌
  • UI 코드와 액션이 밀접하게 결합되어 있다보니 가독성이 좋고, 복잡한 메서드를 따로 정의할 필요가 없음. (굿)
  • 하지만 여러 버튼을 처리할 때나 버튼을 구별하는 방법에 대해 좀 더 신경 써야 함.
    (클로저 내부에서 sender을 사용하거나 tag 등 방법이 있다)

어차피 대부분은 iOS 14 이상으로 만들다보니 addAction이 아무래도 간결하고 좀더 명확한 방식이어서 적극적으로 활용할 만할 것 같다.

그래도! 복잡한 로직이나 여러 버튼을 처리해야 할 때는 addTarget을 쓸 필요도 있다는 점을 기억해두자.

profile
iOS 좋아. swift 좋아.

5개의 댓글

comment-user-thumbnail
2024년 12월 24일

최.신.좋.아.

1개의 답글
comment-user-thumbnail
2024년 12월 24일

MVC 혹은 MVVM에서 일반적으로 button은 올바른 설계구조상 View 에 종속적인 관계이기 때문에, 순환참조가 발생 가능성은 낮습니다. 하지만 addAction을 호출한 View를 암시적으로 self 로 강참조하기에 인스턴스가 메모리에서 해제되지않을 수 있습니다.
좀 더 안전한 구조를 지향하면서, 편리성을 가져가고싶다면 꼭 weak가 아닌, 라이프사이클을 고려한 unowned 도 추천드립니다.
button.addAction(UIAction(handler: tappedKeyPadButton), for: .touchUpInside) 와 같이 함수를 변수로 전달할 수 있습니다

1개의 답글

관련 채용 정보