From 377d5b8ac62537d52e7a453043e04a018739554b Mon Sep 17 00:00:00 2001 From: HyoWon Choi Date: Fri, 3 Mar 2023 02:40:13 +0900 Subject: [PATCH] =?UTF-8?q?[Refactor]#71=20UserAPI=20URL=20Session?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Keyneez/Keyneez.xcodeproj/project.pbxproj | 4 + .../Global/NetworkLayer/NetworkError.swift | 32 ++++++ .../Keyneez/Global/NetworkLayer/UserAPI.swift | 103 ++++++++++-------- .../Global/NetworkLayer/UserAPIProvider.swift | 82 +++++++------- 4 files changed, 138 insertions(+), 83 deletions(-) create mode 100644 Keyneez/Keyneez/Global/NetworkLayer/NetworkError.swift diff --git a/Keyneez/Keyneez.xcodeproj/project.pbxproj b/Keyneez/Keyneez.xcodeproj/project.pbxproj index 6e888e5..47aa5c5 100644 --- a/Keyneez/Keyneez.xcodeproj/project.pbxproj +++ b/Keyneez/Keyneez.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 0233C6FC296C8E4600A177E9 /* JellyDetailBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0233C6FB296C8E4600A177E9 /* JellyDetailBottomSheetViewController.swift */; }; 0233C6FE296C8F4A00A177E9 /* JellyDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0233C6FD296C8F4A00A177E9 /* JellyDetailView.swift */; }; 023B559429655CD200FB2462 /* CGFloat+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 023B559329655CD200FB2462 /* CGFloat+Extension.swift */; }; + 024EF5B429ABCE5A00085D0D /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024EF5B329ABCE5A00085D0D /* NetworkError.swift */; }; 0250056E296DB58600DE08BE /* PhoneLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0250056D296DB58600DE08BE /* PhoneLoginViewController.swift */; }; 02500573296DDF8F00DE08BE /* SignUpConstant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02500572296DDF8F00DE08BE /* SignUpConstant.swift */; }; 02500575296DEF7F00DE08BE /* SimplePwdCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02500574296DEF7F00DE08BE /* SimplePwdCollectionViewCell.swift */; }; @@ -194,6 +195,7 @@ 0233C6FB296C8E4600A177E9 /* JellyDetailBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyDetailBottomSheetViewController.swift; sourceTree = ""; }; 0233C6FD296C8F4A00A177E9 /* JellyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyDetailView.swift; sourceTree = ""; }; 023B559329655CD200FB2462 /* CGFloat+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extension.swift"; sourceTree = ""; }; + 024EF5B329ABCE5A00085D0D /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 0250056D296DB58600DE08BE /* PhoneLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneLoginViewController.swift; sourceTree = ""; }; 02500572296DDF8F00DE08BE /* SignUpConstant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpConstant.swift; sourceTree = ""; }; 02500574296DEF7F00DE08BE /* SimplePwdCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplePwdCollectionViewCell.swift; sourceTree = ""; }; @@ -736,6 +738,7 @@ B14D10B92959CC340004946F /* NetworkLayer */ = { isa = PBXGroup; children = ( + 024EF5B329ABCE5A00085D0D /* NetworkError.swift */, B1A80379296E9D680007DDD9 /* Encodable.swift */, B1A8037B296E9DB60007DDD9 /* NetworkLoggerPlugin.swift */, B8FC6639296F22EA00AEE9C3 /* ContentAPI.swift */, @@ -1292,6 +1295,7 @@ 022CB224296A2B370084DEF5 /* SignUpContentModel.swift in Sources */, B8C02F052962F5D4005612CE /* SettingViewController.swift in Sources */, B1A80363296DA9A40007DDD9 /* IDIssuedViewActionables.swift in Sources */, + 024EF5B429ABCE5A00085D0D /* NetworkError.swift in Sources */, 0233C6F8296C07A300A177E9 /* JellyProductCollectionViewCell.swift in Sources */, B18B60D2296552DA00AF14F5 /* NiblessViewController.swift in Sources */, B10AAD64296967F300AB8475 /* BenefitCollectionViewCell.swift in Sources */, diff --git a/Keyneez/Keyneez/Global/NetworkLayer/NetworkError.swift b/Keyneez/Keyneez/Global/NetworkLayer/NetworkError.swift new file mode 100644 index 0000000..e272d55 --- /dev/null +++ b/Keyneez/Keyneez/Global/NetworkLayer/NetworkError.swift @@ -0,0 +1,32 @@ +// +// NetworkError.swift +// Keyneez +// +// Created by 최효원 on 2023/02/27. +// + +import Foundation + +enum NetworkError: LocalizedError { + case unknownError + case invalidStatusCode(Int) + case invalidResponse(Error) + case components + case badRequest(Error) + case parsing(Error) + case emptyData + case decodeError + + var errorDescription: String? { + switch self { + case .unknownError: return "알수 없는 에러입니다." + case .invalidStatusCode: return "status코드가 200~299가 아닙니다." + case .invalidResponse: return "잘못된 응답입니다." + case .components: return "components를 생성 에러가 발생했습니다." + case .badRequest: return "URL request 관련 에러가 발생했습니다." + case .parsing: return "데이터 parsing 중에 에러가 발생했습니다." + case .emptyData: return "data가 비어있습니다." + case .decodeError: return "decode 에러가 발생했습니다." + } + } +} diff --git a/Keyneez/Keyneez/Global/NetworkLayer/UserAPI.swift b/Keyneez/Keyneez/Global/NetworkLayer/UserAPI.swift index a4b6377..13bf54d 100644 --- a/Keyneez/Keyneez/Global/NetworkLayer/UserAPI.swift +++ b/Keyneez/Keyneez/Global/NetworkLayer/UserAPI.swift @@ -6,7 +6,6 @@ // import Foundation -import Moya enum UserAPIError: LocalizedError { case encodingError @@ -23,7 +22,7 @@ enum UserAPI { case patchUserWithOCRTeenID(token: String, param: UserCheckYouthIDRequestDto) } -extension UserAPI: TargetType { +extension UserAPI { var baseURL: URL { return URL(string: "http://15.165.186.200:3000")! @@ -33,71 +32,89 @@ extension UserAPI: TargetType { switch self { case .getUserInfo: return URLConstant.user - case .postUserInfo, .patchUserPickInfo: + case .postUserInfo, + .patchUserPickInfo: return "/user/signup" - case .patchUserPwdInfo, .postPwdFetch: + case .patchUserPwdInfo, + .postPwdFetch: return "/user/signup/pw" case .postUserLoginInfo: return "/user/signin" - case .patchUserWithOCRSchoolID, .patchUserWithOCRTeenID, .getUserInfo: + case .patchUserWithOCRSchoolID, + .patchUserWithOCRTeenID, + .getUserInfo: return "/user" default: return "" } } - var method: Moya.Method { + var method: String { switch self { case .postUserInfo: - return .post - case .patchUserPickInfo: - return .patch - case .patchUserPwdInfo, .patchUserWithOCRTeenID, .patchUserWithOCRSchoolID: - return .patch - case .postPwdFetch: - return .post - case .postUserLoginInfo: - return .post + return "POST" + case .patchUserPickInfo, + .patchUserPwdInfo, + .patchUserWithOCRTeenID, + .patchUserWithOCRSchoolID: + return "PATCH" + case .postPwdFetch, + .postUserLoginInfo: + return "POST" case .getUserInfo: - return .get + return "GET" + } + } + + var headers: [String: String]? { + switch self { + case .postUserInfo, + .postUserLoginInfo: + return ["Content-Type": "application/json"] + case .patchUserPickInfo(let token, _), + .patchUserPwdInfo(let token, _), + .postPwdFetch(let token, _), + .getUserInfo(let token), + .patchUserWithOCRTeenID(let token, _), + .patchUserWithOCRSchoolID(let token, _): + return ["Content-Type": "application/json", "Authorization": token] + default: + return nil } } - var task: Moya.Task { + private func body() -> [String: Any]? { switch self { case .postUserInfo(let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) + return try? param.asParameter() case .patchUserPickInfo(_, let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) - case .patchUserPwdInfo(_, let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) + return try? param.asParameter() + case .patchUserPwdInfo(_, param: let param): + return try? param.asParameter() case .postPwdFetch(_, param: let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) - case .postUserLoginInfo(let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) + return try? param.asParameter() + case .postUserLoginInfo(param: let param): + return try? param.asParameter() + case .patchUserWithOCRSchoolID(_, param: let param): + return try? param.asParameter() + case .patchUserWithOCRTeenID(token: let token, param: let param): + return try? param.asParameter() case .getUserInfo: - return .requestPlain - case .patchUserWithOCRSchoolID(_, let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) - case .patchUserWithOCRTeenID(_, let param): - return .requestParameters(parameters: try! param.asParameter(), encoding: JSONEncoding.default) + return nil } } - private var vaildationType: Moya.ValidationType { - return .successAndRedirectCodes - } - - var headers: [String: String]? { - switch self { - case .postUserInfo, .postUserLoginInfo: - return ["Content-Type": "application/json"] - case .patchUserPickInfo(let token, _), .patchUserPwdInfo(let token, _), - .postPwdFetch(let token, _), .getUserInfo(let token), .patchUserWithOCRTeenID(let token, _), .patchUserWithOCRSchoolID(let token, _): - return ["Content-Type": "application/json", "Authorization": token] - default: - return nil +// Url Request setting 함수 + func asURLRequest() -> URLRequest { + let url = baseURL.appendingPathComponent(path) + var request = URLRequest(url: url) + request.httpMethod = method + request.allHTTPHeaderFields = headers + + if let param = body() { + let jsonData = try? JSONSerialization.data(withJSONObject: param) + request.httpBody = jsonData } + return request } - } diff --git a/Keyneez/Keyneez/Global/NetworkLayer/UserAPIProvider.swift b/Keyneez/Keyneez/Global/NetworkLayer/UserAPIProvider.swift index 2dbae05..deacf60 100644 --- a/Keyneez/Keyneez/Global/NetworkLayer/UserAPIProvider.swift +++ b/Keyneez/Keyneez/Global/NetworkLayer/UserAPIProvider.swift @@ -5,7 +5,6 @@ // Created by Jung peter on 1/11/23. // -import Moya import Foundation enum DecodeError: Error { @@ -21,71 +20,74 @@ struct UserInfo: Codable { final class UserAPIProvider { static let shared: UserAPIProvider = .init() - let userProvider = MoyaProvider(plugins: [NetworkLoggerPlugin(verbose: true)]) private init() { } func postUserInfo(param: ProductDanalRequestDto, completion: @escaping (Result) -> Void) { - let target = UserAPI.postUserInfo(param: param) - responseFrom(target, modelType: ProductDanalResponseDto.self, completion: completion) + makeRequest(UserAPI.postUserInfo(param: param), modelType: ProductDanalResponseDto.self, completion: completion) } func patchUserInfo(token: String, param: ProductJellyRequstDto, completion: @escaping (Result) -> Void) { - let target = UserAPI.patchUserPickInfo(token: token, param: param) - responseFrom(target, modelType: ProductJellyResponseDto.self, completion: completion) + makeRequest(UserAPI.patchUserPickInfo(token: token, param: param), modelType: ProductJellyResponseDto.self, completion: completion) } func patchPwdInfo(token: String, param: ProductPwdRequestDto, completion: @escaping (Result) -> Void) { - let target = UserAPI.patchUserPwdInfo(token: token, param: param) - responseFrom(target, modelType: ProductPwdResponseDto.self, completion: completion) + makeRequest(UserAPI.patchUserPwdInfo(token: token, param: param), modelType: ProductPwdResponseDto.self, completion: completion) } func postLoginInfo(param: LoginRequestDto, completion: @escaping (Result) -> Void) { - let target = UserAPI.postUserLoginInfo(param: param) - responseFrom(target, modelType: LoginResponseDto.self, completion: completion) + makeRequest(UserAPI.postUserLoginInfo(param: param), modelType: LoginResponseDto.self, completion: completion) } func patchInfoWithStudentIDOCR(token: String, param: UserCheckStudentIDRequestDto, completion: @escaping(Result) -> Void) { - let target = UserAPI.patchUserWithOCRSchoolID(token: token, param: param) - responseFrom(target, modelType: EditUserResponseDto.self, completion: completion) + makeRequest(UserAPI.patchUserWithOCRSchoolID(token: token, param: param), modelType: EditUserResponseDto.self, completion: completion) } func patchInfoWithTeenIDOCR(token: String, param: UserCheckYouthIDRequestDto, completion: @escaping(Result) -> Void) { - let target = UserAPI.patchUserWithOCRTeenID(token: token, param: param) - responseFrom(target, modelType: EditUserResponseDto.self, completion: completion) + makeRequest(UserAPI.patchUserWithOCRTeenID(token: token, param: param), modelType: EditUserResponseDto.self, completion: completion) } func getUserInfo(token: String, completion: @escaping (Result) -> Void) { - let target = UserAPI.getUserInfo(token: token) - responseFrom(target, modelType: UserInquiryResponseDto.self, completion: completion) + makeRequest(UserAPI.getUserInfo(token: token), modelType: UserInquiryResponseDto.self, completion: completion) } } extension UserAPIProvider { - - func responseFrom(_ target: UserAPI, modelType: T.Type, completion: @escaping (Result) -> Void) { - userProvider.request(target) { result in - self.process(type: modelType, result: result, completion: completion) - } - } - - func process( - type: T.Type, - result: Result, - completion: @escaping (Result) -> Void - ) { - switch result { - case .success(let response): - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - if let data = try? decoder.decode(GenericResponse.self, from: response.data), data.status >= 200 && data.status < 400 { - let body = data.data - completion(.success(body as? T)) - } else { - completion(.failure(DecodeError.decodeError)) + + func makeRequest(_ target: UserAPI, modelType: T.Type, completion: @escaping (Result) -> Void) { + //세션 생성 + let session = URLSession.shared + //task 지정 + let task = session.dataTask(with: target.asURLRequest()) { data, response, error in + + //에러 처리 - Response + if let error = error { + completion(.failure(error)) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(NetworkError.invalidResponse as! Error)) + return + } + + let statusCode = httpResponse.statusCode + guard (200..<300).contains(statusCode) else { + completion(.failure(NetworkError.invalidStatusCode(statusCode))) + return + } + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + + if let data = data, let response = try? decoder.decode(GenericResponse.self, from: data) { + let body = response.data + completion(.success(body as? T)) + } else { + completion(.failure(DecodeError.decodeError)) + } } - case .failure(let error): - completion(.failure(error)) + + task.resume() } } -}