Skip to content

Commit bb3c9af

Browse files
committed
Example - services layer
1 parent 9198b50 commit bb3c9af

24 files changed

+1015
-2
lines changed

Example/AppDelegate.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,19 @@ import ServiceContainerKit
1313
class AppDelegate: UIResponder, UIApplicationDelegate {
1414

1515
var window: UIWindow?
16+
private var appServices: AppDelegateServices?
1617

1718
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
1819

20+
let (services, appServices) = ServicesFactory.makeDefault()
21+
self.appServices = appServices
22+
ServiceInjectResolver.register(services)
23+
24+
// AutoLogin
25+
appServices.userService.auth(login: "User") { result in
26+
print("AutoLogin result: \(result)")
27+
}
28+
1929
return true
2030
}
2131

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// APIClient.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 29.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
enum APIError: Error {
12+
case userInvalid
13+
case notFound
14+
}
15+
16+
protocol APIUserIdProvider: class {
17+
func currentUserId() -> User.Id?
18+
}
19+
20+
protocol APIClient: class {
21+
// User
22+
func authUser(login: String, completion: @escaping (Result<User, APIError>) -> Void)
23+
24+
// Folders
25+
func requestFolders(completion: @escaping (Result<[NoteFolder], APIError>) -> Void)
26+
func addFolder(content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, APIError>) -> Void)
27+
func removeFolder(folderId: NoteFolder.Id, completion: @escaping (Result<Void, APIError>) -> Void)
28+
func editFolder(folderId: NoteFolder.Id, content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, APIError>) -> Void)
29+
30+
// Notes
31+
func requestNotes(folderId: NoteFolder.Id, completion: @escaping (Result<[NoteRecord], APIError>) -> Void)
32+
func addNote(folderId: NoteFolder.Id, content: NoteRecord.Content, completion: @escaping (Result<NoteRecord, APIError>) -> Void)
33+
func removeNote(folderId: NoteFolder.Id, recordId: NoteRecord.Id, completion: @escaping (Result<Void, APIError>) -> Void)
34+
func editNote(folderId: NoteFolder.Id, recordId: NoteRecord.Id, content: NoteRecord.Content, completion: @escaping (Result<NoteRecord, APIError>) -> Void)
35+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// APIClientFactory.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 29.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import ServiceContainerKit
11+
12+
struct APIClientFactory: ServiceFactory {
13+
let userIdProvider: APIUserIdProvider
14+
15+
let mode: ServiceFactoryMode = .atOne
16+
func makeService() throws -> APIClient {
17+
return APIMockClient(userIdProvider: userIdProvider)
18+
}
19+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//
2+
// APIMockClient.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 29.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
final class APIMockClient: APIClient {
12+
private struct UserStore {
13+
var folders: [NoteFolder] = []
14+
var notes: [NoteFolder.Id: [NoteRecord]] = [:]
15+
}
16+
17+
private let userIdProvider: APIUserIdProvider
18+
19+
private var authUsers: [String: User] = [:]
20+
private var userStores: [User.Id: UserStore] = [:]
21+
22+
private var lastUserId: User.Id = 0
23+
private var lastFolderId: NoteFolder.Id = 0
24+
private var lastNoteId: NoteRecord.Id = 0
25+
26+
init(userIdProvider: APIUserIdProvider) {
27+
self.userIdProvider = userIdProvider
28+
}
29+
30+
// MARK: User
31+
func authUser(login: String, completion: @escaping (Result<User, APIError>) -> Void) {
32+
if let user = authUsers[login] {
33+
completion(.success(user))
34+
} else {
35+
lastUserId += 1
36+
let user = User(id: lastUserId, login: login)
37+
authUsers[login] = user
38+
userStores[user.id] = .init()
39+
40+
completion(.success(user))
41+
}
42+
}
43+
44+
// MARK: Folders
45+
func requestFolders(completion: @escaping (Result<[NoteFolder], APIError>) -> Void) {
46+
guard let userId = userIdProvider.currentUserId(),
47+
let folders = userStores[userId]?.folders else {
48+
return completion(.failure(.userInvalid))
49+
}
50+
completion(.success(folders))
51+
}
52+
53+
func addFolder(content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, APIError>) -> Void) {
54+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
55+
return completion(.failure(.userInvalid))
56+
}
57+
58+
lastFolderId += 1
59+
let folder = NoteFolder(id: lastFolderId, content: content)
60+
userStores[userId]?.folders.append(folder)
61+
userStores[userId]?.notes[folder.id] = []
62+
completion(.success(folder))
63+
}
64+
65+
func removeFolder(folderId: NoteFolder.Id, completion: @escaping (Result<Void, APIError>) -> Void) {
66+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
67+
return completion(.failure(.userInvalid))
68+
}
69+
guard let index = userStores[userId]?.folders.firstIndex(where: { $0.id == folderId }) else {
70+
return completion(.failure(.notFound))
71+
}
72+
userStores[userId]?.folders.remove(at: index)
73+
userStores[userId]?.notes.removeValue(forKey: folderId)
74+
completion(.success(()))
75+
}
76+
77+
func editFolder(folderId: NoteFolder.Id, content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, APIError>) -> Void) {
78+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
79+
return completion(.failure(.userInvalid))
80+
}
81+
guard let index = userStores[userId]?.folders.firstIndex(where: { $0.id == folderId }) else {
82+
return completion(.failure(.notFound))
83+
}
84+
let folder = NoteFolder(id: folderId, content: content)
85+
userStores[userId]?.folders[index] = folder
86+
completion(.success(folder))
87+
}
88+
89+
// MARK: Notes
90+
func requestNotes(folderId: NoteFolder.Id, completion: @escaping (Result<[NoteRecord], APIError>) -> Void) {
91+
guard let userId = userIdProvider.currentUserId(),
92+
let noteMap = userStores[userId]?.notes else {
93+
return completion(.failure(.userInvalid))
94+
}
95+
if let notes = noteMap[folderId] {
96+
completion(.success(notes))
97+
} else {
98+
completion(.failure(.notFound))
99+
}
100+
}
101+
102+
func addNote(folderId: NoteFolder.Id, content: NoteRecord.Content, completion: @escaping (Result<NoteRecord, APIError>) -> Void) {
103+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
104+
return completion(.failure(.userInvalid))
105+
}
106+
guard userStores[userId]?.notes[folderId] != nil else {
107+
return completion(.failure(.notFound))
108+
}
109+
110+
lastNoteId += 1
111+
let note = NoteRecord(id: lastNoteId, date: Date(), content: content)
112+
userStores[userId]?.notes[folderId]?.append(note)
113+
completion(.success(note))
114+
}
115+
116+
func removeNote(folderId: NoteFolder.Id, recordId: NoteRecord.Id, completion: @escaping (Result<Void, APIError>) -> Void) {
117+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
118+
return completion(.failure(.userInvalid))
119+
}
120+
guard let index = userStores[userId]?.notes[folderId]?.firstIndex(where: { $0.id == recordId }) else {
121+
return completion(.failure(APIError.notFound))
122+
}
123+
124+
userStores[userId]?.notes[folderId]?.remove(at: index)
125+
completion(.success(()))
126+
}
127+
128+
func editNote(folderId: NoteFolder.Id, recordId: NoteRecord.Id, content: NoteRecord.Content, completion: @escaping (Result<NoteRecord, APIError>) -> Void) {
129+
guard let userId = userIdProvider.currentUserId(), userStores[userId] != nil else {
130+
return completion(.failure(.userInvalid))
131+
}
132+
guard let index = userStores[userId]?.notes[folderId]?.firstIndex(where: { $0.id == recordId }) else {
133+
return completion(.failure(.notFound))
134+
}
135+
136+
let note = NoteRecord(id: recordId, date: Date(), content: content)
137+
userStores[userId]?.notes[folderId]?[index] = note
138+
completion(.success(note))
139+
}
140+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// NoteFoldersManager.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 30.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Combine
11+
12+
protocol NoteFoldersManager: class {
13+
var folders: [NoteFolder] { get }
14+
var foldersPublisher: Published<[NoteFolder]>.Publisher { get }
15+
16+
func reloadIfNeeded()
17+
func reload(completion: ((Result<[NoteFolder], Error>) -> Void)?)
18+
19+
func add(content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, Error>) -> Void)
20+
func remove(folderId: NoteFolder.Id, completion: @escaping (Result<Void, Error>) -> Void)
21+
func edit(folderId: NoteFolder.Id, content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, Error>) -> Void)
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// NoteFoldersManagerFactory.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 30.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import ServiceContainerKit
11+
12+
struct NoteFoldersManagerFactory: ServiceFactory {
13+
let apiClient: ServiceProvider<APIClient>
14+
let userService: ServiceProvider<UserService>
15+
16+
let mode: ServiceFactoryMode = .lazy
17+
func makeService() throws -> NoteFoldersManager {
18+
return NoteFoldersManagerImpl(
19+
apiClient: try apiClient.getService(),
20+
userService: try userService.getService())
21+
}
22+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// NoteFoldersManagerImpl.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 30.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Combine
11+
12+
final class NoteFoldersManagerImpl: NoteFoldersManager {
13+
private let apiClient: APIClient
14+
private var lastReloadSuccess = false
15+
16+
private var cancellableStorage: Set<AnyCancellable> = []
17+
18+
@Published
19+
private(set) var folders: [NoteFolder] = []
20+
var foldersPublisher: Published<[NoteFolder]>.Publisher { $folders }
21+
22+
init(apiClient: APIClient, userService: UserService) {
23+
self.apiClient = apiClient
24+
25+
userService.userPublisher.sink { [weak self] user in
26+
self?.handleUserChanged(user)
27+
}.store(in: &cancellableStorage)
28+
}
29+
30+
func reloadIfNeeded() {
31+
if lastReloadSuccess == false {
32+
reload(completion: nil)
33+
}
34+
}
35+
36+
func reload(completion: ((Result<[NoteFolder], Error>) -> Void)?) {
37+
apiClient.requestFolders { [weak self] result in
38+
guard let self = self else { return }
39+
if let folders = try? result.get() {
40+
self.lastReloadSuccess = true
41+
self.folders = folders
42+
} else {
43+
self.lastReloadSuccess = false
44+
}
45+
completion?(result.mapError({ $0 }))
46+
}
47+
}
48+
49+
func add(content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, Error>) -> Void) {
50+
apiClient.addFolder(content: content) { [weak self] result in
51+
completion(result.mapError({ $0 }))
52+
self?.handleEditFolders(result)
53+
}
54+
}
55+
56+
func remove(folderId: NoteFolder.Id, completion: @escaping (Result<Void, Error>) -> Void) {
57+
apiClient.removeFolder(folderId: folderId) { [weak self] result in
58+
completion(result.mapError({ $0 }))
59+
self?.handleEditFolders(result)
60+
}
61+
}
62+
63+
func edit(folderId: NoteFolder.Id, content: NoteFolder.Content, completion: @escaping (Result<NoteFolder, Error>) -> Void) {
64+
apiClient.editFolder(folderId: folderId, content: content) { [weak self] result in
65+
completion(result.mapError({ $0 }))
66+
self?.handleEditFolders(result)
67+
}
68+
}
69+
70+
// MARK: Private
71+
private func handleEditFolders<T>(_ result: Result<T, APIError>) {
72+
if case .success = result {
73+
lastReloadSuccess = false
74+
reload(completion: nil)
75+
}
76+
}
77+
78+
private func handleUserChanged(_ user: User?) {
79+
folders = []
80+
if user != nil {
81+
lastReloadSuccess = false
82+
reload(completion: nil)
83+
} else {
84+
lastReloadSuccess = true
85+
}
86+
}
87+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// NoteRecordEditService.swift
3+
// Example
4+
//
5+
// Created by Короткий Виталий on 30.11.2020.
6+
// Copyright © 2020 ProVir. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Combine
11+
12+
protocol NoteRecordEditService: class {
13+
var folder: NoteFolder { get }
14+
var record: NoteRecord? { get }
15+
var recordPublisher: Published<NoteRecord?>.Publisher { get }
16+
17+
func apply(content: NoteRecord.Content, completion: @escaping (Result<NoteRecord, Error>) -> Void)
18+
func remove(completion: @escaping (Result<Void, Error>) -> Void)
19+
}

0 commit comments

Comments
 (0)