Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,7 @@ iOSInjectionProject/
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings

## config ##
*.xcconfig

Comment on lines +102 to +104
Copy link

Choose a reason for hiding this comment

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

현재 빌드가 정상적으로 되지 않아요.
혹시 이 파일을 gitignore에 추가하신 이유가 있으실까요??
여러분들의 로컬에 있는 프로젝트를 삭제한 이후에 다시 클론을 받아서 빌드를 진행해보시면 좋을 것 같아요.
gitignore에 추가한 파일이 어떤파일인지 어떤 역할을 하는 파일인지 알아보시면 좋을 것 같아요.
그리고 이 문제를 어떻게 해결할 수 있을지도 확인해보세요!

Copy link
Author

@angryeon7 angryeon7 Apr 8, 2024

Choose a reason for hiding this comment

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

xcconfig란?

Xcode 프로젝트의 빌드 설정을 외부화하여 관리할 수 있게 해주는 파일입니다.

.xcconfig 확장자로 저장되며, 이를 사용하면 빌드 설정을 코드로 관리할 수 있어 복잡한 프로젝트에서 빌드 설정을 좀 더 쉽게 관리할 수 있습니다.

api key를 git에 노출시키지 않도록 하기 위해서 추가하였습니다.

Choose a reason for hiding this comment

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

의도는 이해했습니다.
그렇지만 git을 통해서 pull을 받았을때 정상 빌드가 되지 않고 xcconfig를 사용하지 않고 빌드하는 경우 api key가 없어 API요청에 실패하게 되어있습니다.
데이터의 보안에 관련해서는 어떻게 처리하면 좋을지 추후에 조금 더 알아보시고 이번에는 추가해주시면 좋을 것 같네요 ㅎㅎ

# End of https://www.toptal.com/developers/gitignore/api/swift
98 changes: 94 additions & 4 deletions ChatBot/ChatBot.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
3756D96839324F046B039CE8 /* Pods_ChatBot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86F2885AC45A38F47FDA4AD5 /* Pods_ChatBot.framework */; };
84149A5B2BB285B1003595D0 /* ResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84149A5A2BB285B1003595D0 /* ResponseDTO.swift */; };
84149A5D2BB2940B003595D0 /* RequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84149A5C2BB2940B003595D0 /* RequestDTO.swift */; };
95280FAF2BBD68C90071860A /* NetworkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FAE2BBD68C90071860A /* NetworkRouter.swift */; };
95280FB12BBD6C010071860A /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FB02BBD6C010071860A /* APIClient.swift */; };
95280FB32BBD6C440071860A /* APIRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FB22BBD6C440071860A /* APIRouter.swift */; };
95280FB52BBD6CA10071860A /* NetworkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FB42BBD6CA10071860A /* NetworkResult.swift */; };
95280FB72BBD6CF80071860A /* APIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FB62BBD6CF80071860A /* APIConstants.swift */; };
95280FBB2BBD70AE0071860A /* RequestDTO+DictionaryRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FBA2BBD70AE0071860A /* RequestDTO+DictionaryRepresentation.swift */; };
95280FBD2BBD70E50071860A /* MessageDTO+DictionaryRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FBC2BBD70E50071860A /* MessageDTO+DictionaryRepresentation.swift */; };
95280FBF2BBD71390071860A /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FBE2BBD71390071860A /* ChatService.swift */; };
95280FC12BBE0C880071860A /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95280FC02BBE0C880071860A /* AppConfig.swift */; };
95B4F31B2BB2C21E00AB4952 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 95B4F31A2BB2C21E00AB4952 /* .swiftlint.yml */; };
95B4F3272BB42ED300AB4952 /* MessageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B4F3262BB42ED300AB4952 /* MessageDTO.swift */; };
B4B3E2BD2B42D1BB00818B3C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4B3E2BC2B42D1BB00818B3C /* AppDelegate.swift */; };
Expand All @@ -23,7 +32,17 @@
548DCC0B40D1F4BC4766D293 /* Pods-ChatBot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChatBot.release.xcconfig"; path = "Target Support Files/Pods-ChatBot/Pods-ChatBot.release.xcconfig"; sourceTree = "<group>"; };
84149A5A2BB285B1003595D0 /* ResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseDTO.swift; sourceTree = "<group>"; };
84149A5C2BB2940B003595D0 /* RequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestDTO.swift; sourceTree = "<group>"; };
843E18412BBD214C003EEF6D /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Configuration.xcconfig; sourceTree = "<group>"; };
86F2885AC45A38F47FDA4AD5 /* Pods_ChatBot.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ChatBot.framework; sourceTree = BUILT_PRODUCTS_DIR; };
95280FAE2BBD68C90071860A /* NetworkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRouter.swift; sourceTree = "<group>"; };
95280FB02BBD6C010071860A /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
95280FB22BBD6C440071860A /* APIRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIRouter.swift; sourceTree = "<group>"; };
95280FB42BBD6CA10071860A /* NetworkResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkResult.swift; sourceTree = "<group>"; };
95280FB62BBD6CF80071860A /* APIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConstants.swift; sourceTree = "<group>"; };
95280FBA2BBD70AE0071860A /* RequestDTO+DictionaryRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RequestDTO+DictionaryRepresentation.swift"; sourceTree = "<group>"; };
95280FBC2BBD70E50071860A /* MessageDTO+DictionaryRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageDTO+DictionaryRepresentation.swift"; sourceTree = "<group>"; };
95280FBE2BBD71390071860A /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = "<group>"; };
95280FC02BBE0C880071860A /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
95B4F31A2BB2C21E00AB4952 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
95B4F3262BB42ED300AB4952 /* MessageDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDTO.swift; sourceTree = "<group>"; };
B4B3E2B92B42D1BB00818B3C /* ChatBot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatBot.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -56,6 +75,44 @@
name = Frameworks;
sourceTree = "<group>";
};
95280FAA2BBD63A00071860A /* Network */ = {
isa = PBXGroup;
children = (
95280FAE2BBD68C90071860A /* NetworkRouter.swift */,
95280FB02BBD6C010071860A /* APIClient.swift */,
95280FB22BBD6C440071860A /* APIRouter.swift */,
95280FB42BBD6CA10071860A /* NetworkResult.swift */,
95280FB62BBD6CF80071860A /* APIConstants.swift */,
);
path = Network;
sourceTree = "<group>";
};
95280FB82BBD70870071860A /* Util */ = {
isa = PBXGroup;
children = (
95280FB92BBD708E0071860A /* Extensions */,
95280FC02BBE0C880071860A /* AppConfig.swift */,
);
path = Util;
sourceTree = "<group>";
};
95280FB92BBD708E0071860A /* Extensions */ = {
isa = PBXGroup;
children = (
95280FBA2BBD70AE0071860A /* RequestDTO+DictionaryRepresentation.swift */,
95280FBC2BBD70E50071860A /* MessageDTO+DictionaryRepresentation.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
95280FC32BBE36590071860A /* Service */ = {
isa = PBXGroup;
children = (
95280FBE2BBD71390071860A /* ChatService.swift */,
);
path = Service;
sourceTree = "<group>";
};
95B4F3282BB43A8E00AB4952 /* App */ = {
isa = PBXGroup;
children = (
Expand All @@ -78,8 +135,9 @@
95B4F32A2BB43BDB00AB4952 /* Resources */ = {
isa = PBXGroup;
children = (
B4B3E2CA2B42D1BC00818B3C /* Info.plist */,
843E18412BBD214C003EEF6D /* Configuration.xcconfig */,
B4B3E2C52B42D1BC00818B3C /* Assets.xcassets */,
B4B3E2CA2B42D1BC00818B3C /* Info.plist */,
B4B3E2C72B42D1BC00818B3C /* LaunchScreen.storyboard */,
);
path = Resources;
Expand Down Expand Up @@ -126,8 +184,11 @@
children = (
95B4F3282BB43A8E00AB4952 /* App */,
95B4F3292BB43A9C00AB4952 /* Model */,
95280FAA2BBD63A00071860A /* Network */,
95280FC32BBE36590071860A /* Service */,
95B4F32B2BB4714200AB4952 /* Controller */,
95B4F32A2BB43BDB00AB4952 /* Resources */,
95280FB82BBD70870071860A /* Util */,
);
path = ChatBot;
sourceTree = "<group>";
Expand All @@ -139,11 +200,12 @@
isa = PBXNativeTarget;
buildConfigurationList = B4B3E2CD2B42D1BC00818B3C /* Build configuration list for PBXNativeTarget "ChatBot" */;
buildPhases = (
95B4F3192BB2C1A100AB4952 /* SwiftLint */,
CF48DBE895D16B153F73C922 /* [CP] Check Pods Manifest.lock */,
95B4F3192BB2C1A100AB4952 /* SwiftLint */,
B4B3E2B52B42D1BB00818B3C /* Sources */,
B4B3E2B62B42D1BB00818B3C /* Frameworks */,
B4B3E2B72B42D1BB00818B3C /* Resources */,
157BC9F75AEC0E5F7B580B83 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -201,6 +263,23 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
157BC9F75AEC0E5F7B580B83 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ChatBot/Pods-ChatBot-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ChatBot/Pods-ChatBot-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ChatBot/Pods-ChatBot-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
95B4F3192BB2C1A100AB4952 /* SwiftLint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
Expand Down Expand Up @@ -249,12 +328,21 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
95280FBB2BBD70AE0071860A /* RequestDTO+DictionaryRepresentation.swift in Sources */,
B4B3E2C12B42D1BB00818B3C /* ViewController.swift in Sources */,
95280FB72BBD6CF80071860A /* APIConstants.swift in Sources */,
95280FB52BBD6CA10071860A /* NetworkResult.swift in Sources */,
95280FB32BBD6C440071860A /* APIRouter.swift in Sources */,
B4B3E2BD2B42D1BB00818B3C /* AppDelegate.swift in Sources */,
95280FC12BBE0C880071860A /* AppConfig.swift in Sources */,
84149A5B2BB285B1003595D0 /* ResponseDTO.swift in Sources */,
95280FB12BBD6C010071860A /* APIClient.swift in Sources */,
95280FAF2BBD68C90071860A /* NetworkRouter.swift in Sources */,
95280FBF2BBD71390071860A /* ChatService.swift in Sources */,
84149A5D2BB2940B003595D0 /* RequestDTO.swift in Sources */,
95B4F3272BB42ED300AB4952 /* MessageDTO.swift in Sources */,
B4B3E2BF2B42D1BB00818B3C /* SceneDelegate.swift in Sources */,
95280FBD2BBD70E50071860A /* MessageDTO+DictionaryRepresentation.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -274,6 +362,7 @@
/* Begin XCBuildConfiguration section */
B4B3E2CB2B42D1BC00818B3C /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 843E18412BBD214C003EEF6D /* Configuration.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
Expand Down Expand Up @@ -324,7 +413,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
Copy link

Choose a reason for hiding this comment

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

어떤 기준으로 iOS14를 선택하게 되셨는지 궁금해요~
그리고 현재 프로젝트와 타켓의 최소지원 버전이 다르게 세팅되어있어요!

Copy link

@yoruck2 yoruck2 Apr 9, 2024

Choose a reason for hiding this comment

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

음.. 이상하게도 커밋에는 있지만 최소지원 버전을 14.0 로 바꾼 기억이 없네요.. 따라서 이유는 없습니다..! 😅
저번의 의견을 참고해서 카카오톡의 현재 최소지원 버전을 기준으로 프로젝트와 타겟의 최소지원 버전을 15.0 으로 통일하기로 했습니다.

LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
Expand All @@ -337,6 +426,7 @@
};
B4B3E2CC2B42D1BC00818B3C /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 843E18412BBD214C003EEF6D /* Configuration.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
Expand Down Expand Up @@ -381,7 +471,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
Expand Down
14 changes: 14 additions & 0 deletions ChatBot/ChatBot/Controller/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@
import UIKit

class ViewController: UIViewController {
let chatService = ChatService()

override func viewDidLoad() {
super.viewDidLoad()
sendTestMessage("안녕 만나서 반가워")
}

func sendTestMessage(_ message: String) {
chatService.sendChatRequest(message: message) { result in
switch result {
case .success(let response):
print("Response: \(response)")
case .failure(let error):
print("Error: \(error)")
}
}
}
}
39 changes: 39 additions & 0 deletions ChatBot/ChatBot/Network/APIClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// APIClient.swift
// ChatBot
//
// Created by nayeon on 4/3/24.
//

import Alamofire

final class APIClient {
static let shared = APIClient()
Copy link

Choose a reason for hiding this comment

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

현재 APIClient의 역할이 Network요청을 보내는 것 이외에는 하고 있지 않아요.
APIClient를 싱글톤으로 구현하신 이유가 있으실까요??

Copy link
Author

Choose a reason for hiding this comment

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

APIClient를 싱글톤으로 구현한 이유 중 하나는 애플리케이션의 다양한 부분에서 동일한 APIClient 인스턴스에 접근할 수 있으므로 코드 중복을 줄이고 일관된 네트워킹 인터페이스를 유지할 수 있습니다. 또한, 싱글톤을 사용하여 코드를 간소화하고 객체 생성 및 관리에 대한 부담을 줄이기위해서 사용하였습니다.

Choose a reason for hiding this comment

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

여러분들의 의도라면 enum에 static func으로 구현해도 동일하게 동작합니다.
싱글톤 패턴과 static에 대해서 조금 더 구체적으로 고민해보시는게 좋을 것 같아요.


func request<T: Decodable>(_ object: T.Type,
router: URLRequestConvertible,
completion: @escaping (NetworkResult<T>) -> Void) {
AF.request(router)
.validate(statusCode: 200..<500)
.responseDecodable(of: object) { response in
switch response.result {
case .success(let decodedData):
completion(.success(decodedData))
case .failure(_):
Copy link

Choose a reason for hiding this comment

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

현재 에러가 발생했을때 코드에 따라 여러분들이 만든 에러를 전달하고 있네요.
이 방법도 좋지만 API에서 에러가 발생했을때 전달하는 데이터를 사용해야하는 경우도 있어요.
현재 어떻게 전달되고 있고 어떻게 우리가 구현한 에러와 같이 사용해볼 수 있을지 고민해보시면 좋을 것 같아요.
사용자에게 구체적으로 어떤 에러인지 알려줄 수도 있고, 개발하는 입장에서도 왜 에러가 발생했는지 명확하게 알 수 있어 빠르게 문제 파악을 할 수 있을 거에요 ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

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

response.data를 통해 message를 출력할 수 있도록 수정하였습니다.

if let statusCode = response.response?.statusCode {
switch statusCode {
case 400...499:
completion(.failure(.pathError))
Copy link

Choose a reason for hiding this comment

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

현재 앱을 실행하면 Error: pathError가 로그에 찍히고 있어요.
왜 에러가 발생하는지 확인하고 해결해주세요!
그리고 400대 에러코드가 언제 발생할 수 있는지도 다시 고민해보시면 좋을 것 같아요.
경로가 잘못된 상황 이외에도 발생하는 경우가 있는지 확인해보세요!

Copy link
Author

Choose a reason for hiding this comment

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

아마 이전 API키로 사용하셔서 pathError가 발생하는것같아요 header에 api 키를 정확하게 입력하게된다면 pathError가 발생하지않을것같습니다.
400대 에러코드 종류

  1. 400 Bad Request: 클라이언트 요청이 서버에서 이해할 수 없는 형식으로 되어 있거나 잘못된 구문을 포함하고 있음
  2. 401 Unauthorized: 클라이언트가 인증되지 않았거나, 유효한 인증 정보가 부족하여 요청이 거부되었음을 의미하는 상태값
  3. 403 Forbidden: 서버가 해당 요청을 이해했지만, 권한이 없어 요청이 거부되었음을 의미하는 상태
  4. 404 Not Found: 클라이언트가 요청한 리소스를 서버에서 찾을 수 없음을 나타냅니다. 즉, 요청한 URL이 유효하지 않거나 해당 리소스가 서버에 존재하지 않는 경우입니다.

Choose a reason for hiding this comment

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

제가 말씀 드린 부분은 400대 에러가 발생하는 경우가 path가 잘못된 경우 말고도 여러분들이 말씀하신 api key가 잘못되는 경우도 있어요.
그리고 여러분들이 찾아주신 케이스 중에 권한이 없거나, 데이터를 잘못 전달했을때 등에서도 400대 에러가 발생할 수 있어요.
그런데 현재 여러분들이 구현하신 코드에서는 400대에서 발생하는 모든 에러가 pathError로 보이게 되어 명확한 원인을 알 수 없는 것 같아 코멘트를 남겼습니다.
지금은 message를 로그로 확인할 수 있도록 수정해주셔서 네이밍 정도만 조금 더 포괄적으로 수정되면 좋을 것 같아요.

p.s. 그리고 현재 NetworkError의 case중에 requestError는 사용하고 있지 않은 것 같아요!

case 500...599:
completion(.failure(.serverError))
default:
completion(.failure(.networkFail))
}
} else {
completion(.failure(.networkFail))

}
}
}
}
}
21 changes: 21 additions & 0 deletions ChatBot/ChatBot/Network/APIConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// APIConstants.swift
// ChatBot
//
// Created by nayeon on 4/3/24.
//

import Foundation

struct APIConstants {
static let baseURL = "https://api.openai.com"
}

enum HTTPHeaderField: String {
case authentication = "Authorization"
case contentType = "Content-Type"
}

enum ContentType: String {
case json = "application/json"
}
Comment on lines +14 to +21
Copy link

Choose a reason for hiding this comment

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

HTTPHeaderField, ContentType도 사용하는 방법을 보니 API요청시 필요한 기본 세팅 문자열을 다루고 있네요.
APIConstants에 포함되어도 될 것 같은데 별도로 분리해서 선언한 이유가 궁금합니다.
그리고 여러분들의 class, struct, enum 선택 기준도 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

API 요청에서 반복적으로 사용되는 HTTP 헤더 필드와 콘텐츠 타입을 정의함으로써 모듈화와 코드의 가독성을 높였습니다. 하드코딩하는 대신 의미 있는 이름을 사용할 수 있고, API 요청에 필요한 기본 세팅이 변경될 경우, 해당 세팅을 열거형의 값만 수정하여 유지보수를 쉽게 할 수 있습니다.

Copy link

Choose a reason for hiding this comment

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

애플에서는 기본적으로 특별한 이유가 없다면 struct를 사용하는것을 권장하는 것으로 알고 있습니다.
그 이유라고 하면 다음과 같은 기준이 있을 것입니다.

  1. 해당 객체가 값으로 쓰일 것인지 래퍼런스 타입으로 쓰일것인지 (불변성과 가변성)
  2. 대입 되는 경우가 생성 되는 경우보다 많을 시 class
  3. 하지만 비교적 많은 데이터, 메모리 용량이 큰 타입일 경우 class 유리할 때도 있다

열거형도 값 타입으로서 struct를 사용 할 때와 같은 기준으로 사용하지만 연관된 값들을 그룹화하여 표현하는 데 유리한 점이 있기 때문에 선택지를 제한하거나 관련된 값들을 묶어서 사용할 때 선택합니다.

Choose a reason for hiding this comment

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

먼저 공통으로 사용되는 값들을 이렇게 분리해서 별도로 선언하여 재사용할 수 있게 구현하신 것은 좋은 방법이라고 생각합니다.
다만 제가 코멘트를 남겨놓았던 이유가 몇가지 있는데요.

  1. APIConstants, HTTPHeaderField, ContentType 사용 방법이 동일한 것 같은데 APIConstants는 struct에 static으로 프로퍼티를 선언해서 사용하고 있고 나머지 두개는 enum으로 구현해서 rawValue를 사용하고 있어 같은 목적과 사용 방법이 같은데 구현이 달라서 코멘트 남겼습니다.
  2. HTTPHeaderField, ContentType에 있는 값들을 포괄적으로 보면 APIConstants의 내용에도 포함될 수 있는 것 같아 별도로 분리하신 이유를 여쭤봤습니다.
  3. baseURL를 static으로 선언해두었는데 APIConstants 인스턴스를 생성하는 경우가 있을지?도 고민해보시면 좋을것 같아 질문드렸습니다.

64 changes: 64 additions & 0 deletions ChatBot/ChatBot/Network/APIRouter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// APIRouter.swift
// ChatBot
//
// Created by nayeon on 4/3/24.
//

import Alamofire

enum APIRouter: NetworkRouter {
Copy link

Choose a reason for hiding this comment

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

APIRouter가 반드시 가져야하는 프로퍼티를 정의한다는 측면에서는 NetworkRouter 프로토콜을 구현하고 채택하는 것이 좋아 보입니다.
다만 해당 기능 이외에는 프로토콜을 채택할 이유가 조금 부족해보여요.
현재는 APIRouter가 NetworkRouter 프로토콜을 채택하지 않더라고 기능에 아무 문제가 없어요
프로토콜을 타입으로 활용하거나 기본구현을 통해 공통 로직을 분리하는 등의 방법으로 조금 더 프로토콜을 사용하는 이점들을 가져가는 방향으로 고민을 해보시면 좋을 것 같습니다.
다른 사람들은 프로토콜의 어떤 장점을 어떻게 활용하고 있는지도 찾아보면 도움이 될 것 같네요!

Copy link
Author

Choose a reason for hiding this comment

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

프로토콜의 기본 구현을 통해 공통 로직을 분리했습니다!!

case chatCompletion(requestDTO: RequestDTO)

var baseURL: String {
return APIConstants.baseURL
}

var path: String {
switch self {
case .chatCompletion:
return "/v1/chat/completions"
}
}

var method: HTTPMethod {
switch self {
case .chatCompletion:
return .post
}
}

var headers: [String: String] {
return [
HTTPHeaderField.contentType.rawValue: ContentType.json.rawValue,
HTTPHeaderField.authentication.rawValue: "Bearer \(AppConfig.openAIAPIKey)"
]
}

var parameters: [String: Any]? {
switch self {
case .chatCompletion(let requestDTO):
return requestDTO.dictionaryRepresentation()
}
}

var encoding: ParameterEncoding {
switch self {
case .chatCompletion:
return JSONEncoding.default
}
}
}

extension APIRouter: URLRequestConvertible {
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL()
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = method.rawValue
urlRequest.allHTTPHeaderFields = headers
if let parameters = parameters {
urlRequest = try encoding.encode(urlRequest, with: parameters)
}
return urlRequest
}
}
19 changes: 19 additions & 0 deletions ChatBot/ChatBot/Network/NetworkResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// NetworkResult.swift
// ChatBot
//
// Created by nayeon on 4/3/24.
//

enum NetworkResult<T> {
case success(T)
case failure(NetworkError)
}

enum NetworkError: Error {
case requestError
case pathError
case parsingError
case serverError
case networkFail
}
17 changes: 17 additions & 0 deletions ChatBot/ChatBot/Network/NetworkRouter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// NetworkRouter.swift
// ChatBot
//
// Created by nayeon on 4/3/24.
//

import Alamofire

protocol NetworkRouter {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
var headers: [String: String] { get }
var parameters: [String: Any]? { get }
var encoding: ParameterEncoding { get }
}
2 changes: 2 additions & 0 deletions ChatBot/ChatBot/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>$(API_KEY)</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand Down
Loading