apple/UIKit & ReactiveX

[Swift] extension Reactive

lgvv 2022. 1. 12. 17:24

✅ 이번 시간에는 extension Reactive에 대해서 알아볼 예정이야.

 

우리가 설치한 RxSwfit 안에는 Reactive.swift 파일이 있는데, custom point로 사용하라고 한다.

 

🟠 Reactive.swift

 Use `Reactive` proxy as customization point for constrained protocol extensions.

 General pattern would be:

 // 1. Extend Reactive protocol with constrain on Base
 // Read as: Reactive Extension where Base is a SomeType
 extension Reactive where Base: SomeType {
 // 2. Put any specific reactive extension for SomeType here
 }

 With this approach we can have more specialized methods and properties using
 `Base` and not just specialized on common base type.

 */

public struct Reactive<Base> {
    /// Base object to extend.
    public let base: Base

    /// Creates extensions with base object.
    ///
    /// - parameter base: Base object.
    public init(_ base: Base) {
        self.base = base
    }
}

 

✅ 그렇다면 Reactive 이미 구현된 RxSwift 파일에서는 어떻게 사용하고 있을까?

extension Reactive where Base: UIButton {
    
    /// Reactive wrapper for `TouchUpInside` control event.
    public var tap: ControlEvent<Void> {
        return controlEvent(.touchUpInside)
    }
}

이런식으로 구현하고 있다.

 

그럼 우리도 이에 맞춰서 custom하게끔 구현할 수 있겠지?

 

✅ 본격적으로 들어가기 전에... 알아야 할 3가지

1️⃣ ControlEvent - 값을 관찰할 수는 있으나, 값을 주입시키지는 못함.

2️⃣ ControlProperty - 값을 주입시킬 수도 있고, 값의 변화도 관찰할 수 있다.

3️⃣ Binder - 값을 주입시킬 수 있으나, 값의 변화는 관찰하지 못함.

 

🟠 UIViewController+Rx

import Foundation

import RxCocoa
import RxSwift

extension Reactive where Base: UIViewController {
    var viewWillAppear: ControlEvent<Void> {
        let source = self.methodInvoked(#selector(Base.viewWillAppear(_:))).map { _ in }
        return ControlEvent(events: source)
    }
}

사용법은 아래와 같이 사용할 수 있다.

	rx.viewWillAppear // UIViewController+Rx에 정의
            .asObservable() // 반환되는 이벤트를 옵저버블 타입으로 받는다. (공식문서 link에 의하면 From으로 연결되어 있음)
            .bind(to: viewWillAppearSubject)
            .disposed(by: disposeBag)

 

🟠 UIButton+Rx.swift

import Foundation

import RxCocoa
import RxSwift

extension Reactive where Base: UIButton {
    // 같은 기능을 재사용하기 위한 목적!
    public var customChangeTitle: Binder<String> {
        let s = Binder<String>(base) { btn, text in
            btn.setTitle(text, for: [])
        }
        return s
    } // 같은 기능을 재사용하기 위한 목적!
}

위 아래 두 코드는 target이 base임으로 같은 기능을 한다. 

extension Reactive where Base: UIButton {
    // 같은 기능을 재사용하기 위한 목적!
    public var customChangeTitle: Binder<String> {
        let s = Binder<String>(base) { _, text in
            base.setTitle(text, for: [])
        }
        return s
    } 
}
Binder<String>(base, binding: {}) // 클로저 구문 이렇게도 작성 가능

어떤 일을 할 수 있냐면

         myButton.rx
            .tap
            .map{ "text" }
            .bind(to: myButton.rx.customChangeTitle)
            .disposed(by: bag)

이렇게 해서 사용할 수 있어. 함수가 사라져서 훨씬 좋은 구조

 

🟠 UITextField+Rx.swift

import Foundation

import RxCocoa
import RxSwift
import UIKit

extension Reactive where Base: UITextField {
   
    public var text: ControlProperty<String?> {
        value
    }
}

ControlProperty는 값을 주입할 수 있기 때문에

textField.rx.text.onNext("Hello") // 값을 주입할 수 있기 때문에
textField.rx.text.subcribe(onNext: { _ in }) // 값을 관찰할 수 있기 때문에

 

 

✅ Binder의 원형에 대해서 살펴보자.

Binder

 

extension으로 Binder를 사용하려고 하는 경우에는 target을 받아야 한다.

그래서 아까 Button에서 같은 기능을 하는 것도 이와 같은 이유에서이다.