반응형
Podfile
# Moya with rx
pod 'Moya/RxSwift', '~> 15.0'
# RxSwift & Cocoa
pod 'RxSwift', '~> 6.5.0'
pod 'RxCocoa', '~> 6.5.0'
# SwiftyJson
pod 'SwiftyJSON', '~> 4.0'
1. 엮을 API의 구조
header
x-access-token : jwt 토큰
response
{
"isSuccess": true,
"code": 1000,
"message": "성공",
"result" : [
{
"profileImageUrl" : "이미지 링크",
"roomId" : 255,
"title" : "test",
"repUserName" : "Runnber6421",
"recentMessage" : "N"
},
{
"profileImageUrl" : "이미지 링크",
"roomId" : 256,
"title" : "테스트",
"repUserName" : "Runnber6421",
"recentMessage" : "N"
}
],
}
2. MessageAPI - API 통신에 필요한 Moya TargetType를 준수하는 enum 정의
import Foundation
import Moya
enum MessageAPI {
case getMessageList(token: LoginToken) //사용자의 토큰정보가 담긴 구조체
}
enum APIResult<T> {
case response(result: T)
case error(alertMessage: String?)
}
extension MessageAPI: TargetType {
var baseURL: URL {
return "API url 주소"
}
var path: String { //API 주소 적는곳
switch self {
case let .getMessageList:
return "/message"
}
}
var method: Moya.Method { // http method 정의
switch self {
case .getMessageList:
return Method.get
}
}
var task: Task {
switch self {
case .getMessageList: // request를 어떻게 요청할 것인가?
return .requestPlain
}
}
var header: [String: String]? {
var header: [String : String]? { ["Content-Type": "application/json"] }
switch self {
case let .getMessageList(token):
header["x-access-token"] = "\(token.jwt)"
}
return header
}
}
3. MessageAPIService - API 통신하여 observable response를 받아오는 클래스
class MessageAPIService {
let provider: MoyaProvider<MessageAPI>
let loginKeyChain: LoginKeyChainService
init(provider: MoyaProvider<MessageAPI> = .init(), loginKeyChainService: LoginKeyChainService = BasicLoginKeyChainService.shared) {
loginKeyChain = loginKeyChainService
self.provider = provider
}
func getMessageList() -> Observable<APIResult<[MessageListItem]?>> {
guard let token = loginKeyChain.token
else {
return .just(.error(alertMessage: nil))
}
return provider.rx.request(.getMessageList(token: token))
.asObservable() // return type을 observable로
.map { response in
try? JSON(data: response.data)
}
.map { (try? $0?.json["result"].rawData()) ?? Data() } //result에 해당하는 raw Json Data가 필요함
.decode(type: [MessageListItem]?.self, decoder: JSONDecoder()) //[MessageListItem]으로 디코딩
.catch { error in
Log.e("\(error)")
return .just(nil)
}
.map { APIResult.response(result: $0 ?? []) } //APIResult에 response 담아서 매핑
.catchAndReturn(.error(alertMessage: "네트워크 연결을 확인해 주세요"))
}
4. MessageListItem - API 통신의 result를 감쌀 구조체
struct MessageListItem: Decodable {
let roomId: Int?
let title, repUserName: String?
let profileImageUrl: String?
let recentMessage: String?
enum CodingKeys: String, CodingKey {
case roomId
case title, repUserName
case profileImageUrl
case recentMessage
}
}
5. MessageViewModel
import Foundation
import RxSwift
class MessageViewModel: BaseViewModel {
var messages: [MessageListItem] = []
init(messageAPIService: MessageAPIService = MessageAPIService()) {
super.init()
routeInputs.needUpdate //routeInputs.needUpdate 값이 observe되면
.flatMap { _ in
messageAPIService.getMessageList() //API 호출하여 해당 결과값을 observable로 보냄
}
.map { [weak self] result -> [MessageListItem]? in
switch result {
case let .response(result: data):
return data
case let .error(alertMessage):
if let alertMessage = alertMessage {
self?.toast.onNext(alertMessage)
}
return nil
}
}
.subscribe(onNext: { [weak self] result in
guard let self = self else { return }
self.messages.removeAll()
if let result = result {
self.messages = result
if !self.messages.isEmpty {
self.outputs.messageLists.onNext(self.messages)
} else {
self.outputs.messageLists.onNext([])
}
}
})
.disposed(by: disposeBag)
}
struct Input { // View에서 ViewModel로 전달되는 이벤트 정의
생략
}
struct Output { // ViewModel에서 View로의 데이터 전달이 정의되어있는 구조체
var messageLists = ReplaySubject<[MessageListItem]>.create(bufferSize: 1)
}
struct Route { // 화면 전환이 필요한 경우 해당 이벤트를 Coordinator에 전달하는 구조체
생략
}
struct RouteInput { // 자식화면이 해제되면서 전달되어야하느 정보가 있을 경우, 전달되어야할 이벤트가 정의되어있는 구조체
var needUpdate = PublishSubject<Bool>()
}
private var disposeBag = DisposeBag()
var inputs = Input()
var outputs = Output()
var routes = Route()
var routeInputs = RouteInput()
}
6. MessageViewController - 테이블뷰를 뿌려줄 ViewController
import Kingfisher
import RxCocoa
import RxGesture
import RxSwift
import SnapKit
import Then
import UIKit
class MessageViewController: BaseViewController, UIScrollViewDelegate {
let cellID = "MessageTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
viewModelOutput()
viewModel.routeInputs.needUpdate.onNext(true) //여기서 true값을 주게되면, ViewModel 안의 needUpdate가 동작하여 API를 호출함
}
init(viewModel: MessageViewModel) {
self.viewModel = viewModel
super.init()
}
private var viewModel: MessageViewModel
...중략
private func viewModelOutput() {
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.outputs.messageLists
.filter { [weak self] array in
if array.isEmpty {
self!.tableView.isHidden = true
return false
} else {
self!.tableView.isHidden = false
return true
}
}
.bind(to: tableView.rx.items(cellIdentifier: cellID, cellType: MessageTableViewCell.self)) { _, item, cell in
cell.selectionStyle = .none
// 반짝임 효과 제거
if item.profileImageUrl != nil {
cell.messageProfile.kf.setImage(with: URL(string: item.profileImageUrl!), placeholder: Asset.profileEmptyIcon.uiImage)
} else {
cell.messageProfile.image = Asset.profileEmptyIcon.uiImage
}
cell.postTitle.text = item.title
cell.nameLabel.text = item.repUserName
if item.recentMessage == "Y" { // 안읽은 메시지 여부 : 있음
cell.backgroundColor = .primaryBestDark
} else {
cell.backgroundColor = .clear
}
}
.disposed(by: disposeBag)
viewModel.toast
.subscribe(onNext: { message in
AppContext.shared.makeToast(message)
})
.disposed(by: disposeBag)
} // 뷰모델에서 뷰로 데이터가 전달되어 뷰의 변화가 반영되는 부분
private var tableView = UITableView().then { view in
view.separatorColor = .clear
view.register(MessageTableViewCell.self, forCellReuseIdentifier: MessageTableViewCell.id) //셀 등록
}
}
... 후략
-> 추후에 RxDatasource를 사용해서 테이블뷰에 바인딩해보는것도 할 예정!
실행결과
반응형
'🍎 iOS > RxSwift' 카테고리의 다른 글
[RxSwift, RxCocoa] 주요 Operator 특징 및 차이점 정리 (계속 업데이트 예정) (0) | 2023.03.16 |
---|---|
[RxSwift] Disposable, Dispose, DisposeBag (0) | 2023.03.16 |
[RxSwift] 곰튀김 RxSwift + MVVM 정리 (0) | 2023.01.16 |