네 안녕하십니까.
참으로 굉장히 오랜만이네요.
제가 뭘 하고 있었냐면,
를 구현한 오픈소스 라이브러리를 갖다가 프로젝트에 적용시키고 있었단 말이죠.
뭐 대충 이런 겁니다.
Shuffle이라는 오픈소스 라이브러리인데요, 델리게이트 패턴을 사용한 녀석이라, 처음에 적용하는 데에는 큰 무리가 없었습니다.
UICollectionView와 상당히 흡사한 Delegate와 Datasource 패턴을 채용했기 때문이죠.
근데... RxSwift를 사용해서 리팩터링을 진행하던 과정 중에 이 친구를 어떻게 Rx로 사용할 것인가?
라는 의문이 들었고, 잘 찾아보니 답이 나왔습니다.
그럼 작성해줍시다.
extension SwipeCardStack: HasDelegate {
public typealias Delegate = SwipeCardStackDelegate
}
일단 위의 코드로 Shuffle의 SwipeCardStack의 스와이프 액션 등을 처리해주는 Delegate가 있다고 명시해주고,
class RxSwipeCardStackDelegateProxy
: DelegateProxy<SwipeCardStack, SwipeCardStackDelegate>
, DelegateProxyType
, SwipeCardStackDelegate {
weak private(set) var cardStack: SwipeCardStack?
init(cardStack: SwipeCardStack) {
self.cardStack = cardStack
super.init(parentObject: cardStack, delegateProxy: RxSwipeCardStackDelegateProxy.self)
}
static func registerKnownImplementations() {
self.register { RxSwipeCardStackDelegateProxy(cardStack: $0) }
}
}
위 코드처럼 DelegateProxy를 작성해줍니다.
클래스명은 RxCocoa 내부에서 Rx+(원래 클래스 이름)+DelegateProxy로 다 지어놨길래 마음의 평안을 위해 통일해줍니다.
extension Reactive where Base: SwipeCardStack {
var delegate: DelegateProxy<SwipeCardStack, SwipeCardStackDelegate> {
return RxSwipeCardStackDelegateProxy.proxy(for: base)
}
// (기존 delegate의 메서드들을 넣을 곳)
}
그 담엔 이렇게 Reactive의 extension으로 작성을 해주고요,
아래에 기존 delegate의 메서드들을 넣어줍시다.
// 기존 delegate의 메서드들
@objc
optional func cardStack(_ cardStack: SwipeCardStack, didSelectCardAt index: Int)
@objc
optional func cardStack(_ cardStack: SwipeCardStack, didSwipeCardAt index: Int, with direction: SwipeDirection)
@objc
optional func cardStack(_ cardStack: SwipeCardStack, didUndoCardAt index: Int, from direction: SwipeDirection)
그러니까 요놈들을
// 넣어줌
extension Reactive where Base: SwipeCardStack {
var delegate: DelegateProxy<SwipeCardStack, SwipeCardStackDelegate> {
return RxSwipeCardStackDelegateProxy.proxy(for: base)
}
var didSelectCardAt: Observable<Int> {
return delegate
.methodInvoked(#selector(SwipeCardStackDelegate.cardStack(_:didSelectCardAt:)))
.map { num in
return num[1] as? Int ?? 0
}
}
var didSwipeAllCards: Observable<Void> {
return delegate
.methodInvoked(#selector(SwipeCardStackDelegate.didSwipeAllCards(_:)))
.map { _ in () }
}
var didSwipeCardAt: Observable<(Int, SwipeDirection)> {
return delegate
.methodInvoked(#selector(SwipeCardStackDelegate.cardStack(_:didSwipeCardAt:with:)))
.map { args in
return (
index: args[1] as? Int ?? 0,
direction: args[2] as! SwipeDirection
)
}
}
}
이렇게 넣으면 된다...!
그럼 기존의 델리게이트에서 메서드가 호출되는 타이밍에 .methodInvoked가 호출되어 Observable을 반환하게 됩니다.
그럼 View와 ViewModel에 적용해볼까요?
근데 안되네요. 기존의 Delegate 패턴을 사용했을때 잘 뜨던 카드 스택들이 래핑하고 나니 안됩니다...
왜 안되는지... 뭐가 잘못되었는지 한참을 생각하고 코드를 읽어보다가...
설마... 하는 생각이 들어 다른 뷰의 프로퍼티로 분리해뒀던 것 때문에↓
프로젝트/
├─ class ItemSearchView/
│ ├─ 이것저것 뷰 컴포넌트들
├─ class ItemCardsView/
│ ├─ cardStack = SwipeCardStack()
그런 건가 싶어 합쳐주었고↓
프로젝트/
├─ class ItemSearchView/
│ ├─ 이것저것 뷰 컴포넌트들
│ ├─ cardStack = SwipeCardStack()
├─ class ItemCardsView -> delete/
그러니까 됩니다 또...?
여튼 그렇게 해결이 되었습니다.
예에에에에소리질러~
(실제로 몇시간동안 끙끙댄 후에 해결하고 소리지름 리얼참트루스토리)
그게 말입니다....
Shuffle이 가지고있는 Delegate와 Datasource가 UICollectionView와 유사하게 되어 있어,
RxCocoa의 UICollectionView+Rx와 동일한 구조로 작업하면 되지 않을까... 하고 코드를 읽고 적용해보려 했거든요?
근데 안돼요.
계속 에러가 뜨더라고요...
정확히는:
'subscribeProxyDataSource(ofObject:dataSource:retainDataSource:binding:)' requires that 'any RxSwipeCardStackDataSourceProxy.Delegate' (aka 'any SwipeCardStackDataSource') be a class type
이렇게 뜨는데,
대충 해석하자면:
응 니가 준거 클래스 타입 아니야 돌아가~
라네요? 그래서 RxSwipeCardStackDataSourceProxy와 관련된 클래스들을 찾아봤는데 죄다 클래스였고,
SwipeCardStackDataSource도 확인해봤는데 AnyObject였단 말입니다..
public protocol SwipeCardStackDataSource: AnyObject {
func cardStack(_ cardStack: SwipeCardStack, cardForIndexAt index: Int) -> SwipeCard
func numberOfCards(in cardStack: SwipeCardStack) -> Int
}
그래서 일단은 데이터소스는 기존 형식을 사용하는 중인데...
이거 왜 이러는지 아시는 분은 댓글로 알려주시면 감사하겠습니다...