Skip to content
Open
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
401 changes: 401 additions & 0 deletions movie/movie.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions movie/movie/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// AppDelegate.swift
// movie
//
// Created by sun on 12/16/25.
//

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {



func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}


}

11 changes: 11 additions & 0 deletions movie/movie/Assets.xcassets/AccentColor.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
35 changes: 35 additions & 0 deletions movie/movie/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions movie/movie/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
25 changes: 25 additions & 0 deletions movie/movie/Base.lproj/LaunchScreen.storyboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
27 changes: 27 additions & 0 deletions movie/movie/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-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>KOBIS_API_KEY</key>
<string>$(KOBIS_API_KEY)</string>
<key>BASE_URL</key>
<string>$(BASE_URL)</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
14 changes: 14 additions & 0 deletions movie/movie/Network/Base/BaseResponseBody.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// BaseResponseBody.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

struct BaseResponseBody<T: ResponseModelType>: ResponseModelType {
let code: Int
let message: String
let data: T
}
87 changes: 87 additions & 0 deletions movie/movie/Network/Base/BaseService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// BaseService.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

class BaseService<Target: TargetType> {
let provider = NetworkProvider<Target>()

func request<T: ResponseModelType>(with target: Target) async throws -> BaseResponseBody<T> {

let urlRequest = try provider.makeRequest(target)

NetworkLogger.logRequest(urlRequest, target: target)

do {
let (data, response) = try await URLSession.shared.data(for: urlRequest)

NetworkLogger.logResponse(data: data, response: response, target: target)

guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.responseError
}

let statusCode = httpResponse.statusCode

guard 200..<300 ~= statusCode else {
if statusCode == 404 {
throw NetworkError.notFoundError
} else if statusCode == 500 {
throw NetworkError.internalServerError
} else {
throw NetworkError.responseError
}
}

do {
return try JSONDecoder().decode(BaseResponseBody<T>.self, from: data)
} catch {
throw NetworkError.responseDecodingError
}

} catch {
NetworkLogger.logError(error)
throw error
}
}
}

extension BaseService {

func requestRaw<T: Decodable>(with target: Target) async throws -> T {
let urlRequest = try provider.makeRequest(target)

NetworkLogger.logRequest(urlRequest, target: target)

do {
let (data, response) = try await URLSession.shared.data(for: urlRequest)

NetworkLogger.logResponse(data: data, response: response, target: target)

guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.responseError
}

let statusCode = httpResponse.statusCode
guard 200..<300 ~= statusCode else {
if statusCode == 404 { throw NetworkError.notFoundError }
if statusCode == 500 { throw NetworkError.internalServerError }
throw NetworkError.responseError
}

do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw NetworkError.responseDecodingError
}

} catch {
NetworkLogger.logError(error)
throw error
}
}
}
16 changes: 16 additions & 0 deletions movie/movie/Network/Base/Environment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Environment.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

enum Environment {
static let baseURL: String =
Bundle.main.object(forInfoDictionaryKey: "BASE_URL") as! String

static let kobisAPIKey: String =
Bundle.main.object(forInfoDictionaryKey: "KOBIS_API_KEY") as! String
}
14 changes: 14 additions & 0 deletions movie/movie/Network/Base/ModelType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// ModelType.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

/// 요청 DTO용
protocol RequestModelType: Encodable {}

/// 응답 DTO용
protocol ResponseModelType: Decodable {}
37 changes: 37 additions & 0 deletions movie/movie/Network/Base/NetworkError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// NetworkError.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

enum NetworkError: Error, CustomStringConvertible {
case invalidURLComponents
case invalidURLString
case requestEncodingError
case responseDecodingError
case responseError
case notFoundError
case internalServerError

var description: String {
switch self {
case .invalidURLComponents:
return "URL 구성을 만들 수 없습니다."
case .invalidURLString:
return "잘못된 URL입니다."
case .requestEncodingError:
return "요청 인코딩에 실패했습니다."
case .responseDecodingError:
return "응답 디코딩에 실패했습니다."
case .responseError:
return "서버로부터 에러 응답을 받았습니다."
case .notFoundError:
return "요청한 리소스를 찾을 수 없습니다. (404)"
case .internalServerError:
return "서버 내부 오류입니다. (500)"
}
}
}
69 changes: 69 additions & 0 deletions movie/movie/Network/Base/NetworkLogger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// NetworkLogger.swift
// movie
//
// Created by sun on 12/17/25.
//

import Foundation

enum NetworkLogger {

static func logRequest(_ request: URLRequest, target: TargetType) {
guard let url = request.url?.absoluteString else {
print("--> 유효하지 않은 요청")
return
}

let method = request.httpMethod ?? "메소드값이 nil입니다."
var log = """
----------------------------------------------------
1️⃣[\(method)] \(url)
----------------------------------------------------
2️⃣API: \(target)
"""

if let headers = request.allHTTPHeaderFields, !headers.isEmpty {
log.append("\nheader: \(headers)")
}

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

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

static func logResponse(data: Data, response: URLResponse, target: TargetType) {
guard let httpResponse = response as? HTTPURLResponse else { return }
let url = httpResponse.url?.absoluteString ?? "nil"
let statusCode = httpResponse.statusCode

var log = """
------------------- Response가 도착했습니다. -------------------
3️⃣[\(statusCode)] \(url)
API: \(target)
Status Code: [\(statusCode)]
URL: \(url)
response:
"""

if let body = String(data: data, encoding: .utf8) {
log.append("\n4️⃣\(body)")
}

log.append("\n------------------- END HTTP -------------------")
print(log)
}

static func logError(_ error: Error) {
let log = """
❌ 네트워크 오류 발생
<-- \(error.localizedDescription)
<-- END HTTP
"""
print(log)
}
}
Loading