├── images
├── screenshot1.png
├── screenshot2.png
└── architecture.png
├── MyToDo
└── MyToDo
│ ├── MyToDo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── SceneDelegate.swift
│ ├── TitleInputViewController.swift
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── ViewModel.swift
│ └── ViewController.swift
│ ├── MyToDo.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
│ ├── ToDoService
│ ├── ToDo.swift
│ ├── Repository.swift
│ ├── MemoryRepository.swift
│ ├── ToDoService.swift
│ ├── UserDefaultsRepository.swift
│ └── NetworkRepository.swift
│ └── MyToDoTests
│ ├── AsyncUtil.swift
│ ├── Info.plist
│ └── MyToDoTests.swift
├── README.md
└── .gitignore
/images/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamchiwon/whatisarchitecture/HEAD/images/screenshot1.png
--------------------------------------------------------------------------------
/images/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamchiwon/whatisarchitecture/HEAD/images/screenshot2.png
--------------------------------------------------------------------------------
/images/architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamchiwon/whatisarchitecture/HEAD/images/architecture.png
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/ToDo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToDo.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ToDo: Identifiable, Codable {
12 | let id: Int
13 | let title: String
14 | let completed: Bool
15 | let createdAt: Date
16 | let updatedAt: Date?
17 | }
18 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/Repository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Repository.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol Repository {
12 | func create(item: ToDo, completed: @escaping (Bool) -> Void)
13 | func read(completed: @escaping ([ToDo]) -> Void)
14 | func update(item: ToDo, completed: @escaping (Bool) -> Void)
15 | func delete(item: ToDo, completed: @escaping (Bool) -> Void)
16 | }
17 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDoTests/AsyncUtil.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncUtil.swift
3 | // MyToDoTests
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCTestCase {
12 | func async(timeout: TimeInterval = 10,
13 | title: String = #function,
14 | job: @escaping (@escaping () -> Void) -> Void) {
15 | let expectation = self.expectation(description: title)
16 |
17 | job {
18 | expectation.fulfill()
19 | }
20 |
21 | waitForExpectations(timeout: 10)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 아키텍쳐가 뭔데
2 |
3 | #### 예제 코드 가이드
4 |
5 | - [여기](https://github1s.com/iamchiwon/whatisarchitecture)에서 코드를 편하게 보세요.
6 | - 커밋 단위로 개발이 진행되는 과정을 살펴보세요.
7 | - 의도적으로 외부 라이브러리를 사용하지 않았습니다.
8 | - Service 레이어 하위 레이어들에서 UIKit이나 UIApplication에 의존하지 않는 부분을 주목해 보세요.
9 | - 프로토콜과 구현이 분리되어 각각 어디에 코드가 있는지 살펴보세요.
10 |
11 | ###### 20200805 fastcampus 특강
12 |
13 | 
14 |
15 | ## Agenda
16 |
17 | 1. 아키텍쳐가 뭐냐?
18 | - 소문: 어디서 들어본 얘기
19 | 2. 잘 구성된 프로그램이란?
20 | - 좋은 프로그램의 기준
21 | 3. 프로그램 조망하기
22 | - 컴퓨터의 구성 및 동작
23 | - 프로그램의 구성 및 동작
24 | 4. 좋은 프로그램을 만드는 방법
25 | - 이론적 원칙
26 | - 모범사례
27 | 5. 연습
28 | - MyToDo 만들기
29 |  
30 | - 요구사항 > 설계 (책임과 역할) > 구현
31 | - TDD > MVC > MVVM
32 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 | var window: UIWindow?
13 |
14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
15 | guard let windowScene = (scene as? UIWindowScene) else { return }
16 |
17 | let reposity: Repository = NetworkRepository()
18 | let service: ToDoService = ToDoServiceImpl(repository: reposity)
19 |
20 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
21 | let navi = storyboard.instantiateInitialViewController() as? UINavigationController
22 | let viewController = navi?.viewControllers.first as? ViewController
23 |
24 | let viewModel: ViewModel = ViewModelImpl(service: service, observer: viewController)
25 | viewController?.viewModel = viewModel
26 |
27 | window = UIWindow(windowScene: windowScene)
28 | window?.rootViewController = navi
29 | window?.makeKeyAndVisible()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/TitleInputViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TitleInputViewController.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TitleInputViewController: UIViewController {
12 | var onTitleInput: ((String) -> Void)?
13 | var updateItem: ToDo?
14 |
15 | @IBOutlet var titleTextField: UITextField!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | view.addGestureRecognizer(UITapGestureRecognizer(target: self,
21 | action: #selector(onBackgroundTouched)))
22 | if let item = updateItem {
23 | titleTextField.text = item.title
24 | }
25 | }
26 |
27 | @objc func onBackgroundTouched() {
28 | view.endEditing(true)
29 | }
30 |
31 | @IBAction func onCancel() {
32 | dismiss(animated: true, completion: nil)
33 | }
34 |
35 | @IBAction func onDone() {
36 | guard let text = titleTextField.text, !text.isEmpty,
37 | let callback = onTitleInput else {
38 | onCancel()
39 | return
40 | }
41 |
42 | callback(text)
43 | dismiss(animated: true, completion: nil)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | if #available(iOS 13, *) {
17 | // SceneDeledate
18 |
19 | } else {
20 | let reposity: Repository = NetworkRepository()
21 | let service: ToDoService = ToDoServiceImpl(repository: reposity)
22 |
23 | let storyboard = UIStoryboard(name: "Main", bundle: nil)
24 | let navi = storyboard.instantiateInitialViewController() as? UINavigationController
25 | let viewController = navi?.viewControllers.first as? ViewController
26 |
27 | let viewModel: ViewModel = ViewModelImpl(service: service, observer: viewController)
28 | viewController?.viewModel = viewModel
29 |
30 | window = UIWindow(frame: UIScreen.main.bounds)
31 | window?.rootViewController = navi
32 | window?.makeKeyAndVisible()
33 | }
34 |
35 | return true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/MemoryRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MemoryRepository.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class MemoryRepository: Repository {
12 | var todos: [Int: ToDo] = [:]
13 |
14 | func create(item: ToDo, completed: @escaping (Bool) -> Void) {
15 | let todo = ToDo(id: todos.count,
16 | title: item.title,
17 | completed: item.completed,
18 | createdAt: item.createdAt,
19 | updatedAt: item.updatedAt)
20 |
21 | guard todos[todo.id] == nil else {
22 | completed(false)
23 | return
24 | }
25 |
26 | todos[todo.id] = todo
27 | completed(true)
28 | }
29 |
30 | func read(completed: @escaping ([ToDo]) -> Void) {
31 | completed(todos.compactMap { $1 })
32 | }
33 |
34 | func update(item: ToDo, completed: @escaping (Bool) -> Void) {
35 | guard todos[item.id] != nil else {
36 | completed(false)
37 | return
38 | }
39 |
40 | todos[item.id] = item
41 | completed(true)
42 | }
43 |
44 | func delete(item: ToDo, completed: @escaping (Bool) -> Void) {
45 | guard todos[item.id] != nil else {
46 | completed(false)
47 | return
48 | }
49 |
50 | todos.removeValue(forKey: item.id)
51 | completed(true)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModel.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ViewModelObserver {
12 | func updated()
13 | }
14 |
15 | protocol ViewModel {
16 | func didLoaded()
17 | func count() -> Int
18 | func todo(at index: Int) -> ToDo
19 |
20 | func create(title: String)
21 | func toggle(todo: ToDo)
22 | func update(title: String, todo: ToDo)
23 | func delete(todo: ToDo)
24 | }
25 |
26 | class ViewModelImpl: ViewModel {
27 | private var items: [ToDo] = []
28 |
29 | private var service: ToDoService
30 | private var observer: ViewModelObserver?
31 |
32 | init(service: ToDoService, observer: ViewModelObserver?) {
33 | self.service = service
34 | self.observer = observer
35 | }
36 |
37 | func didLoaded() {
38 | updateList()
39 | }
40 |
41 | private func updateList() {
42 | service.list { [weak self] items in
43 | self?.items = items
44 | self?.observer?.updated()
45 | }
46 | }
47 |
48 | func count() -> Int {
49 | return items.count
50 | }
51 |
52 | func todo(at index: Int) -> ToDo {
53 | return items[index]
54 | }
55 |
56 | func create(title: String) {
57 | service.create(title: title) { [weak self] success in
58 | guard success else { return }
59 | self?.updateList()
60 | }
61 | }
62 |
63 | func toggle(todo: ToDo) {
64 | service.toggleCompleted(item: todo) { [weak self] success in
65 | guard success else { return }
66 | self?.updateList()
67 | }
68 | }
69 |
70 | func update(title: String, todo: ToDo) {
71 | service.update(title: title, with: todo) { [weak self] success in
72 | guard success else { return }
73 | self?.updateList()
74 | }
75 | }
76 |
77 | func delete(todo: ToDo) {
78 | service.delete(item: todo) { [weak self] success in
79 | guard success else { return }
80 | self?.updateList()
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/ToDoService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToDoService.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ToDoService {
12 | func create(title: String,
13 | completed: @escaping (Bool) -> Void)
14 |
15 | func list(completed: @escaping ([ToDo]) -> Void)
16 |
17 | func update(title: String,
18 | with item: ToDo,
19 | completed: @escaping (Bool) -> Void)
20 |
21 | func toggleCompleted(item: ToDo,
22 | completed: @escaping (Bool) -> Void)
23 |
24 | func delete(item: ToDo,
25 | completed: @escaping (Bool) -> Void)
26 | }
27 |
28 | class ToDoServiceImpl: ToDoService {
29 | var repository: Repository!
30 |
31 | init(repository: Repository) {
32 | self.repository = repository
33 | }
34 |
35 | func create(title: String, completed: @escaping (Bool) -> Void) {
36 | let todo = ToDo(id: 0,
37 | title: title,
38 | completed: false,
39 | createdAt: Date(),
40 | updatedAt: nil)
41 | repository.create(item: todo, completed: completed)
42 | }
43 |
44 | func list(completed: @escaping ([ToDo]) -> Void) {
45 | repository.read(completed: completed)
46 | }
47 |
48 | func update(title: String, with item: ToDo, completed: @escaping (Bool) -> Void) {
49 | let todo = ToDo(id: item.id,
50 | title: title,
51 | completed: item.completed,
52 | createdAt: item.createdAt,
53 | updatedAt: Date())
54 | repository.update(item: todo, completed: completed)
55 | }
56 |
57 | func toggleCompleted(item: ToDo, completed: @escaping (Bool) -> Void) {
58 | let todo = ToDo(id: item.id,
59 | title: item.title,
60 | completed: !item.completed,
61 | createdAt: item.createdAt,
62 | updatedAt: Date())
63 | repository.update(item: todo, completed: completed)
64 | }
65 |
66 | func delete(item: ToDo, completed: @escaping (Bool) -> Void) {
67 | repository.delete(item: item, completed: completed)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/UserDefaultsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultsRepository.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/26.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | fileprivate let TODO_SAVE_KEY = "TODOS"
12 |
13 | class UserDefaultsRepository: Repository {
14 | var todos: [Int: ToDo] = [:]
15 |
16 | private func save(completed: @escaping (Bool) -> Void) {
17 | DispatchQueue.global().async {
18 | let jsonEncoder = JSONEncoder()
19 | if let data = try? jsonEncoder.encode(self.todos) {
20 | UserDefaults.standard.set(data, forKey: TODO_SAVE_KEY)
21 | }
22 |
23 | DispatchQueue.main.async {
24 | completed(true)
25 | }
26 | }
27 | }
28 |
29 | private func load(completed: @escaping ([Int: ToDo]) -> Void) {
30 | DispatchQueue.global().async {
31 | let jsonDecoder = JSONDecoder()
32 | guard let data = UserDefaults.standard.data(forKey: TODO_SAVE_KEY),
33 | let items = try? jsonDecoder.decode([Int: ToDo].self, from: data) else {
34 | completed([:])
35 | return
36 | }
37 |
38 | DispatchQueue.main.async {
39 | completed(items)
40 | }
41 | }
42 | }
43 |
44 | func create(item: ToDo, completed: @escaping (Bool) -> Void) {
45 | let todo = ToDo(id: todos.count,
46 | title: item.title,
47 | completed: item.completed,
48 | createdAt: item.createdAt,
49 | updatedAt: item.updatedAt)
50 |
51 | guard todos[todo.id] == nil else {
52 | completed(false)
53 | return
54 | }
55 |
56 | todos[todo.id] = todo
57 | save(completed: completed)
58 | }
59 |
60 | func read(completed: @escaping ([ToDo]) -> Void) {
61 | load { items in
62 | self.todos = items
63 | completed(items.compactMap { $1 })
64 | }
65 | }
66 |
67 | func update(item: ToDo, completed: @escaping (Bool) -> Void) {
68 | guard todos[item.id] != nil else {
69 | completed(false)
70 | return
71 | }
72 |
73 | todos[item.id] = item
74 | save(completed: completed)
75 | }
76 |
77 | func delete(item: ToDo, completed: @escaping (Bool) -> Void) {
78 | guard todos[item.id] != nil else {
79 | completed(false)
80 | return
81 | }
82 |
83 | todos.removeValue(forKey: item.id)
84 | save(completed: completed)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ToDoCell: UITableViewCell {
12 | var onToggleCompleted: (() -> Void)?
13 | var onDelete: (() -> Void)?
14 |
15 | @IBOutlet var completebutton: UIButton!
16 | @IBOutlet var titleLabel: UILabel!
17 | @IBOutlet var dateLabel: UILabel!
18 | @IBOutlet var deleteButton: UIButton!
19 |
20 | @IBAction func onCompleteSelected(sender: UIButton) {
21 | onToggleCompleted?()
22 | }
23 |
24 | @IBAction func onDeleteButton(sender: UIButton) {
25 | onDelete?()
26 | }
27 | }
28 |
29 | class ViewController: UITableViewController {
30 | var viewModel: ViewModel!
31 |
32 | override func viewDidLoad() {
33 | guard viewModel != nil else { fatalError("viewModel must not be nil") }
34 | super.viewDidLoad()
35 | }
36 |
37 | override func viewDidAppear(_ animated: Bool) {
38 | super.viewDidAppear(animated)
39 | viewModel.didLoaded()
40 | }
41 |
42 | @IBAction func onAddToDo(sender: Any) {
43 | let vc = storyboard?.instantiateViewController(identifier: "TitleInputViewController") as! TitleInputViewController
44 | if let updateItem = sender as? ToDo {
45 | vc.updateItem = updateItem
46 | vc.onTitleInput = { [weak self] in self?.viewModel.update(title: $0, todo: updateItem) }
47 | } else {
48 | vc.onTitleInput = { [weak self] in self?.viewModel.create(title: $0) }
49 | }
50 |
51 | present(vc, animated: true, completion: nil)
52 | }
53 | }
54 |
55 | extension ViewController: ViewModelObserver {
56 | func updated() {
57 | tableView.reloadData()
58 | }
59 | }
60 |
61 | // MARK: - UITableViewDataSource
62 |
63 | extension ViewController {
64 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
65 | return viewModel.count()
66 | }
67 |
68 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
69 | let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoCell") as! ToDoCell
70 |
71 | let todo = viewModel.todo(at: indexPath.row)
72 | cell.titleLabel.text = todo.title
73 | cell.dateLabel.text = todo.dateText()
74 | cell.completebutton.isSelected = todo.completed
75 | cell.onToggleCompleted = { [weak self] in
76 | self?.viewModel.toggle(todo: todo)
77 | }
78 | cell.onDelete = { [weak self] in
79 | self?.viewModel.delete(todo: todo)
80 | }
81 |
82 | return cell
83 | }
84 |
85 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
86 | tableView.deselectRow(at: indexPath, animated: true)
87 | let todo = viewModel.todo(at: indexPath.row)
88 | onAddToDo(sender: todo)
89 | }
90 | }
91 |
92 | // MARK: - ToDo extension
93 |
94 | extension ToDo {
95 | func dateText() -> String {
96 | let displayDate = updatedAt ?? createdAt
97 |
98 | let formatter = DateFormatter()
99 | formatter.dateFormat = "yyyy년 MM월 dd일 HH시 mm분"
100 |
101 | return formatter.string(from: displayDate)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
2 |
3 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,swift,xcode
4 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,macos,swift,xcode
5 |
6 | ### macOS ###
7 | # General
8 | .DS_Store
9 | .AppleDouble
10 | .LSOverride
11 |
12 | # Icon must end with two \r
13 | Icon
14 |
15 | # Thumbnails
16 | ._*
17 |
18 | # Files that might appear in the root of a volume
19 | .DocumentRevisions-V100
20 | .fseventsd
21 | .Spotlight-V100
22 | .TemporaryItems
23 | .Trashes
24 | .VolumeIcon.icns
25 | .com.apple.timemachine.donotpresent
26 |
27 | # Directories potentially created on remote AFP share
28 | .AppleDB
29 | .AppleDesktop
30 | Network Trash Folder
31 | Temporary Items
32 | .apdisk
33 |
34 | ### Swift ###
35 | # Xcode
36 | #
37 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
38 |
39 | ## User settings
40 | xcuserdata/
41 |
42 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
43 | *.xcscmblueprint
44 | *.xccheckout
45 |
46 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
47 | build/
48 | DerivedData/
49 | *.moved-aside
50 | *.pbxuser
51 | !default.pbxuser
52 | *.mode1v3
53 | !default.mode1v3
54 | *.mode2v3
55 | !default.mode2v3
56 | *.perspectivev3
57 | !default.perspectivev3
58 |
59 | ## Obj-C/Swift specific
60 | *.hmap
61 |
62 | ## App packaging
63 | *.ipa
64 | *.dSYM.zip
65 | *.dSYM
66 |
67 | ## Playgrounds
68 | timeline.xctimeline
69 | playground.xcworkspace
70 |
71 | # Swift Package Manager
72 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
73 | # Packages/
74 | # Package.pins
75 | # Package.resolved
76 | # *.xcodeproj
77 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
78 | # hence it is not needed unless you have added a package configuration file to your project
79 | # .swiftpm
80 |
81 | .build/
82 |
83 | # CocoaPods
84 | # We recommend against adding the Pods directory to your .gitignore. However
85 | # you should judge for yourself, the pros and cons are mentioned at:
86 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
87 | # Pods/
88 | # Add this line if you want to avoid checking in source code from the Xcode workspace
89 | # *.xcworkspace
90 |
91 | # Carthage
92 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
93 | # Carthage/Checkouts
94 |
95 | Carthage/Build/
96 |
97 | # Accio dependency management
98 | Dependencies/
99 | .accio/
100 |
101 | # fastlane
102 | # It is recommended to not store the screenshots in the git repo.
103 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
104 | # For more information about the recommended setup visit:
105 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
106 |
107 | fastlane/report.xml
108 | fastlane/Preview.html
109 | fastlane/screenshots/**/*.png
110 | fastlane/test_output
111 |
112 | # Code Injection
113 | # After new code Injection tools there's a generated folder /iOSInjectionProject
114 | # https://github.com/johnno1962/injectionforxcode
115 |
116 | iOSInjectionProject/
117 |
118 | ### VisualStudioCode ###
119 | .vscode/*
120 | !.vscode/settings.json
121 | !.vscode/tasks.json
122 | !.vscode/launch.json
123 | !.vscode/extensions.json
124 | *.code-workspace
125 |
126 | ### VisualStudioCode Patch ###
127 | # Ignore all local history of files
128 | .history
129 |
130 | ### Xcode ###
131 | # Xcode
132 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
133 |
134 |
135 |
136 |
137 | ## Gcc Patch
138 | /*.gcno
139 |
140 | ### Xcode Patch ###
141 | *.xcodeproj/*
142 | !*.xcodeproj/project.pbxproj
143 | !*.xcodeproj/xcshareddata/
144 | !*.xcworkspace/contents.xcworkspacedata
145 | **/xcshareddata/WorkspaceSettings.xcsettings
146 |
147 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,swift,xcode
148 |
149 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
150 |
151 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/ToDoService/NetworkRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkRepository.swift
3 | // MyToDo
4 | //
5 | // Created by Chiwon Song on 2020/07/31.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class NetworkRepository: Repository {
12 | private let TODO_URL = "https://us-central1-bearfried-b8ae1.cloudfunctions.net/api/todo"
13 |
14 | private var formatter: DateFormatter {
15 | let formatter = DateFormatter()
16 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
17 | return formatter
18 | }
19 |
20 | private var encoder: JSONEncoder {
21 | let jsonEncoder = JSONEncoder()
22 | jsonEncoder.dateEncodingStrategy = .formatted(formatter)
23 | return jsonEncoder
24 | }
25 |
26 | private var decoder: JSONDecoder {
27 | let jsonDecoder = JSONDecoder()
28 | jsonDecoder.dateDecodingStrategy = .formatted(formatter)
29 | return jsonDecoder
30 | }
31 |
32 | func create(item: ToDo, completed: @escaping (Bool) -> Void) {
33 | let data: [String: Any] = [
34 | "title": item.title,
35 | "completed": item.completed,
36 | "createdAt": formatter.string(from: item.createdAt),
37 | ]
38 |
39 | let jsonData = try? JSONSerialization.data(withJSONObject: data)
40 |
41 | var request = URLRequest(url: URL(string: TODO_URL)!)
42 | request.httpMethod = "POST"
43 | request.httpBody = jsonData
44 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
45 |
46 | URLSession.shared.dataTask(with: request) { data, _, error in
47 | guard error == nil else {
48 | DispatchQueue.main.async {
49 | completed(false)
50 | }
51 | return
52 | }
53 | DispatchQueue.main.async {
54 | completed(true)
55 | }
56 | }.resume()
57 | }
58 |
59 | func read(completed: @escaping ([ToDo]) -> Void) {
60 | URLSession.shared.dataTask(with: URL(string: TODO_URL)!) { data, _, error in
61 | guard error == nil else {
62 | DispatchQueue.main.async {
63 | completed([])
64 | }
65 | return
66 | }
67 |
68 | if let data = data,
69 | let todos = try? self.decoder.decode([ToDo].self, from: data) {
70 | DispatchQueue.main.async {
71 | completed(todos)
72 | }
73 | } else {
74 | DispatchQueue.main.async {
75 | completed([])
76 | }
77 | }
78 | }.resume()
79 | }
80 |
81 | func update(item: ToDo, completed: @escaping (Bool) -> Void) {
82 | guard let jsonData = try? encoder.encode(item) else {
83 | DispatchQueue.main.async {
84 | completed(false)
85 | }
86 | return
87 | }
88 |
89 | let url = "\(TODO_URL)/\(item.id)"
90 | var request = URLRequest(url: URL(string: url)!)
91 | request.httpMethod = "PUT"
92 | request.httpBody = jsonData
93 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
94 |
95 | URLSession.shared.dataTask(with: request) { _, _, error in
96 | guard error == nil else {
97 | DispatchQueue.main.async {
98 | completed(false)
99 | }
100 | return
101 | }
102 | DispatchQueue.main.async {
103 | completed(true)
104 | }
105 | }.resume()
106 | }
107 |
108 | func delete(item: ToDo, completed: @escaping (Bool) -> Void) {
109 | let url = "\(TODO_URL)/\(item.id)"
110 | var request = URLRequest(url: URL(string: url)!)
111 | request.httpMethod = "DELETE"
112 |
113 | URLSession.shared.dataTask(with: request) { _, _, error in
114 | guard error == nil else {
115 | DispatchQueue.main.async {
116 | completed(false)
117 | }
118 | return
119 | }
120 | DispatchQueue.main.async {
121 | completed(true)
122 | }
123 | }.resume()
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDoTests/MyToDoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyToDoTests.swift
3 | // MyToDoTests
4 | //
5 | // Created by Chiwon Song on 2020/07/25.
6 | // Copyright © 2020 Chiwon Song. All rights reserved.
7 | //
8 |
9 | @testable import MyToDo
10 | import XCTest
11 |
12 | class MyToDoTests: XCTestCase {
13 | func test_ToDo를_json으로_만들기() {
14 | let createdAt = Date()
15 | let formatter = DateFormatter()
16 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
17 | let createdAtString = formatter.string(from: createdAt)
18 | let str = "{\"id\":100,\"title\":\"title\",\"completed\":false,\"createdAt\":\"\(createdAtString)\"}"
19 |
20 | let todo = ToDo(id: 100,
21 | title: "title",
22 | completed: false,
23 | createdAt: createdAt,
24 | updatedAt: nil)
25 |
26 | let jsonEncoder = JSONEncoder()
27 | jsonEncoder.dateEncodingStrategy = .formatted(formatter)
28 | let data = try! jsonEncoder.encode(todo)
29 | let json = String(data: data, encoding: .utf8)!
30 |
31 | XCTAssertEqual(str, json)
32 | }
33 |
34 | func test_json으로_ToDo_만들기() {
35 | let createdAt = Date()
36 | let formatter = DateFormatter()
37 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
38 | let createdAtString = formatter.string(from: createdAt)
39 | let str = "{\"id\":100,\"title\":\"title\",\"completed\":false,\"createdAt\":\"\(createdAtString)\"}"
40 |
41 | let jsonDecoder = JSONDecoder()
42 | jsonDecoder.dateDecodingStrategy = .formatted(formatter)
43 | let data = str.data(using: .utf8)!
44 | let todo = try! jsonDecoder.decode(ToDo.self, from: data)
45 |
46 | XCTAssertNotNil(todo)
47 | XCTAssertEqual(todo.id, 100)
48 | XCTAssertEqual(todo.title, "title")
49 | XCTAssertEqual(todo.completed, false)
50 | XCTAssertEqual(formatter.string(from: todo.createdAt),
51 | formatter.string(from: createdAt))
52 | XCTAssertNil(todo.updatedAt)
53 | }
54 |
55 | func test_ToDo_만들고_저장하기() {
56 | let repository: Repository = MemoryRepository()
57 | let service: ToDoService = ToDoServiceImpl(repository: repository)
58 | let expectation = self.expectation(description: #function)
59 |
60 | let title = "HelloWorld"
61 | service.create(title: title) { success in
62 | XCTAssertTrue(success)
63 |
64 | service.list { list in
65 | XCTAssertEqual(list.count, 1)
66 | XCTAssertEqual(list[0].title, "HelloWorld")
67 | XCTAssertEqual(list[0].completed, false)
68 | expectation.fulfill()
69 | }
70 | }
71 |
72 | waitForExpectations(timeout: 10)
73 | }
74 |
75 | func test_ToDo_만들고_완료하기() {
76 | let repository: Repository = MemoryRepository()
77 | let service: ToDoService = ToDoServiceImpl(repository: repository)
78 | let expectation = self.expectation(description: #function)
79 |
80 | service.create(title: "HelloWorld") { success in
81 | XCTAssertTrue(success)
82 |
83 | service.list { list in
84 | XCTAssertEqual(list.count, 1)
85 |
86 | service.toggleCompleted(item: list[0]) { success in
87 | XCTAssertTrue(success)
88 |
89 | service.list { list2 in
90 | XCTAssertEqual(list2.count, 1)
91 | XCTAssertEqual(list2[0].title, "HelloWorld")
92 | XCTAssertEqual(list2[0].completed, true)
93 | expectation.fulfill()
94 | }
95 | }
96 | }
97 | }
98 |
99 | waitForExpectations(timeout: 10)
100 | }
101 |
102 | func test_ToDo_만들고_제목_변경하기() {
103 | let repository: Repository = MemoryRepository()
104 | let service: ToDoService = ToDoServiceImpl(repository: repository)
105 | async { done in
106 | service.create(title: "HelloWorld") { success in
107 | XCTAssertTrue(success)
108 |
109 | service.list { list in
110 | XCTAssertEqual(list.count, 1)
111 |
112 | service.update(title: "WorldHello", with: list[0]) { success in
113 | XCTAssertTrue(success)
114 |
115 | service.list { list2 in
116 | XCTAssertEqual(list2.count, 1)
117 | XCTAssertEqual(list2[0].title, "WorldHello")
118 | XCTAssertNotNil(list2[0])
119 |
120 | done()
121 | }
122 | }
123 | }
124 | }
125 | }
126 | }
127 |
128 | func test_ToDo_만들고_삭제하기() {
129 | let repository: Repository = MemoryRepository()
130 | let service: ToDoService = ToDoServiceImpl(repository: repository)
131 | async { done in
132 | service.create(title: "HelloWorld") { success in
133 | XCTAssertTrue(success)
134 |
135 | service.list { list in
136 | XCTAssertEqual(list.count, 1)
137 |
138 | service.delete(item: list[0]) { (success) in
139 | XCTAssertTrue(success)
140 |
141 | service.list { list2 in
142 | XCTAssertEqual(list2.count, 0)
143 |
144 | done()
145 | }
146 | }
147 | }
148 | }
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
43 |
55 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
145 |
153 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/MyToDo/MyToDo/MyToDo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A1005BDF24CBE2FB0091740A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005BDE24CBE2FB0091740A /* AppDelegate.swift */; };
11 | A1005BE124CBE2FB0091740A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005BE024CBE2FB0091740A /* SceneDelegate.swift */; };
12 | A1005BE324CBE2FB0091740A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005BE224CBE2FB0091740A /* ViewController.swift */; };
13 | A1005BE624CBE2FB0091740A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1005BE424CBE2FB0091740A /* Main.storyboard */; };
14 | A1005BE824CBE2FD0091740A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A1005BE724CBE2FD0091740A /* Assets.xcassets */; };
15 | A1005BEB24CBE2FD0091740A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A1005BE924CBE2FD0091740A /* LaunchScreen.storyboard */; };
16 | A1005BF624CBE2FD0091740A /* MyToDoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005BF524CBE2FD0091740A /* MyToDoTests.swift */; };
17 | A1005C0324CBF17C0091740A /* ToDoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0224CBF17C0091740A /* ToDoService.swift */; };
18 | A1005C0524CBF5620091740A /* ToDo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0424CBF5620091740A /* ToDo.swift */; };
19 | A1005C0724CC05860091740A /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0624CC05860091740A /* Repository.swift */; };
20 | A1005C0924CC079E0091740A /* MemoryRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0824CC079E0091740A /* MemoryRepository.swift */; };
21 | A1005C0B24CC0BAB0091740A /* AsyncUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0A24CC0BAB0091740A /* AsyncUtil.swift */; };
22 | A1005C0D24CC20E80091740A /* TitleInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0C24CC20E80091740A /* TitleInputViewController.swift */; };
23 | A1005C0F24CC761C0091740A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C0E24CC761C0091740A /* ViewModel.swift */; };
24 | A1005C1124CC878D0091740A /* UserDefaultsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1005C1024CC878D0091740A /* UserDefaultsRepository.swift */; };
25 | A16C6FB924D415B9002BE50F /* NetworkRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16C6FB824D415B9002BE50F /* NetworkRepository.swift */; };
26 | /* End PBXBuildFile section */
27 |
28 | /* Begin PBXContainerItemProxy section */
29 | A1005BF224CBE2FD0091740A /* PBXContainerItemProxy */ = {
30 | isa = PBXContainerItemProxy;
31 | containerPortal = A1005BD324CBE2FB0091740A /* Project object */;
32 | proxyType = 1;
33 | remoteGlobalIDString = A1005BDA24CBE2FB0091740A;
34 | remoteInfo = MyToDo;
35 | };
36 | /* End PBXContainerItemProxy section */
37 |
38 | /* Begin PBXFileReference section */
39 | A1005BDB24CBE2FB0091740A /* MyToDo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyToDo.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | A1005BDE24CBE2FB0091740A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | A1005BE024CBE2FB0091740A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
42 | A1005BE224CBE2FB0091740A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
43 | A1005BE524CBE2FB0091740A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
44 | A1005BE724CBE2FD0091740A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
45 | A1005BEA24CBE2FD0091740A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
46 | A1005BEC24CBE2FD0091740A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
47 | A1005BF124CBE2FD0091740A /* MyToDoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyToDoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
48 | A1005BF524CBE2FD0091740A /* MyToDoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyToDoTests.swift; sourceTree = ""; };
49 | A1005BF724CBE2FD0091740A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
50 | A1005C0224CBF17C0091740A /* ToDoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoService.swift; sourceTree = ""; };
51 | A1005C0424CBF5620091740A /* ToDo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDo.swift; sourceTree = ""; };
52 | A1005C0624CC05860091740A /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; };
53 | A1005C0824CC079E0091740A /* MemoryRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryRepository.swift; sourceTree = ""; };
54 | A1005C0A24CC0BAB0091740A /* AsyncUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncUtil.swift; sourceTree = ""; };
55 | A1005C0C24CC20E80091740A /* TitleInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleInputViewController.swift; sourceTree = ""; };
56 | A1005C0E24CC761C0091740A /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; };
57 | A1005C1024CC878D0091740A /* UserDefaultsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsRepository.swift; sourceTree = ""; };
58 | A16C6FB824D415B9002BE50F /* NetworkRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRepository.swift; sourceTree = ""; };
59 | /* End PBXFileReference section */
60 |
61 | /* Begin PBXFrameworksBuildPhase section */
62 | A1005BD824CBE2FB0091740A /* Frameworks */ = {
63 | isa = PBXFrameworksBuildPhase;
64 | buildActionMask = 2147483647;
65 | files = (
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | A1005BEE24CBE2FD0091740A /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | );
74 | runOnlyForDeploymentPostprocessing = 0;
75 | };
76 | /* End PBXFrameworksBuildPhase section */
77 |
78 | /* Begin PBXGroup section */
79 | A1005BD224CBE2FB0091740A = {
80 | isa = PBXGroup;
81 | children = (
82 | A1005BDD24CBE2FB0091740A /* MyToDo */,
83 | A1005C0124CBF1670091740A /* ToDoService */,
84 | A1005BF424CBE2FD0091740A /* MyToDoTests */,
85 | A1005BDC24CBE2FB0091740A /* Products */,
86 | );
87 | sourceTree = "";
88 | };
89 | A1005BDC24CBE2FB0091740A /* Products */ = {
90 | isa = PBXGroup;
91 | children = (
92 | A1005BDB24CBE2FB0091740A /* MyToDo.app */,
93 | A1005BF124CBE2FD0091740A /* MyToDoTests.xctest */,
94 | );
95 | name = Products;
96 | sourceTree = "";
97 | };
98 | A1005BDD24CBE2FB0091740A /* MyToDo */ = {
99 | isa = PBXGroup;
100 | children = (
101 | A1005BDE24CBE2FB0091740A /* AppDelegate.swift */,
102 | A1005BE024CBE2FB0091740A /* SceneDelegate.swift */,
103 | A1005BE224CBE2FB0091740A /* ViewController.swift */,
104 | A1005C0C24CC20E80091740A /* TitleInputViewController.swift */,
105 | A1005C0E24CC761C0091740A /* ViewModel.swift */,
106 | A1005BE424CBE2FB0091740A /* Main.storyboard */,
107 | A1005BE724CBE2FD0091740A /* Assets.xcassets */,
108 | A1005BE924CBE2FD0091740A /* LaunchScreen.storyboard */,
109 | A1005BEC24CBE2FD0091740A /* Info.plist */,
110 | );
111 | path = MyToDo;
112 | sourceTree = "";
113 | };
114 | A1005BF424CBE2FD0091740A /* MyToDoTests */ = {
115 | isa = PBXGroup;
116 | children = (
117 | A1005BF524CBE2FD0091740A /* MyToDoTests.swift */,
118 | A1005C0A24CC0BAB0091740A /* AsyncUtil.swift */,
119 | A1005BF724CBE2FD0091740A /* Info.plist */,
120 | );
121 | path = MyToDoTests;
122 | sourceTree = "";
123 | };
124 | A1005C0124CBF1670091740A /* ToDoService */ = {
125 | isa = PBXGroup;
126 | children = (
127 | A1005C0224CBF17C0091740A /* ToDoService.swift */,
128 | A1005C0424CBF5620091740A /* ToDo.swift */,
129 | A1005C0624CC05860091740A /* Repository.swift */,
130 | A1005C0824CC079E0091740A /* MemoryRepository.swift */,
131 | A1005C1024CC878D0091740A /* UserDefaultsRepository.swift */,
132 | A16C6FB824D415B9002BE50F /* NetworkRepository.swift */,
133 | );
134 | path = ToDoService;
135 | sourceTree = "";
136 | };
137 | /* End PBXGroup section */
138 |
139 | /* Begin PBXNativeTarget section */
140 | A1005BDA24CBE2FB0091740A /* MyToDo */ = {
141 | isa = PBXNativeTarget;
142 | buildConfigurationList = A1005BFA24CBE2FD0091740A /* Build configuration list for PBXNativeTarget "MyToDo" */;
143 | buildPhases = (
144 | A1005BD724CBE2FB0091740A /* Sources */,
145 | A1005BD824CBE2FB0091740A /* Frameworks */,
146 | A1005BD924CBE2FB0091740A /* Resources */,
147 | );
148 | buildRules = (
149 | );
150 | dependencies = (
151 | );
152 | name = MyToDo;
153 | productName = MyToDo;
154 | productReference = A1005BDB24CBE2FB0091740A /* MyToDo.app */;
155 | productType = "com.apple.product-type.application";
156 | };
157 | A1005BF024CBE2FD0091740A /* MyToDoTests */ = {
158 | isa = PBXNativeTarget;
159 | buildConfigurationList = A1005BFD24CBE2FD0091740A /* Build configuration list for PBXNativeTarget "MyToDoTests" */;
160 | buildPhases = (
161 | A1005BED24CBE2FD0091740A /* Sources */,
162 | A1005BEE24CBE2FD0091740A /* Frameworks */,
163 | A1005BEF24CBE2FD0091740A /* Resources */,
164 | );
165 | buildRules = (
166 | );
167 | dependencies = (
168 | A1005BF324CBE2FD0091740A /* PBXTargetDependency */,
169 | );
170 | name = MyToDoTests;
171 | productName = MyToDoTests;
172 | productReference = A1005BF124CBE2FD0091740A /* MyToDoTests.xctest */;
173 | productType = "com.apple.product-type.bundle.unit-test";
174 | };
175 | /* End PBXNativeTarget section */
176 |
177 | /* Begin PBXProject section */
178 | A1005BD324CBE2FB0091740A /* Project object */ = {
179 | isa = PBXProject;
180 | attributes = {
181 | LastSwiftUpdateCheck = 1150;
182 | LastUpgradeCheck = 1150;
183 | ORGANIZATIONNAME = "Chiwon Song";
184 | TargetAttributes = {
185 | A1005BDA24CBE2FB0091740A = {
186 | CreatedOnToolsVersion = 11.5;
187 | };
188 | A1005BF024CBE2FD0091740A = {
189 | CreatedOnToolsVersion = 11.5;
190 | TestTargetID = A1005BDA24CBE2FB0091740A;
191 | };
192 | };
193 | };
194 | buildConfigurationList = A1005BD624CBE2FB0091740A /* Build configuration list for PBXProject "MyToDo" */;
195 | compatibilityVersion = "Xcode 9.3";
196 | developmentRegion = en;
197 | hasScannedForEncodings = 0;
198 | knownRegions = (
199 | en,
200 | Base,
201 | );
202 | mainGroup = A1005BD224CBE2FB0091740A;
203 | productRefGroup = A1005BDC24CBE2FB0091740A /* Products */;
204 | projectDirPath = "";
205 | projectRoot = "";
206 | targets = (
207 | A1005BDA24CBE2FB0091740A /* MyToDo */,
208 | A1005BF024CBE2FD0091740A /* MyToDoTests */,
209 | );
210 | };
211 | /* End PBXProject section */
212 |
213 | /* Begin PBXResourcesBuildPhase section */
214 | A1005BD924CBE2FB0091740A /* Resources */ = {
215 | isa = PBXResourcesBuildPhase;
216 | buildActionMask = 2147483647;
217 | files = (
218 | A1005BEB24CBE2FD0091740A /* LaunchScreen.storyboard in Resources */,
219 | A1005BE824CBE2FD0091740A /* Assets.xcassets in Resources */,
220 | A1005BE624CBE2FB0091740A /* Main.storyboard in Resources */,
221 | );
222 | runOnlyForDeploymentPostprocessing = 0;
223 | };
224 | A1005BEF24CBE2FD0091740A /* Resources */ = {
225 | isa = PBXResourcesBuildPhase;
226 | buildActionMask = 2147483647;
227 | files = (
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | /* End PBXResourcesBuildPhase section */
232 |
233 | /* Begin PBXSourcesBuildPhase section */
234 | A1005BD724CBE2FB0091740A /* Sources */ = {
235 | isa = PBXSourcesBuildPhase;
236 | buildActionMask = 2147483647;
237 | files = (
238 | A1005BE324CBE2FB0091740A /* ViewController.swift in Sources */,
239 | A1005C0F24CC761C0091740A /* ViewModel.swift in Sources */,
240 | A1005C1124CC878D0091740A /* UserDefaultsRepository.swift in Sources */,
241 | A1005C0524CBF5620091740A /* ToDo.swift in Sources */,
242 | A1005C0D24CC20E80091740A /* TitleInputViewController.swift in Sources */,
243 | A1005C0924CC079E0091740A /* MemoryRepository.swift in Sources */,
244 | A16C6FB924D415B9002BE50F /* NetworkRepository.swift in Sources */,
245 | A1005BDF24CBE2FB0091740A /* AppDelegate.swift in Sources */,
246 | A1005BE124CBE2FB0091740A /* SceneDelegate.swift in Sources */,
247 | A1005C0324CBF17C0091740A /* ToDoService.swift in Sources */,
248 | A1005C0724CC05860091740A /* Repository.swift in Sources */,
249 | );
250 | runOnlyForDeploymentPostprocessing = 0;
251 | };
252 | A1005BED24CBE2FD0091740A /* Sources */ = {
253 | isa = PBXSourcesBuildPhase;
254 | buildActionMask = 2147483647;
255 | files = (
256 | A1005C0B24CC0BAB0091740A /* AsyncUtil.swift in Sources */,
257 | A1005BF624CBE2FD0091740A /* MyToDoTests.swift in Sources */,
258 | );
259 | runOnlyForDeploymentPostprocessing = 0;
260 | };
261 | /* End PBXSourcesBuildPhase section */
262 |
263 | /* Begin PBXTargetDependency section */
264 | A1005BF324CBE2FD0091740A /* PBXTargetDependency */ = {
265 | isa = PBXTargetDependency;
266 | target = A1005BDA24CBE2FB0091740A /* MyToDo */;
267 | targetProxy = A1005BF224CBE2FD0091740A /* PBXContainerItemProxy */;
268 | };
269 | /* End PBXTargetDependency section */
270 |
271 | /* Begin PBXVariantGroup section */
272 | A1005BE424CBE2FB0091740A /* Main.storyboard */ = {
273 | isa = PBXVariantGroup;
274 | children = (
275 | A1005BE524CBE2FB0091740A /* Base */,
276 | );
277 | name = Main.storyboard;
278 | sourceTree = "";
279 | };
280 | A1005BE924CBE2FD0091740A /* LaunchScreen.storyboard */ = {
281 | isa = PBXVariantGroup;
282 | children = (
283 | A1005BEA24CBE2FD0091740A /* Base */,
284 | );
285 | name = LaunchScreen.storyboard;
286 | sourceTree = "";
287 | };
288 | /* End PBXVariantGroup section */
289 |
290 | /* Begin XCBuildConfiguration section */
291 | A1005BF824CBE2FD0091740A /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ALWAYS_SEARCH_USER_PATHS = NO;
295 | CLANG_ANALYZER_NONNULL = YES;
296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
298 | CLANG_CXX_LIBRARY = "libc++";
299 | CLANG_ENABLE_MODULES = YES;
300 | CLANG_ENABLE_OBJC_ARC = YES;
301 | CLANG_ENABLE_OBJC_WEAK = YES;
302 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
303 | CLANG_WARN_BOOL_CONVERSION = YES;
304 | CLANG_WARN_COMMA = YES;
305 | CLANG_WARN_CONSTANT_CONVERSION = YES;
306 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
307 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
308 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
309 | CLANG_WARN_EMPTY_BODY = YES;
310 | CLANG_WARN_ENUM_CONVERSION = YES;
311 | CLANG_WARN_INFINITE_RECURSION = YES;
312 | CLANG_WARN_INT_CONVERSION = YES;
313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
317 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
318 | CLANG_WARN_STRICT_PROTOTYPES = YES;
319 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
320 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
321 | CLANG_WARN_UNREACHABLE_CODE = YES;
322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
323 | COPY_PHASE_STRIP = NO;
324 | DEBUG_INFORMATION_FORMAT = dwarf;
325 | ENABLE_STRICT_OBJC_MSGSEND = YES;
326 | ENABLE_TESTABILITY = YES;
327 | GCC_C_LANGUAGE_STANDARD = gnu11;
328 | GCC_DYNAMIC_NO_PIC = NO;
329 | GCC_NO_COMMON_BLOCKS = YES;
330 | GCC_OPTIMIZATION_LEVEL = 0;
331 | GCC_PREPROCESSOR_DEFINITIONS = (
332 | "DEBUG=1",
333 | "$(inherited)",
334 | );
335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
337 | GCC_WARN_UNDECLARED_SELECTOR = YES;
338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
339 | GCC_WARN_UNUSED_FUNCTION = YES;
340 | GCC_WARN_UNUSED_VARIABLE = YES;
341 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
342 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
343 | MTL_FAST_MATH = YES;
344 | ONLY_ACTIVE_ARCH = YES;
345 | SDKROOT = iphoneos;
346 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
347 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
348 | };
349 | name = Debug;
350 | };
351 | A1005BF924CBE2FD0091740A /* Release */ = {
352 | isa = XCBuildConfiguration;
353 | buildSettings = {
354 | ALWAYS_SEARCH_USER_PATHS = NO;
355 | CLANG_ANALYZER_NONNULL = YES;
356 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
358 | CLANG_CXX_LIBRARY = "libc++";
359 | CLANG_ENABLE_MODULES = YES;
360 | CLANG_ENABLE_OBJC_ARC = YES;
361 | CLANG_ENABLE_OBJC_WEAK = YES;
362 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
363 | CLANG_WARN_BOOL_CONVERSION = YES;
364 | CLANG_WARN_COMMA = YES;
365 | CLANG_WARN_CONSTANT_CONVERSION = YES;
366 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
367 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
368 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
369 | CLANG_WARN_EMPTY_BODY = YES;
370 | CLANG_WARN_ENUM_CONVERSION = YES;
371 | CLANG_WARN_INFINITE_RECURSION = YES;
372 | CLANG_WARN_INT_CONVERSION = YES;
373 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
374 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
378 | CLANG_WARN_STRICT_PROTOTYPES = YES;
379 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
380 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
381 | CLANG_WARN_UNREACHABLE_CODE = YES;
382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
383 | COPY_PHASE_STRIP = NO;
384 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
385 | ENABLE_NS_ASSERTIONS = NO;
386 | ENABLE_STRICT_OBJC_MSGSEND = YES;
387 | GCC_C_LANGUAGE_STANDARD = gnu11;
388 | GCC_NO_COMMON_BLOCKS = YES;
389 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
390 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
391 | GCC_WARN_UNDECLARED_SELECTOR = YES;
392 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
393 | GCC_WARN_UNUSED_FUNCTION = YES;
394 | GCC_WARN_UNUSED_VARIABLE = YES;
395 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
396 | MTL_ENABLE_DEBUG_INFO = NO;
397 | MTL_FAST_MATH = YES;
398 | SDKROOT = iphoneos;
399 | SWIFT_COMPILATION_MODE = wholemodule;
400 | SWIFT_OPTIMIZATION_LEVEL = "-O";
401 | VALIDATE_PRODUCT = YES;
402 | };
403 | name = Release;
404 | };
405 | A1005BFB24CBE2FD0091740A /* Debug */ = {
406 | isa = XCBuildConfiguration;
407 | buildSettings = {
408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
409 | CODE_SIGN_STYLE = Automatic;
410 | DEVELOPMENT_TEAM = 77YZ9248DM;
411 | INFOPLIST_FILE = MyToDo/Info.plist;
412 | LD_RUNPATH_SEARCH_PATHS = (
413 | "$(inherited)",
414 | "@executable_path/Frameworks",
415 | );
416 | PRODUCT_BUNDLE_IDENTIFIER = com.friedbear.app.MyToDo;
417 | PRODUCT_NAME = "$(TARGET_NAME)";
418 | SWIFT_VERSION = 5.0;
419 | TARGETED_DEVICE_FAMILY = 1;
420 | };
421 | name = Debug;
422 | };
423 | A1005BFC24CBE2FD0091740A /* Release */ = {
424 | isa = XCBuildConfiguration;
425 | buildSettings = {
426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
427 | CODE_SIGN_STYLE = Automatic;
428 | DEVELOPMENT_TEAM = 77YZ9248DM;
429 | INFOPLIST_FILE = MyToDo/Info.plist;
430 | LD_RUNPATH_SEARCH_PATHS = (
431 | "$(inherited)",
432 | "@executable_path/Frameworks",
433 | );
434 | PRODUCT_BUNDLE_IDENTIFIER = com.friedbear.app.MyToDo;
435 | PRODUCT_NAME = "$(TARGET_NAME)";
436 | SWIFT_VERSION = 5.0;
437 | TARGETED_DEVICE_FAMILY = 1;
438 | };
439 | name = Release;
440 | };
441 | A1005BFE24CBE2FD0091740A /* Debug */ = {
442 | isa = XCBuildConfiguration;
443 | buildSettings = {
444 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
445 | BUNDLE_LOADER = "$(TEST_HOST)";
446 | CODE_SIGN_STYLE = Automatic;
447 | DEVELOPMENT_TEAM = 77YZ9248DM;
448 | INFOPLIST_FILE = MyToDoTests/Info.plist;
449 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
450 | LD_RUNPATH_SEARCH_PATHS = (
451 | "$(inherited)",
452 | "@executable_path/Frameworks",
453 | "@loader_path/Frameworks",
454 | );
455 | PRODUCT_BUNDLE_IDENTIFIER = com.friedbear.app.MyToDoTests;
456 | PRODUCT_NAME = "$(TARGET_NAME)";
457 | SWIFT_VERSION = 5.0;
458 | TARGETED_DEVICE_FAMILY = "1,2";
459 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyToDo.app/MyToDo";
460 | };
461 | name = Debug;
462 | };
463 | A1005BFF24CBE2FD0091740A /* Release */ = {
464 | isa = XCBuildConfiguration;
465 | buildSettings = {
466 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
467 | BUNDLE_LOADER = "$(TEST_HOST)";
468 | CODE_SIGN_STYLE = Automatic;
469 | DEVELOPMENT_TEAM = 77YZ9248DM;
470 | INFOPLIST_FILE = MyToDoTests/Info.plist;
471 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
472 | LD_RUNPATH_SEARCH_PATHS = (
473 | "$(inherited)",
474 | "@executable_path/Frameworks",
475 | "@loader_path/Frameworks",
476 | );
477 | PRODUCT_BUNDLE_IDENTIFIER = com.friedbear.app.MyToDoTests;
478 | PRODUCT_NAME = "$(TARGET_NAME)";
479 | SWIFT_VERSION = 5.0;
480 | TARGETED_DEVICE_FAMILY = "1,2";
481 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MyToDo.app/MyToDo";
482 | };
483 | name = Release;
484 | };
485 | /* End XCBuildConfiguration section */
486 |
487 | /* Begin XCConfigurationList section */
488 | A1005BD624CBE2FB0091740A /* Build configuration list for PBXProject "MyToDo" */ = {
489 | isa = XCConfigurationList;
490 | buildConfigurations = (
491 | A1005BF824CBE2FD0091740A /* Debug */,
492 | A1005BF924CBE2FD0091740A /* Release */,
493 | );
494 | defaultConfigurationIsVisible = 0;
495 | defaultConfigurationName = Release;
496 | };
497 | A1005BFA24CBE2FD0091740A /* Build configuration list for PBXNativeTarget "MyToDo" */ = {
498 | isa = XCConfigurationList;
499 | buildConfigurations = (
500 | A1005BFB24CBE2FD0091740A /* Debug */,
501 | A1005BFC24CBE2FD0091740A /* Release */,
502 | );
503 | defaultConfigurationIsVisible = 0;
504 | defaultConfigurationName = Release;
505 | };
506 | A1005BFD24CBE2FD0091740A /* Build configuration list for PBXNativeTarget "MyToDoTests" */ = {
507 | isa = XCConfigurationList;
508 | buildConfigurations = (
509 | A1005BFE24CBE2FD0091740A /* Debug */,
510 | A1005BFF24CBE2FD0091740A /* Release */,
511 | );
512 | defaultConfigurationIsVisible = 0;
513 | defaultConfigurationName = Release;
514 | };
515 | /* End XCConfigurationList section */
516 | };
517 | rootObject = A1005BD324CBE2FB0091740A /* Project object */;
518 | }
519 |
--------------------------------------------------------------------------------