[iOS] RxDelegateProxy 1편 (feat. example)
✅ 이번 시간에는 RxDelegateProxy에 대해서 알아볼 예정이야.
여.기.부.분.은!! 내가 엄청난 삽질(?)을 하면서 내부코드까지 다 뜯어보면서 알게된 부분이라 진짜 스스로...
근데 이론 부분은 확실히 알겠는데, "그래서 실제 프로젝트에 어떻게 적용할건데?" 라고 물으신다면 🥲
MVVM 패턴을 고려해서 적용하는 일이 생각보다 쉬운 일은 아니야 ㅠㅠ
이번 포스팅에서는 정말 많은 글들을 참고했었는데, 제일 아래에 (참고)를 남겨 두도록 할게
✅ 목차
1️⃣ Protocol과 Delegate
2️⃣ RxDelegateProxy GuideLine
3️⃣ RxDelegateProxy example -> 하단에 참고링크 걸어둠!
이 부분은 기존 파일에 조금 더 rx스럽게 개편해 보았음!
🟠 개편한 코드
✅ Protocol과 Delegate
우선 RxDelegateProxy를 잘 이해하기 위해서는 이 부분을 온전하게 이해하고 있을 필요가 있어.
우리 tableView를 예시로 생각해보자.
class VC1: UIViewController, UITableViewDelegate {
var tableViewVC1: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableViewVC1.delegate = self // [CASE #1] self(VC1)에 있는 delegate 기능을 tableViewVC1이 사용하겠다.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { }
}
class VC2: UIViewController {
var tableViewVC2: UITableView!
let vc1 = VC1()
override func viewDidLoad() {
super.viewDidLoad()
tableViewVC2.delegate = vc1 // [CASE #2] vc1에 있는 delegate 기능(함수)를 tableViewVC2에서 사용하겠다.
tableViewVC2.dataSource = vc1
}
}
위에 코드를 보면 delegate에 대해서 직관적으로 이해할 수 있을거야.
VC2에서 Delegate와 DataSource가 구현되어 있지 않음에도 error이 발생하지 않는다는 사실!
🟠 그렇다면 protocol은 어떤 상관이 있는걸까?
자 이제 알겠지? DataSource가 결국에는 protocol이었다는 사실!!
마찬가지로 Delegate도 내부를 열어보면 protocol로 되어있어
🟠 UITableViewDelegate에서는 필수로 구현해야하는 메소드가 없는데,
UITableViewDataSource에서는 필수로 구현해야 하는 메소드가 있는 이유는 뭐야?
그 이유는 protocol안에 어떻게 구현되어 있는지를 봐야하는데 다시 UITableViewDataSource의 내부를 보자면
자세히 관찰하면 optional이라는 키워드를 확인할 수 있는데, optional이라는 키워드가 붙으면 구현되지 않아도 되고 그렇지 않은 함수는 채택했을 때, 필수로 구현해주어야 해.
근데 여기서 하나 더 알아가야할 것이 있는데, Swift에서는 함수를 optional로 만들 수 없도록 되어있어.
optional은 objc함수일 경우에만 가능한데, 근데 저게 어떻게 되는걸까?
그 이유는 바로 NSObjectProtocol를 상속받기 때문이지.
쉽게 말해서 objc로 사용할 수 있도록 만들어준다고 생각하면 좋아!
지금 내가 말한 NSObjectProtocol로 인해서.. Delegateproxy를 사용하는데 엄청 애를 먹게 되는데,,, 그건 포스팅을 쭉 따라가보면 나와
✅ RxDelegateProxy GuideLine
🟠 RxInputViewControllerProxy.swift
import UIKit
import RxSwift
import RxCocoa
// rx로 프로토콜을 만들때 obj-c를 사용하는 이유는 rx_sendString 부분에서 #selector를 사용하는데 이게 obj-c로만 사용할 수 있기 때문이다.
@objc protocol InputViewControllerDelegate: class {
@objc optional func sendString(string: String)
}
// 이건 하나의 형식임으로 이에 맞게 메소드를 넣어줘
class RxInputViewControllerProxy: DelegateProxy<InputViewController, InputViewControllerDelegate>, DelegateProxyType, InputViewControllerDelegate, UITableViewDelegate {
static func registerKnownImplementations() {
self.register { (inputViewController) -> RxInputViewControllerProxy in
RxInputViewControllerProxy(parentObject: inputViewController, delegateProxy: self)
}
}
static func currentDelegate(for object: InputViewController) -> InputViewControllerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: InputViewControllerDelegate?, to object: InputViewController) {
object.delegate = delegate
}
}
// 하하.. 이 부분을 하기 위해서는 Reactive Extension을 또 알아야하네 ㅠㅠ 하단에 포스팅 링크를 걸어둘게요!
extension Reactive where Base: InputViewController {
var rx_deleagte: DelegateProxy<InputViewController, InputViewControllerDelegate> {
return RxInputViewControllerProxy.proxy(for: self.base)
}
var rx_sendString: Observable<String?> {
return rx_deleagte.sentMessage(#selector(InputViewControllerDelegate.sendString(string:))).map { arr in arr[0] as? String }
}
}
위의 코드는 proxy로 만드는 방법에 대한 가이드라인이니까 보이는 대로 만들면 돼
✅ RxDelegateProxy example
(단, 위의 가이드라인에서 제시한 코드도 example안에 있어야 합니다.)
🟠 ViewController.swift
//
// ViewController.swift
// RxSwiftDelegateExample
//
// Created by lgvv0908 on 2022/01/12
//
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController, InputViewControllerDelegate {
@IBOutlet weak var inputLabel: UILabel!
@IBOutlet weak var openButton: UIButton!
@IBOutlet weak var openSubjectButton: UIButton!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let storyBoard = UIStoryboard(name: "Main", bundle: .main)
let inputVC = storyBoard.instantiateViewController(withIdentifier: "InputViewController") as? InputViewController
// InputViewController에 rx로 delegate해서 사용하겠다
inputVC?.rx.rx_deleagte.setForwardToDelegate(self, retainDelegate: false)
inputVC?.rx.rx_sendString
.bind(to: inputLabel.rx.text)
.disposed(by: disposeBag)
openButton.rx.tap // 파란색버튼 클릭시
.debug("openButton")
.throttle(.seconds(1), scheduler: MainScheduler.instance) // 버튼 클릭 후 1초동안 동일 이벤트 방출 안함
.subscribe(onNext: { [weak self] _ in // tap하면 controlEvent가 발생되는데, 이를 막음
if let inputVC = inputVC {
self?.present(inputVC, animated: true, completion: nil) // 화면 전환
}
})
.disposed(by: disposeBag)
openSubjectButton.rx.tap
.bind { [weak self] in
guard let inputVC = inputVC else { return } // 위에는 if let을 사용했는데 여기는 가드문 사용
self?.present(inputVC, animated: true, completion: nil) // 화면 전환
}
.disposed(by: disposeBag)
inputVC!.inputStringSubject
.bind(to: inputLabel.rx.text)
.disposed(by: disposeBag)
}
}
🟠 InputViewController.swift
//
// InputViewController.swift
// RxSwiftDelegateExample
//
// Created by lgvv0908 on 2022/01/12
// Copyright © 2020 swieeft. All rights reserved.
//
import UIKit
import RxSwift
import RxCocoa
class InputViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var confirmButton: UIButton!
@IBOutlet weak var confirmSubjectButton: UIButton!
weak var delegate: InputViewControllerDelegate?
var disposeBag = DisposeBag()
var inputStringSubject = PublishSubject<String>() // 여기서 서브젝트를 갖고 있음
var inputString: Observable<String> { // 프로퍼티인데, inputStringSubject값을 Observable<String>값으로 받고 있음
return inputStringSubject.asObservable()
}
override func viewDidLoad() {
super.viewDidLoad()
confirmButton.rx.tap
.throttle(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { [weak self] _ in
guard let string = self?.textField.text else { return }
self?.dismiss(animated: true, completion: {
self?.delegate?.sendString?(string: string) // 프로토콜로 데이터 전달함
})
})
.disposed(by: disposeBag)
confirmSubjectButton.rx.tap
.debug("confirmSubjectButton")
.throttle(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { [weak self] _ in
guard let string = self?.textField.text else { return }
// self?.inputStringSubject.onNext(string) // 서브젝트로 방출함
self?.dismiss(animated: true, completion: {
self?.inputStringSubject.onNext(string) // 서브젝트로 방출함
print("string -> \(string)")
// self?.inputStringSubject.onCompleted()
})
})
.disposed(by: disposeBag)
textField.rx.controlEvent(.editingDidEndOnExit)
.debug("editingDidEndOnExit")
.subscribe(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.disposed(by: disposeBag)
}
}
코드는 쓱 보면 이해할 수 있을거라고 믿어!
(참고)
https://danielt1263.medium.com/convert-a-swift-delegate-to-rxswift-observables-f52afe77f8d6
https://swieeft.github.io/2020/02/20/RxSwiftDelegate1.html