본문 바로가기
RxSwift

[RxSwift Community] Action 사용하기

by Developer.Paul() 2021. 6. 1.

Action Github : https://github.com/RxSwiftCommunity/Action

 

RxSwiftCommunity/Action

Abstracts actions to be performed in RxSwift. Contribute to RxSwiftCommunity/Action development by creating an account on GitHub.

github.com

RxSwift 사용해서 개발할 때, 유용한 라이브러리를 소개하고자 한다. 첫 번째는 Action인데, 순서는 필자의 마음대로... Action은 많은 구글링에도 많은 내용이 없기도 하고 Github의 내용으로도 충분히 이해가 가기 때문에 위의 링크를 기반으로 설명을 이어나가려고 한다.

 

1. 소개

 

이 라이브러리는 RxSwift에서 사용된다. 라이브러리의 이름에서 알 수 있듯, 'action'이라는 observable을 추상화한 작업을 제공한다. Actions은 workFactory라는 클로저를 가지고 있는데, 이 클로저는 어떤 input을 받아서 observable을 제공하는 클로저다. 그리고 execute라는 메소드를 호출할 수 있는데, 이 메소드는 파라미터로 이 클로저(wordFactory)를 받아 구독할 수 있게 한다. excute 메소드는 몇 가지 특징이 있다. 첫 번째, "enabled"된 동안에만 실행된다(기본적으로 true). 두 번째, 한 번에 하나만 실행할 수 있다. 세 번째, 각각의 실행들에서 Next 이벤트와 Error 이벤트를 종합한다.

 

2. 사용법

 

let disposeBag = DisposeBag()

let enabledAction = Observable.just(true)

let action: Action<String, Bool> = Action(enabledIf: enabledAction, workFactory: { input in
    return Observable.just(input == "123")
})

action.execute("123")
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

// true

action.execute("444")
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

// error

 

첫 번째로, Action의 타입을 결정해줘야 한다. 제네릭 파라미터의 첫 번째는 input의 타입을 나타낸다. 그리고 두 번째는 workFactory에서 만들어 낼 observable의 타입을 나타낸다. 쉽게 output의 타입이라고 생각하면 좋을 것 같다. 실제 Action을 초기화하려고 하면, workFactory외에 enabledIf라는 파라미터를 추가로 설정할 수 있다. 이건 해당 Action의 사용 가능 여부를 나타내는데, observable<Bool>를 값으로 받고 Boolean이 false일 시에는 에러와 함께 동작하지 않는다.

 

다음으로는, 수행해야 하는 작업들을 나타내는 workFactory 클로저를 구성하는 것이다. 이 예제에서는 input(String)을 받고 작업의 결과(Bool)를 Observable<Bool>로 리턴하는 workFactory를 만들고 있다. 이 Action을 실제로 사용하기 원할 때는 execute 메소드를 호출하고 파라미터에 값을 넣으면 된다. 그러면 파라미터의 값이 workFactory 클로저의 input으로 들어가게 되고, 작업을 완료한 뒤 next 이벤트를 결과값(element)과 함께 방출한다. 위에서 언급했던 것처럼 Action은 한 번에 하나만 실행시킬 수 있다. 그렇기 때문에 실행하고 있는 action을 다시 실행시키려 한다면, error가 나게 된다.

 

다음과 같이 축약해서 사용하거나, wrapping해서 사용할 수도 있다.

 

let loadData: Action<String, Void>!

init(loadData: Action<String, Void>?) {

   // Trailing Closure로 workFactory를 구현한다.
   self.loadData = Action { input in
    
      // 초기화시에 들어오는 loadData를 input을 받아 실행하고
      if let action = loadData {
         action.excute(input)
      }
        
      // 또 input의 count를 방출하는 Action을 loadData에 넣는다.
      return Observable.just(input.count)
   }
}

 

사실 Action의 가장 좋은 기능은 UIButton extension이다. 우리가 만든 액션을 Button의 액션으로 간편하게 넣어줄 수 있다.

 

button.rx.action = action

 

이렇게 넣을 수 있는 이유는 (button.rx.)action이 실제로는 Action<Void, Void>인 CocoaAction을 채용하고 있기 때문이다. 이제 해당 버튼을 누르게 되면 우리가 커스텀한 action이 실행된다. 이 때 버튼의 enabled 여부가 action의 enabled와 연결되어 있어서 즉각적인 반응이 가능하다. 예를 들어, 로그인 상황에서 action의 enabledIf 파라미터에 이메일 사용 가능 여부를 넣으면, 버튼의 enabled 여부가 자동으로 이메일의 사용 가능 여부에 의해 결정되기 때문에 적절한 이메일이 적힐 때까지 사용자가 아에 버튼을 누르지 못하게도 만들 수 있다.

 

만약에 '다수의 버튼'이 '서로 다른 input을 가진 Action'을 트리거 해야 한다면 어떻게 하면 좋을까? 이 때는 bindTo(Action<input, output>)를 사용하면 된다. 다음의 예제를 참고하자.

 

let button1 = UIButton()
let button2 = UIButton()

let action = Action<String, String> { input in
    print(input)
    return .just(input)
}

button1.rx.bindTo(action) { _ in return "Hello"}
button2.rx.bindTo(action) { _ in return "Goodbye"}

 

만약 Action을 좀 더 복잡한 상황에서 사용하게 된다면 CocoaAction 대신에 Action<Type, Type>으로 커스텀해서 사용할 수도 있다. Action의 구현부를 보면 사실 버튼 이외에도 Control, AlertAction, BarButtonItem, RefreshControl 등이 추가적으로 존재하기 때문에 CocoaAction으로 다 구현하기에 어려운 경우가 있다. 예를 들어, download progressbar의 경우는 Action<Void, Int>가 필요하다. 이외에도 다양하게 사용할 수 있기 때문에 Github을 참고해도 좋겠다.

 

 

댓글