Skip to content

Instantly share code, notes, and snippets.

@NunciosChums
Last active June 8, 2023 04:25
Show Gist options
  • Select an option

  • Save NunciosChums/841b2c3935b2028b2162842d479de143 to your computer and use it in GitHub Desktop.

Select an option

Save NunciosChums/841b2c3935b2028b2162842d479de143 to your computer and use it in GitHub Desktop.

Revisions

  1. Jin Changhoon revised this gist Jun 8, 2020. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion handleResponse.swift
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,9 @@ extension PrimitiveSequence where Trait == SingleTrait, Element == Response {
    if let newToken = try? response.map(Token.self) {
    UserDefaults.accessToken = newToken.accessToken
    UserDefaults.refreshToken = newToken.refreshToken
    } else if (200 ... 299) ~= response.statusCode {
    }

    if (200 ... 299) ~= response.statusCode {
    return Single.just(response)
    }

  2. Jin Changhoon revised this gist Jun 8, 2020. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions handleResponse.swift
    Original file line number Diff line number Diff line change
    @@ -10,9 +10,7 @@ extension PrimitiveSequence where Trait == SingleTrait, Element == Response {
    if let newToken = try? response.map(Token.self) {
    UserDefaults.accessToken = newToken.accessToken
    UserDefaults.refreshToken = newToken.refreshToken
    }

    if (200 ... 299) ~= response.statusCode {
    } else if (200 ... 299) ~= response.statusCode {
    return Single.just(response)
    }

  3. Jin Changhoon revised this gist Jun 6, 2020. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions How To Call.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    TestService.shared.user(name: "Moya")
    .map(TestModel.self)
    .subscribe(
    onSuccess: {
    print("=== user: \($0)")
    },
    onError: {
    print("==== error: \($0)")
    }
    ).disposed(by: disposeBag)
  4. Jin Changhoon revised this gist Jun 6, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions BaseService.swift
    Original file line number Diff line number Diff line change
    @@ -12,6 +12,7 @@ class BaseService<API: TargetType> {
    private let provider = MoyaProvider<API>(plugins: [RequestLoggingPlugin()])

    /// 네트워크 호출
    /// help from https://github.com/Moya/Moya/issues/1177#issuecomment-345132374
    func request(_ api: API) -> Single<Response> {
    return provider.rx.request(api)
    .flatMap {
  5. Jin Changhoon created this gist Jun 6, 2020.
    49 changes: 49 additions & 0 deletions AuthService.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,49 @@
    import Foundation
    import Moya
    import RxSwift

    /// 인증 관련 API
    final class AuthService: BaseService<AuthAPI> {
    static let shared = AuthService()
    private override init() {}

    /// 토큰 재발급
    func renewalToken(refreshToken: String) -> Single<Response> {
    return request(.renewalToken(refreshToken))
    }
    }

    // MARK: - API

    enum AuthAPI {
    /// 토큰 재발급
    case renewalToken(String)
    }

    extension AuthAPI: BaseAPI {
    var path: String {
    let apiPath = "/api-as/v1"

    switch self {
    case .renewalToken:
    return "\(apiPath)/\("renewalToken".lowercased())"
    }
    }

    var method: Moya.Method {
    switch self {
    case .renewalToken:
    return .post
    }
    }

    var task: Task {
    switch self {
    case let .renewalToken(refreshToken):
    return .requestParameters(
    parameters: ["refreshToken": refreshToken],
    encoding: JSONEncoding.default
    )
    }
    }
    }
    16 changes: 16 additions & 0 deletions BaseAPI.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    import Foundation
    import Moya

    protocol BaseAPI: TargetType {}

    extension BaseAPI {
    var baseURL: URL { URL(string: "https://api.github.com")! }

    var method: Moya.Method { .get }

    var sampleData: Data { Data() }

    var task: Task { .requestPlain }

    var headers: [String: String]? { nil }
    }
    34 changes: 34 additions & 0 deletions BaseService.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,34 @@
    import Moya
    import RxMoya
    import RxSwift

    /// 네트워크 호출 상속용
    /// https://github.com/Moya/Moya
    class BaseService<API: TargetType> {
    // moya에서 지원하는 로깅 플러그인
    // private let provider = MoyaProvider<API>(plugins: [NetworkLoggerPlugin()])

    // 커스텀 로깅 플러그인
    private let provider = MoyaProvider<API>(plugins: [RequestLoggingPlugin()])

    /// 네트워크 호출
    func request(_ api: API) -> Single<Response> {
    return provider.rx.request(api)
    .flatMap {
    // 401(Unauthorized) 발생 시 자동으로 토큰을 재발급 받는다
    if $0.statusCode == 401 {
    throw TokenError.tokenExpired
    } else {
    return Single.just($0)
    }
    }
    .retryWhen { (error: Observable<TokenError>) in
    error.flatMap { error -> Single<Response> in
    AuthService.shared.renewalToken() // 토큰 재발급 받기
    }
    }
    .handleResponse()
    .filterSuccessfulStatusCodes()
    .retry(2)
    }
    }
    72 changes: 72 additions & 0 deletions RequestLoggingPlugin.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    import Foundation
    import Moya

    /// 네트워크 호출 결과 로그 표시
    final class RequestLoggingPlugin: PluginType {
    /// API를 보내기 직전에 호출
    func willSend(_ request: RequestType, target: TargetType) {
    guard let httpRequest = request.request else {
    print("--> invalid request")
    return
    }

    let url = httpRequest.description
    let method = httpRequest.httpMethod ?? "unknown method"

    var log = "--> \(method) \(url)\n"
    log.append("API: \(target)\n")

    if let headers = httpRequest.allHTTPHeaderFields, !headers.isEmpty {
    log.append("header: \(headers)\n")
    }

    if let body = httpRequest.httpBody, let bodyString = String(bytes: body, encoding: String.Encoding.utf8) {
    log.append("\(bodyString)\n")
    }

    log.append("--> END \(method)")
    print(log)
    }

    /// API Response
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
    switch result {
    case let .success(response):
    onSuceed(response, target: target, isFromError: false)
    case let .failure(error):
    onFail(error, target: target)
    }
    }

    func onSuceed(_ response: Response, target: TargetType, isFromError: Bool) {
    let request = response.request
    let url = request?.url?.absoluteString ?? "nil"
    let statusCode = response.statusCode

    var log = "<-- \(statusCode) \(url)\n"
    log.append("API: \(target)\n")

    response.response?.allHeaderFields.forEach {
    log.append("\($0): \($1)\n")
    }

    if let reString = String(bytes: response.data, encoding: String.Encoding.utf8) {
    log.append("\(reString)\n")
    }

    log.append("<-- END HTTP (\(response.data.count)-byte body)")
    print(log)
    }

    func onFail(_ error: MoyaError, target: TargetType) {
    if let response = error.response {
    onSuceed(response, target: target, isFromError: true)
    return
    }

    var log = "<-- \(error.errorCode) \(target)\n"
    log.append("\(error.failureReason ?? error.errorDescription ?? "unknown error")\n")
    log.append("<-- END HTTP")
    print(log)
    }
    }
    5 changes: 5 additions & 0 deletions ResponseError.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    struct ResponseError: Decodable, Error {
    var statusCode: Int?
    let message: String
    let documentation_url: String
    }
    7 changes: 7 additions & 0 deletions TestModel.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    import Foundation

    struct TestModel: Decodable {
    let id: Int
    let name: String
    let login: String
    }
    28 changes: 28 additions & 0 deletions TestService.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    import Foundation
    import Moya
    import RxSwift

    final class TestService: BaseService<TestAPI> {
    static let shared = TestService()
    private override init() {}

    /// 아이디로 사용자 정보 가져오기
    /// - Parameters:
    /// - name: 로그인 아이디
    func user(name: String) -> Single<Response> {
    return request(.profile(name))
    }
    }

    enum TestAPI {
    case profile(String)
    }

    extension TestAPI: BaseAPI {
    var path: String {
    switch self {
    case let .profile(name):
    return "/users/\(name)"
    }
    }
    }
    6 changes: 6 additions & 0 deletions Token.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    struct Token: Decodable {
    let tokenType: String
    let accessToken: String
    let refreshToken: String
    let expiresAt: TimeInterval
    }
    37 changes: 37 additions & 0 deletions handleResponse.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,37 @@
    import Foundation
    import Moya
    import RxSwift

    /// 서버에서 보내주는 오류 문구 파싱용
    extension PrimitiveSequence where Trait == SingleTrait, Element == Response {
    func handleResponse() -> Single<Element> {
    return flatMap { response in
    // 토큰 재발급 받았을 때 토큰 변경함
    if let newToken = try? response.map(Token.self) {
    UserDefaults.accessToken = newToken.accessToken
    UserDefaults.refreshToken = newToken.refreshToken
    }

    if (200 ... 299) ~= response.statusCode {
    return Single.just(response)
    }

    if var error = try? response.map(ResponseError.self) {
    error.statusCode = response.statusCode
    return Single.error(error)
    }

    // Its an error and can't decode error details from server, push generic message
    let genericError = ResponseError(statusCode: response.statusCode
    serverName: "unknown Server Name",
    error: "unknown error",
    message: "empty message")
    return Single.error(genericError)
    }
    }
    }

    /// 토큰 만료 에러
    enum TokenError: Swift.Error {
    case tokenExpired
    }