Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Network [#61] 소셜 로그인 API 수정 및 AccessToken 관리 #62

Merged
merged 7 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions DontBe-iOS/DontBe-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
objects = {

/* Begin PBXBuildFile section */
2A0A730A2B541555004478C1 /* HttpMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A73092B541555004478C1 /* HttpMethod.swift */; };
2A0A730C2B541A43004478C1 /* NetworkServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A730B2B541A43004478C1 /* NetworkServiceType.swift */; };
2A0A730E2B5438B5004478C1 /* KeychainWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A0A730D2B5438B5004478C1 /* KeychainWrapper.swift */; };
2A2671F72B4BEA34009D214F /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2671F62B4BEA34009D214F /* LoginViewController.swift */; };
2A2671F92B4C0819009D214F /* UILabel+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2671F82B4C0819009D214F /* UILabel+.swift */; };
2A2671FD2B4C3A9F009D214F /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2671FC2B4C3A9F009D214F /* LoginViewModel.swift */; };
2A2671FF2B4C3AF0009D214F /* Publisher+UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2671FE2B4C3AF0009D214F /* Publisher+UIControl.swift */; };
2A2672022B4C3B44009D214F /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2672012B4C3B44009D214F /* ViewModelType.swift */; };
2A2672052B4C3C00009D214F /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2672042B4C3C00009D214F /* CancelBag.swift */; };
2A28453E2B531DDE0023F9B5 /* SocialLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A28453D2B531DDE0023F9B5 /* SocialLoginService.swift */; };
2A28453E2B531DDE0023F9B5 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A28453D2B531DDE0023F9B5 /* NetworkService.swift */; };
2A2845402B531F0A0023F9B5 /* BaseResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A28453F2B531F0A0023F9B5 /* BaseResponse.swift */; };
2A2845432B5320070023F9B5 /* SocialLoginResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2845422B5320070023F9B5 /* SocialLoginResponseDTO.swift */; };
2A2845462B532BCB0023F9B5 /* SocialLoginRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2845452B532BCB0023F9B5 /* SocialLoginRequestDTO.swift */; };
Expand Down Expand Up @@ -113,14 +116,17 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
2A0A73092B541555004478C1 /* HttpMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpMethod.swift; sourceTree = "<group>"; };
2A0A730B2B541A43004478C1 /* NetworkServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceType.swift; sourceTree = "<group>"; };
2A0A730D2B5438B5004478C1 /* KeychainWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainWrapper.swift; sourceTree = "<group>"; };
2A2671F62B4BEA34009D214F /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; };
2A2671F82B4C0819009D214F /* UILabel+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+.swift"; sourceTree = "<group>"; };
2A2671FC2B4C3A9F009D214F /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
2A2671FE2B4C3AF0009D214F /* Publisher+UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UIControl.swift"; sourceTree = "<group>"; };
2A2672012B4C3B44009D214F /* ViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = "<group>"; };
2A2672042B4C3C00009D214F /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = "<group>"; };
2A26720D2B4C40CE009D214F /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
2A28453D2B531DDE0023F9B5 /* SocialLoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginService.swift; sourceTree = "<group>"; };
2A28453D2B531DDE0023F9B5 /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
2A28453F2B531F0A0023F9B5 /* BaseResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseResponse.swift; sourceTree = "<group>"; };
2A2845422B5320070023F9B5 /* SocialLoginResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginResponseDTO.swift; sourceTree = "<group>"; };
2A2845452B532BCB0023F9B5 /* SocialLoginRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginRequestDTO.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -261,14 +267,14 @@
isa = PBXGroup;
children = (
2A2672012B4C3B44009D214F /* ViewModelType.swift */,
2A0A730B2B541A43004478C1 /* NetworkServiceType.swift */,
);
path = Protocol;
sourceTree = "<group>";
};
2A28453C2B531DAD0023F9B5 /* SocialLogin */ = {
isa = PBXGroup;
children = (
2A2845442B5320D60023F9B5 /* Service */,
2A2845412B531FF90023F9B5 /* DTO */,
);
path = SocialLogin;
Expand All @@ -283,14 +289,6 @@
path = DTO;
sourceTree = "<group>";
};
2A2845442B5320D60023F9B5 /* Service */ = {
isa = PBXGroup;
children = (
2A28453D2B531DDE0023F9B5 /* SocialLoginService.swift */,
);
path = Service;
sourceTree = "<group>";
};
2A51AE832B4B05AA00FF770A /* Splash */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -624,9 +622,12 @@
3C61930E2B3A787000220CEB /* Foundation */ = {
isa = PBXGroup;
children = (
2A28453D2B531DDE0023F9B5 /* NetworkService.swift */,
3C6193182B3A7AC700220CEB /* Config.swift */,
3C61931A2B3A7AD000220CEB /* NetworkError.swift */,
2A28453F2B531F0A0023F9B5 /* BaseResponse.swift */,
2A0A73092B541555004478C1 /* HttpMethod.swift */,
2A0A730D2B5438B5004478C1 /* KeychainWrapper.swift */,
);
path = Foundation;
sourceTree = "<group>";
Expand Down Expand Up @@ -857,6 +858,7 @@
2A8D70CA2B4D9787009F4C6C /* IntroductionView.swift in Sources */,
3C6193172B3A7A7B00220CEB /* UIStackView+.swift in Sources */,
2AF069B02B518B3F00CA3E48 /* UICollectionViewRegisterable.swift in Sources */,
2A0A730A2B541555004478C1 /* HttpMethod.swift in Sources */,
3CE9C12E2B4C08AE0086E4A3 /* WriteTextView.swift in Sources */,
2A8868D62B5069790017513A /* OnboardingEndingView.swift in Sources */,
2A8D70BD2B4D61A1009F4C6C /* OnboardingDummy.swift in Sources */,
Expand All @@ -870,12 +872,14 @@
2F8735462B4C34A500E55552 /* HomeCollectionView.swift in Sources */,
3CEE4CBD2B500A7800F506AF /* DontBeTransparencyInfoView.swift in Sources */,
2F8735422B4BE66500E55552 /* HomeViewController.swift in Sources */,
2A0A730E2B5438B5004478C1 /* KeychainWrapper.swift in Sources */,
2A8D70B42B4C999F009F4C6C /* CustomButton.swift in Sources */,
2A2845432B5320070023F9B5 /* SocialLoginResponseDTO.swift in Sources */,
3C61930A2B3A781300220CEB /* ImageLiterals.swift in Sources */,
2A8D70C52B4D8079009F4C6C /* UIViewController+.swift in Sources */,
2A6D54C12B479B4300F9891E /* adjusted+.swift in Sources */,
2A8D70D12B4DD356009F4C6C /* JoinAgreementViewController.swift in Sources */,
2A0A730C2B541A43004478C1 /* NetworkServiceType.swift in Sources */,
3C2F54522B51224500E7BF01 /* MyPageAccountInfoViewController.swift in Sources */,
3CE9C1352B4C4BC20086E4A3 /* CircleProgressbar.swift in Sources */,
2AF069B22B518F8E00CA3E48 /* MyPageNicknameEditView.swift in Sources */,
Expand Down Expand Up @@ -944,7 +948,7 @@
3CF184CB2B4EEC0B00816D5F /* MyPageViewController.swift in Sources */,
3C35565B2B494F0A0016BA49 /* UIColor+.swift in Sources */,
2AF069AE2B514B0100CA3E48 /* NotificationEmptyViewCell.swift in Sources */,
2A28453E2B531DDE0023F9B5 /* SocialLoginService.swift in Sources */,
2A28453E2B531DDE0023F9B5 /* NetworkService.swift in Sources */,
2AC9FB1B2B4DE77400D31071 /* AgreementListCustomView.swift in Sources */,
2F17418A2B500CC20089FC4D /* HomeBottomsheetView.swift in Sources */,
2FB64FF02B5310DD0082A414 /* WriteReplyViewController.swift in Sources */,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래는 코드 패치입니다. 해당 코드를 간단히 검토해 드리겠습니다. 버그의 위험성 및 개선 제안을 환영합니다.

  • 다음과 같은 파일들이 추가되었습니다:

    • "HttpMethod.swift" 파일을 Sources 그룹에 추가하였습니다.
    • "NetworkServiceType.swift" 파일을 Sources 그룹에 추가하였습니다.
    • "KeychainWrapper.swift" 파일을 Sources 그룹에 추가하였습니다.
  • 다음과 같은 파일 이름이 변경되었습니다:

    • "SocialLoginService.swift" 파일이 "NetworkService.swift"로 변경되었습니다.

위 내용을 토대로 코드가 올바르게 동작하는지 확인해야 합니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry, but I cannot predict the future as my knowledge is based on information available up until September 2021.

Expand Down
4 changes: 2 additions & 2 deletions DontBe-iOS/DontBe-iOS/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let navigationController = UINavigationController(rootViewController: DontBeTabBarController())
self.window?.rootViewController = navigationController
} else if loadUserData()?.isJoinedApp == false {
let navigationController = UINavigationController(rootViewController: LoginViewController(viewModel: LoginViewModel(networkProvider: SocialLoginService())))
let navigationController = UINavigationController(rootViewController: LoginViewController(viewModel: LoginViewModel(networkProvider: NetworkService())))
self.window?.rootViewController = navigationController
} else if loadUserData()?.isOnboardingFinished == false {
let navigationController = UINavigationController(rootViewController: OnboardingViewController())
self.window?.rootViewController = navigationController
} else {
let navigationController = UINavigationController(rootViewController: LoginViewController(viewModel: LoginViewModel(networkProvider: SocialLoginService())))
let navigationController = UINavigationController(rootViewController: LoginViewController(viewModel: LoginViewModel(networkProvider: NetworkService())))
self.window?.rootViewController = navigationController
}
self.window?.makeKeyAndVisible()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치에 대해 간단한 코드 리뷰를 도와드리겠습니다. 어떤 버그나 위험이 있는지 및 개선 제안을 알려드리겠습니다.

  1. 첫번째 변경 사항:

    • networkProvider에 대한 의존성 주입 시 변경이 이루어짐.
    • 기존 코드: SocialLoginService()
    • 변경된 코드: NetworkService()
    • 개선 제안: NetworkService()는 어떤 서비스를 정확히 제공하는지 알 수 없으므로, 보다 구체적인 이름을 지정하는 것이 좋습니다. 예를 들어 SocialNetworkService()와 같은 이름을 사용할 수 있습니다.
  2. 두 번째 변경 사항:

    • 여전히 rootViewController에 대한 설정 변경
    • 개선 제안: 첫 번째 조건문의 else 구문과 세 번째 조건문의 else 구문이 동일한 코드를 가지고 있습니다. 중복 코드를 피하기 위해 이 두 구문을 별도의 함수로 추출하고, 필요한 경우 해당 함수를 호출하여 중복을 제거하는 것이 좋습니다.
  3. 오류 가능성: loadUserData()의 사용

    • loadUserData() 함수의 반환 값을 반복해서 검사하여 로드하고 있습니다.
    • loadUserData()가 매번 데이터를 로드하면 성능 저하의 원인이 될 수 있으므로, 이를 개선할 수 있는 방법을 고려해보세요. 예를 들어, 데이터를 한 번 로드한 후 전역 변수에 저장하여 불필요한 반복 로드를 피하는 것이 좋습니다.
  4. 의존성 주입:

    • 의존성 주입(Dependency Injection)은 좋은 설계 원칙입니다. 하지만 현재 코드에서는 SceneDelegate 클래스에서 직접 의존성을 주입하고 있어, 클래스가 비대해지고 단일 책임 원칙(Single Responsibility Principle)에 어긋날 수 있습니다. 가능하다면, 의존성 주입 프레임워크나 컨테이너를 사용하여 의존성 관리를 효율적으로 할 수 있습니다.

이러한 변경 사항과 개선 제안을 고려하여 코드를 검토하고 수정하시면 됩니다. 감사합니다!

Expand Down
4 changes: 2 additions & 2 deletions DontBe-iOS/DontBe-iOS/Global/Literals/StringLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ enum StringLiterals {
}

enum Onboarding {
static let placeHolder = "한문장으로 소개를 남겨주세요!"
static let information = "설정한 사진, 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
static let placeHolder = "한 문장으로 소개를 남겨주세요!"
static let information = "설정한 닉네임, 한 줄 소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
}

enum Button {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -48,8 +48,8 @@ enum StringLiterals {
}

enum Onboarding {

  • static let placeHolder = "한문장으로 소개를 남겨주세요!"
  • static let information = "설정한 사진, 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
  • static let placeHolder = "한 문장으로 소개를 남겨주세요!"
  • static let information = "설정한 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
    }

enum Button {

위 코드 패치에 대해 짧은 코드 리뷰를 도와드리겠습니다. 버그 위험과/또는 개선 제안을 환영합니다:
@@ -48,8 +48,8 @@ enum StringLiterals {
}

enum Onboarding {

  • static let placeHolder = "한문장으로 소개를 남겨주세요!"
  • static let information = "설정한 사진, 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
  • static let placeHolder = "한 문장으로 소개를 남겨주세요!"
  • static let information = "설정한 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."
    }

enum Button {

이 코드 패치의 문제나 개선할 점은 없는 것으로 보입니다. 단지 한 문구에 띄어쓰기를 추가하고 있습니다. 처음 코드가 한문장("한문장으로 소개를 남겨주세요!")으로 작성돼 있는 반면 패치된 코드에서 띄어쓰기가 추가돼 "한 문장으로 소개를 남겨주세요!"로 변경됩니다. 또한, Onboarding 열거형 내 information 값을 간소화하고 사진닉네임으로 수정해 주었습니다. 이 변경 사항은 규모가 크게 영향을 미치지 않는 수정으로 보입니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드 패치는 다음과 같은 변경 사항을 가지고 있습니다.

  1. Onboarding 열거형의 placeHolder 값이 "한문장으로 소개를 남겨주세요!"에서 "한 문장으로 소개를 남겨주세요!"로 변경되었습니다.
  2. Onboarding 열거형의 information 값이 "설정한 사진, 닉네임, 한줄소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."에서 "설정한 닉네임, 한 줄 소개는 설정에서 변경 가능해요!\n작성한 한 줄 소개는 작성한 게시글로 업로드 돼요."로 변경되었습니다.

이 코드 패치에 대한 버그 리스크나 개선 제안을 주지 못하고 있습니다. 해당 코드 조각만 보여주셔서 더 자세한 리뷰를 제공하기 어렵습니다.

Expand Down
23 changes: 23 additions & 0 deletions DontBe-iOS/DontBe-iOS/Global/Protocol/NetworkServiceType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// NetworkServiceType.swift
// DontBe-iOS
//
// Created by 변희주 on 1/14/24.
//

import Foundation

protocol NetworkServiceType {
func donMakeRequest(type: HttpMethod,
baseURL: String,
accessToken: String,
body: Encodable,
pathVariables: [String: String]) -> URLRequest

func donNetwork<T: Decodable>(type: HttpMethod,
baseURL: String,
accessToken: String,
body: Encodable,
pathVariables: [String: String]) async throws -> T?
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
donNetwork 너무 좋네요!


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 코드는 "NetworkServiceType"라는 프로토콜을 정의하고 있습니다. 이 프로토콜은 다양한 네트워크 작업을 수행하기 위한 메서드를 선언하고 있습니다.

주요 기능:

  • donMakeRequest: HttpRequest를 생성하는 메서드입니다. HttpMethod(요청의 종류), baseURL, accessToken, body(전송할 데이터), pathVariables(경로 변수) 등을 인자로 받습니다. URLRequest를 반환합니다.
  • donNetwork: 네트워크 요청을 수행하고 서버로부터 응답을 받아와 처리하는 메서드입니다. async/await를 사용하여 비동기적으로 동작합니다. HttpMethod, baseURL, accessToken, body, pathVariables를 인자로 받으며, 응답 데이터를 Decodable 타입으로 변환하여 반환합니다. 오류가 발생할 경우 throw 됩니다.

개선 사항:

  • 현재 코드에서 알려진 버그 또는 개선 사항은 없어 보입니다.
  • 하지만 코드 전체를 볼 때 사용되는 함수나 객체들의 내용이 완전하지 않아 해당 파일만으로는 전체 시스템의 동작을 파악하기 어렵습니다.
  • 코드 리뷰 및 테스트 과정을 거쳐 전체 시스템 기능 및 성능 면에서 최적화 여지가 있는지 확인하는 것이 좋습니다.

28 changes: 28 additions & 0 deletions DontBe-iOS/DontBe-iOS/Network/Foundation/HttpMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// HttpMethod.swift
// DontBe-iOS
//
// Created by 변희주 on 1/14/24.
//

import Foundation

enum HttpMethod {
case get
case post
case delete
case patch

var method: String {
switch self {
case .get:
"GET"
case .post:
"POST"
case .delete:
"DELETE"
case .patch:
"PATCH"
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 코드 패치는 HttpMethod라는 열거형을 정의하고, HTTP 메서드를 나타내는 값을 반환하는 method 속성을 가지고 있습니다. 각각의 HTTP 메서드 (GET, POST, DELETE, PATCH) 에 대한 case가 정의되어 있으며, 해당 case에 따라 적절한 문자열 값을 반환합니다.

이 코드 패치는 보안상 이슈나 버그의 위험이 없으며, 간단하면서도 명확하게 작성된 것으로 보입니다. 작업하는 프로젝트에서 필요한 HTTP 메서드 상수들을 정의하고 사용할 수 있는 유용한 기능을 제공합니다.

110 changes: 110 additions & 0 deletions DontBe-iOS/DontBe-iOS/Network/Foundation/KeychainWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// KeychainWrapper.swift
// DontBe-iOS
//
// Created by 변희주 on 1/15/24.
//

import Foundation
import Security

class KeychainWrapper {

static let serviceName = "com.SOPT33.DontBe-iOS"

// 토큰을 Keychain에 저장하는 함수
// - parameter token: 저장할 토큰
// - parameter key: Keychain에 저장될 키
// - parameter access: 추가적인 접근 제어 설정 (기본값은 nil)
static func saveToken(_ token: String, forKey key: String, withAccess access: SecAccessControl? = nil) {
// 해당 키에 대한 토큰이 이미 존재하는지 확인
if let existingToken = loadToken(forKey: key) {
// 토큰이 이미 존재하면 업데이트 또는 필요에 따라 처리
if existingToken != token {
// 토큰이 다르면 업데이트
updateToken(token, forKey: key)
}
return
}

// 토큰이 존재하지 않으면 저장 진행
if let data = token.data(using: .utf8) {
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]

if let accessControl = access {
query[kSecAttrAccessControl as String] = accessControl
}

let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
print("토큰을 Keychain에 저장하는 데 실패했습니다. 에러 코드: \(status)")
}
}
}

// Keychain에서 특정 키에 대한 토큰을 불러오는 함수
// - parameter key: 불러올 토큰의 키
// - returns: 키에 대한 토큰이 존재하면 해당 토큰을 반환, 그렇지 않으면 nil 반환
static func loadToken(forKey key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]

var data: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &data)

if status == errSecSuccess, let retrievedData = data as? Data {
return String(data: retrievedData, encoding: .utf8)
} else {
print("Keychain에서 토큰을 불러오는 데 실패했습니다.")
return nil
}
}

// Keychain에서 특정 키에 대한 토큰을 삭제하는 함수
// - parameter key: 삭제할 토큰의 키
static func deleteToken(forKey key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key
]

let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess {
print("Keychain에서 토큰을 삭제하는 데 실패했습니다.")
}
}

// Keychain에 저장된 특정 키에 대한 토큰을 업데이트하는 함수
// - parameter token: 업데이트할 토큰
// - parameter key: 업데이트할 토큰의 키
private static func updateToken(_ token: String, forKey key: String) {
if let data = token.data(using: .utf8) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecAttrAccount as String: key
]

let attributes: [String: Any] = [
kSecValueData as String: data
]

let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
if status != errSecSuccess {
print("Keychain에서 토큰을 업데이트하는 데 실패했습니다. 에러 코드: \(status)")
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3
토큰 처리 수고하셨습니다!!! 깔끔하네요~!

}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드는 KeychainWrapper 클래스를 포함하는 파일의 코드 패치입니다. 이 클래스는 토큰을 Keychain에 저장하고 불러오며 삭제 및 업데이트하는 기능을 제공합니다.

여기 몇 가지 개선점과 버그 위험성이 있습니다:

  • 토큰을 저장할 때, 이미 해당 키에 대한 토큰이 존재하는지 확인하고 있습니다. 그러나없는 경우 저장 진행 중에 이미 동일한 키에 다른 스레드에서 저장하는 경우에도 업데이트가 발생할 수 있습니다.
  • loadToken 메서드에서 토큰을 불러올 때, 오류 발생 시 nil을 반환하고 에러메시지만 출력합니다. 일부 클라이언트 코드에서 이 에러를 처리해야 할 수 있으므로, nil 대신 에러를 던지는 것이 더 좋은 방법일 수 있습니다.
  • 인스턴스화될 필요가 없는 정적 유틸리티 함수들은 static으로 선언되어 있습니다. 그러나 클래스 자체를 인스턴스화하지 않아도 되므로 클래스 말고 구조체(struct)로 변경하여 더욱 명확하게 표현할 수 있습니다.
  • 에러 메시지 출력은 콘솔에 출력되도록 되어 있지만, 실제 애플리케이션의 콘솔에서 확인할 수 없습니다. 대신 로깅 라이브러리를 사용하거나 에러를 전달하고 호출하는 코드 쪽에서 오류를 처리할 수 있도록 수정하는 것이 좋습니다.

이러한 개선점과 주의사항을 고려하여 코드에서 버그나 위험성은 발견되지 않았습니다.

85 changes: 85 additions & 0 deletions DontBe-iOS/DontBe-iOS/Network/Foundation/NetworkService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// KakaoLoginService.swift
// DontBe-iOS
//
// Created by 변희주 on 1/14/24.
//

import Foundation

final class NetworkService: NetworkServiceType {
func donMakeRequest(type: HttpMethod,
baseURL: String,
accessToken: String,
body: Encodable,
pathVariables: [String: String]) -> URLRequest {
var urlComponents = URLComponents(string: baseURL)

// Path Variable 추가
for (key, value) in pathVariables {
let pathVariableItem = URLQueryItem(name: key, value: value)
urlComponents?.queryItems = [pathVariableItem]
}

// 기존의 URL이 존재하지 않으면 fatalError
guard let url = urlComponents?.url else {
fatalError("Failed to create URL")
}

var request = URLRequest(url: url)
request.httpMethod = type.method

let header = ["Content-Type": "application/json",
"Authorization": "Bearer \(accessToken)"]

header.forEach {
request.addValue($0.value, forHTTPHeaderField: $0.key)
}

// 리퀘스트 바디 설정 (구조체)
do {
let jsonData = try JSONEncoder().encode(body)
request.httpBody = jsonData
} catch {
print("Failed to encode request body: \(error)")
}

return request
}


func donNetwork<T: Decodable>(type: HttpMethod,
baseURL: String,
accessToken: String,
body: Encodable,
pathVariables: [String: String]) async throws -> T? {
do {
let request = self.donMakeRequest(type: type, baseURL: baseURL,
accessToken: accessToken, body: body,
pathVariables: pathVariables)
let (data, response) = try await URLSession.shared.data(for: request)
dump(request)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.responseError
}

switch httpResponse.statusCode {
case 200..<300:
let result = try JSONDecoder().decode(T.self, from: data)
return result
case 400:
throw NetworkError.badRequestError
case 401:
throw NetworkError.unautohorizedError
case 404:
throw NetworkError.notFoundError
case 500:
throw NetworkError.internalServerError
default:
throw NetworkError.unknownError
}
} catch {
throw error
}
}
}
Loading