[Swift] RXSwift Refactoring in iOS
2024. 2. 13. 18:29ใProgramming/Swift
๐บ
์ค๋ ๊ตฌํํ ๊ฒ
hotfix๋ก JWT ํ ํฐ ๋ง๋ฃ ์, refreshํ๋ ๋ก์ง์ ๊ตฌํํ์ต๋๋ค.
https://github.com/GDSC-Wetox/Wetox-iOS/pull/16
์ ๊ฐ ํ์๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ ๊ณผ์ ์ Escaping Closure๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํํ์๋๋ฐ์.,
์ด๋ฒ ๊ธฐํ์ RXSwift Observable๋ก ๋ฆฌํฉํ ๋งํ๊ฒ ๋์์ต๋๋ค. (๋.๋.์ด!)
ํ๋ก์ ํธ ๋ง๊ฐ์ด 10์ผ ์ ๋ ๋จ์์ต๋๋ค. (์ด๋ ๊ฒ ๊ฐ๊ฐ๋ ๊ณง . . . ๐คฏ)
๋๊น์ง ํ์ดํ ํ ,, ๐ช
๐ ๊ธฐ์กด์ MVC + escaping closure ๊ธฐ๋ฐ์ ์ฝ๋
//
// AuthAPI.swift
// Wetox-iOS
//
// Created by ๊น์ํ on 1/28/24.
//
import UIKit
import Moya
public class AuthAPI {
static let shared = AuthAPI()
var authProvider = MoyaProvider<AuthService>(plugins: [MoyaLoggerPlugin()])
public init() { }
// MARK: - ํ์๊ฐ์
func register(registerRequest: RegisterRequest, profileImage: UIImage?, completion: @escaping (NetworkResult<Any>) -> Void) {
authProvider.request(.register(registerRequest: registerRequest, profileImage: profileImage)) { (result) in
switch result {
case .success(let response):
let statusCode = response.statusCode
let data = response.data
let networkResult = self.judgeRegisterStatus(by: statusCode, data)
completion(networkResult)
case .failure(let error):
print("error: \(error)")
}
}
}
func login(tokenRequest: TokenRequest, completion: @escaping (NetworkResult<Any>) -> Void) {
authProvider.request(.login(tokenRequest: tokenRequest)) { (result) in
switch result {
case .success(let response):
let statusCode = response.statusCode
let data = response.data
let networkResult = self.judgeLoginStatus(by: statusCode, data)
completion(networkResult)
case .failure(let error):
print("error: \(error)")
}
}
}
func logout(completion: @escaping (NetworkResult<Any>) -> Void) {
authProvider.request(.logout) { (result) in
switch result {
case .success(let response):
let statusCode = response.statusCode
let data = response.data
let networkResult = self.judgeLoginStatus(by: statusCode, data)
completion(networkResult)
case .failure(let error):
print("error: \(error)")
}
}
}
private func judgeRegisterStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(RegisterResponse.self, from: data) else { return .pathError }
switch statusCode {
case 200:
return .success(decodedData)
case 400..<500:
return .requestError
case 500:
return .serverError
default:
return .networkFail
}
}
private func judgeLoginStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(TokenResponse.self, from: data) else { return .pathError }
switch statusCode {
case 200:
return .success(decodedData)
case 400..<500:
return .requestError
case 500:
return .serverError
default:
return .networkFail
}
}
private func judgeLogoutStatus(by statusCode: Int, _ data: Data) -> NetworkResult<Any> {
let decoder = JSONDecoder()
guard let decodedData = try? decoder.decode(String.self, from: data) else { return .pathError }
switch statusCode {
case 200:
return .success(decodedData)
case 400..<500:
return .requestError
case 500:
return .serverError
default:
return .networkFail
}
}
}
๐ ํธ์ถ๋๋ ๋ถ๋ถ
//
// LoginViewController.swift
// Wetox-iOS
//
// Created by ๊น์ํ on 1/24/24.
//
import UIKit
import SnapKit
import KakaoSDKUser
import KakaoSDKAuth
import AuthenticationServices
// ์๋ต
extension LoginViewController {
func loginWithAPI(tokenRequest: TokenRequest) {
AuthAPI.shared.login(tokenRequest: tokenRequest) { response in
switch response {
case .success(let loginData):
if let data = loginData as? TokenResponse {
UserDefaults.standard.set(tokenRequest.oauthProvider, forKey: Const.UserDefaultsKey.oauthProvider)
UserDefaults.standard.set(data.accessToken, forKey: Const.UserDefaultsKey.accessToken)
UserDefaults.standard.set(Date(), forKey: Const.UserDefaultsKey.updatedAt)
UserDefaults.standard.set(true, forKey: Const.UserDefaultsKey.isLogin)
}
case .requestError:
print("loginWithAPI - requestError")
case .pathError:
print("loginWithAPI - pathError")
case .serverError:
print("loginWithAPI - serverError")
case .networkFail:
print("loginWithAPI - networkFail")
}
}
}
}
๐ new RXSwift ๊ธฐ๋ฐ์ ์ฝ๋
- ์์ง MVVM ํ์์ผ๋ก ๋ทฐ๋ชจ๋ธ๊น์ง๋ ๋ถ๋ฆฌ๋ฅผ ๋ชปํ๋ค ๐ฅฒ
- ๊ธฐ์กด์ ํด๋ก์ ๋ก response๋ฅผ ๊ฐ์ ธ์จ ๋ก์ง์ rx Observable๋ก ๊ฐ์ ธ์ค๋๋ก ๋ฐ๋์์ต๋๋ค.
- ๋์๊ฐ expired token์ผ๋ก ๋ฐ์ํ ์ค๋ฅ์ ๋ํด, ์ฌ๋ฐ๊ธ request๋ฅผ ๋ ๋ฆด ์ ์๋๋ก ํจ์๋ฅผ ๊ตฌํํ์ต๋๋ค.
- RXSwift ์ฒ์ ์จ๋ณด๋๋ฐ์, ์ ๊ธฐํ๋ค์ . . ๋ค์๋ฒ์๋ ๋ ์ ํ ์ ์์ ๊ฒ ๊ฐ์์ :)
//
// AuthAPI.swift
// Wetox-iOS
//
// Created by ๊น์ํ on 1/28/24.
//
import UIKit
import Moya
import RxSwift
import RxMoya
public class AuthAPI {
static let authProvider = MoyaProvider<AuthService>(plugins: [MoyaLoggerPlugin()])
static func register(registerRequest: RegisterRequest, profileImage: UIImage?) -> Observable<RegisterResponse> {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return AuthAPI.authProvider.rx.request(.register(registerRequest: registerRequest, profileImage: profileImage))
.map(RegisterResponse.self, using: decoder)
.asObservable()
.catch { error in
if let moyaError = error as? MoyaError {
switch moyaError {
case .statusCode(let response):
// HTTP ์ํ ์ฝ๋์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ
print("HTTP Status Code: \(response.statusCode)")
case .jsonMapping(let response):
// JSON ๋งคํ ์๋ฌ ์ฒ๋ฆฌ
print("JSON Mapping Error for Response: \(response)")
default:
// ๊ธฐํ Moya ์๋ฌ ์ฒ๋ฆฌ
print("Other MoyaError: \(moyaError.localizedDescription)")
}
}
return Observable.error(error)
}
}
static func login(tokenRequest: TokenRequest) -> Observable<TokenResponse> {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return AuthAPI.authProvider.rx.request(.login(tokenRequest: tokenRequest))
.map(TokenResponse.self, using: decoder)
.asObservable()
.catch { error in
handleTokenError(error: error, request: .login(tokenRequest: tokenRequest))
}
}
static func handleTokenError(error: Error, request: AuthService) -> Observable<TokenResponse> {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let moyaError = error as? MoyaError, moyaError.response?.statusCode == 401 {
print("๋ง๋ฃ๋ ํ ํฐ์ ๋ํ์ฌ ์ฌ๋ฐ๊ธ์ ์๋ํฉ๋๋ค.")
let refreshTokenRequest = TokenRequest(oauthProvider: UserDefaults.standard.string(forKey: Const.UserDefaultsKey.oauthProvider) ?? String(),
openId: UserDefaults.standard.string(forKey: Const.UserDefaultsKey.openId) ?? String())
return AuthAPI.authProvider.rx.request(.login(tokenRequest: refreshTokenRequest))
.map(TokenResponse.self, using: decoder)
.asObservable()
}
else {
return Observable.error(error)
}
}
}
๐ ํธ์ถ๋๋ ๋ถ๋ถ
//
// LoginViewController.swift
// Wetox-iOS
//
// Created by ๊น์ํ on 1/24/24.
//
import UIKit
import SnapKit
import KakaoSDKUser
import KakaoSDKAuth
import AuthenticationServices
import RxSwift
// ์๋ต
extension LoginViewController {
func loginWithAPI(tokenRequest: TokenRequest) {
AuthAPI.login(tokenRequest: tokenRequest)
.subscribe(onNext: { tokenResponse in
UserDefaults.standard.set(tokenRequest.oauthProvider, forKey: Const.UserDefaultsKey.oauthProvider)
UserDefaults.standard.set(tokenResponse.accessToken, forKey: Const.UserDefaultsKey.accessToken)
UserDefaults.standard.set(Date(), forKey: Const.UserDefaultsKey.updatedAt)
UserDefaults.standard.set(true, forKey: Const.UserDefaultsKey.isLogin)
print("๋ก๊ทธ์ธ ์ฑ๊ณต: \(tokenResponse)")
}, onError: { error in
print("๋ก๊ทธ์ธ ์คํจ: \(error.localizedDescription)")
})
.disposed(by: disposeBag)
}
}
'Programming > Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Swift] Alamofire vs Moya & URLSession์ ๋ํ์ฌ (0) | 2023.01.27 |
---|---|
[Swift] XCode Project File (.xcodeproj, .pbxproj) & Conflict์ ๋ํ์ฌ (0) | 2023.01.27 |
[Swift] SnapKit vs AutoLayout & Pros and cons of using 3rd-Party (0) | 2023.01.26 |
[Swift] CocoaPod vs Swift Package Manager (0) | 2023.01.26 |
[Swift] tap gesture (0) | 2022.11.21 |