728x90
리액터킷 적용 예제를 살펴보았습니다.
지난 글에서 로그인 액션이 발생하였을 때 setLoading(true) Mutation을 발생시키며 현재 로딩중임을 알리고
authorize()라는 인증하는 비즈니스 로직을 호출하는 흐름을 쭉 훑어보았습니다.
해당 인증 로직은 서비스 레이어로 넘긴 것을 알 수 있었죠.
서비스 레이어를 살펴보도록 하겠습니다.
비즈니스 로직이 있는 리액터(ViewModel)에 필요한 데이터들을 불러올때
추상화 시킨 데이터 리포지토리(Network디렉토리)를 통해 데이터 소스(local, remote 등등)에 상관없이 서비스 레이어에서 하나의 인터페이스로 데이터를 불러 사용할 수 있도록 구성해두었다는 것을 알 수 있습니다.
final class UserService: UserServiceType {
fileprivate let networking: DrrribleNetworking
init(networking: DrrribleNetworking) {
self.networking = networking
}
fileprivate let userSubject = ReplaySubject<User?>.create(bufferSize: 1)
lazy var currentUser: Observable<User?> = self.userSubject.asObservable()
.startWith(nil)
.share(replay: 1)
func fetchMe() -> Single<Void> {
return self.networking.request(.me)
.map(User.self)
.do(onSuccess: { [weak self] user in
self?.userSubject.onNext(user)
})
.map { _ in }
}
}
final class AuthService: AuthServiceType {
fileprivate let clientID = "130182af71afe5247b857ef622bd344ca5f1c6144c8fa33c932628ac31c5ad78"
fileprivate let clientSecret = "bbebedc51c2301049c2cb57953efefc30dc305523b8fdfadb9e9a25cb81efa1e"
fileprivate var currentViewController: UIViewController?
fileprivate let callbackSubject = PublishSubject<String>()
fileprivate let keychain = Keychain(service: "com.drrrible.ios")
private(set) var currentAccessToken: AccessToken?
private let navigator: NavigatorType
init(navigator: NavigatorType) {
self.navigator = navigator
self.currentAccessToken = self.loadAccessToken()
log.debug("currentAccessToken exists: \(self.currentAccessToken != nil)")
}
func authorize() -> Observable<Void> {
let parameters: [String: String] = [
"client_id": self.clientID,
"scope": "public+write+comment+upload",
]
let parameterString = parameters.map { "\($0)=\($1)" }.joined(separator: "&")
let url = URL(string: "https://dribbble.com/oauth/authorize?\(parameterString)")!
// Default animation of presenting SFSafariViewController is similar to 'push' animation
// (from right to left). To use 'modal' animation (from bottom to top), we have to wrap
// SFSafariViewController with UINavigationController and set navigation bar hidden.
let safariViewController = SFSafariViewController(url: url)
let navigationController = UINavigationController(rootViewController: safariViewController)
navigationController.isNavigationBarHidden = true
self.navigator.present(navigationController)
self.currentViewController = navigationController
return self.callbackSubject
.flatMap(self.accessToken)
.do(onNext: { [weak self] accessToken in
try self?.saveAccessToken(accessToken)
self?.currentAccessToken = accessToken
})
.map { _ in }
}
func callback(code: String) {
self.callbackSubject.onNext(code)
self.currentViewController?.dismiss(animated: true, completion: nil)
self.currentViewController = nil
}
func logout() {
self.currentAccessToken = nil
self.deleteAccessToken()
}
fileprivate func accessToken(code: String) -> Single<AccessToken> {
let urlString = "https://dribbble.com/oauth/token"
let parameters: Parameters = [
"client_id": self.clientID,
"client_secret": self.clientSecret,
"code": code,
]
return Single.create { observer in
let request = AF
.request(urlString, method: .post, parameters: parameters)
.responseData { response in
switch response.result {
case let .success(jsonData):
do {
let accessToken = try JSONDecoder().decode(AccessToken.self, from: jsonData)
observer(.success(accessToken))
} catch let error {
observer(.error(error))
}
case let .failure(error):
observer(.error(error))
}
}
return Disposables.create {
request.cancel()
}
}
}
fileprivate func saveAccessToken(_ accessToken: AccessToken) throws {
try self.keychain.set(accessToken.accessToken, key: "access_token")
try self.keychain.set(accessToken.tokenType, key: "token_type")
try self.keychain.set(accessToken.scope, key: "scope")
}
fileprivate func loadAccessToken() -> AccessToken? {
guard let accessToken = self.keychain["access_token"],
let tokenType = self.keychain["token_type"],
let scope = self.keychain["scope"]
else { return nil }
return AccessToken(accessToken: accessToken, tokenType: tokenType, scope: scope)
}
fileprivate func deleteAccessToken() {
try? self.keychain.remove("access_token")
try? self.keychain.remove("token_type")
try? self.keychain.remove("scope")
}
}
원래는 더 자세하게 코드 한줄한줄 살펴보고자 하였으나 일단 여기서 멈추게 되었습니다.
Drrrible과 달리 현재 Bidit의 레파지토리에서 데이터를 받아오는 수단으로 Apolo를 사용하고 있고
Rx로 콜백을 처리하지 않고 Completion 구조로 받아와서 그 콜백 내에서 옵저버로 콜을 해주고 있기 때문에
현재 코드에 더 집중해보고자 합니다.
728x90
'IOS' 카테고리의 다른 글
[iOS] (까다로운 디자인 수정) UITabbar line 제거 후 Shadow 적용. (0) | 2022.11.13 |
---|---|
[iOS] 앱스토어 리젝 사유 - 에이젠 (0) | 2022.11.12 |
[iOS] 페이징 버벅임 현상 해결해보기 (0) | 2022.10.12 |
[iOS] 화면전환시 StatusBar 회색으로 깜박임 현상 해결하기 (0) | 2022.10.11 |
[iOS] ReactorKit 프로젝트 Drrrible 뜯어보기(1) (0) | 2022.10.07 |
댓글