From 56527b6dbaaba86ecf8bae4c9a6619cde1e23a0a Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 12 Apr 2023 13:02:20 +0300 Subject: [PATCH] fix: code review notes --- TIDeepLink/.metadoc.yaml | 7 ++ .../DeeplinkHandler/AnyDeeplinkHandler.swift | 36 +++++++++ .../DeeplinkHandler/DeeplinkHandler.swift | 12 --- .../Deeplinks.xcplaygroundpage/Contents.swift | 39 ++++++--- .../DeeplinkMapper/AnyDeeplinkMapper.swift | 36 +++++++++ .../DeeplinkMapper}/DeeplinkMapper.swift | 12 --- TIDeeplink/Sources/TIDeeplinkService.swift | 24 +++--- .../contents.xcplayground | 2 +- docs/tideeplink/deeplinks.md | 79 ++++++++++++------- 9 files changed, 171 insertions(+), 76 deletions(-) create mode 100644 TIDeepLink/.metadoc.yaml create mode 100644 TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift create mode 100644 TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift rename {TIDeepLink/Sources => TIDeeplink/Sources/DeeplinkMapper}/DeeplinkMapper.swift (80%) diff --git a/TIDeepLink/.metadoc.yaml b/TIDeepLink/.metadoc.yaml new file mode 100644 index 00000000..0f0425d7 --- /dev/null +++ b/TIDeepLink/.metadoc.yaml @@ -0,0 +1,7 @@ +api_data: + module_name: "TIDeeplink" + component_name: "TIDeeplinksService" + component_type: "base-service" # ui, base-service, default-service + example: "" + pull_request: "https://gitlab.ti/touchinstinct/LeadKit/-/merge_requests/13" + documentation_path: "../docs/tideeplink/deeplinks.md" diff --git a/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift new file mode 100644 index 00000000..78c0b741 --- /dev/null +++ b/TIDeepLink/Sources/DeeplinkHandler/AnyDeeplinkHandler.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public struct AnyDeeplinkHandler: DeeplinkHandler { + private let handlerClosure: Closure + + public init(handler: Handler) where Handler.Deeplink == Deeplink { + self.handlerClosure = handler.handle + } + + public func handle(deeplink: Deeplink) -> Operation? { + handlerClosure(deeplink) + } +} diff --git a/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift index 00177c37..e4a16bfa 100644 --- a/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift +++ b/TIDeepLink/Sources/DeeplinkHandler/DeeplinkHandler.swift @@ -34,15 +34,3 @@ public extension DeeplinkHandler { AnyDeeplinkHandler(handler: self) } } - -public struct AnyDeeplinkHandler: DeeplinkHandler { - private let handlerClosure: Closure - - public init(handler: Handler) where Handler.Deeplink == Deeplink { - self.handlerClosure = handler.handle - } - - public func handle(deeplink: Deeplink) -> Operation? { - handlerClosure(deeplink) - } -} diff --git a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift index 80178578..042866f0 100644 --- a/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift +++ b/TIDeepLink/TIDeeplink.app/Contents/MacOS/TIDeeplink.playground/Pages/Deeplinks.xcplaygroundpage/Contents.swift @@ -9,6 +9,8 @@ */ import Foundation import TIDeeplink +import TIFoundationUtils +import TISwiftUtils import UIKit enum ProjectDeeplink: Hashable { @@ -46,6 +48,10 @@ class MainCoordinator: DeeplinkHandler { print("Presenting edit profile view controller") } + func openOfficesScreen() { + print("Presenting offices view controller") + } + func openOfficesScreen(forId id: String) { print("Presenting offices view controller by id: \(id)") } @@ -60,8 +66,15 @@ class MainCoordinator: DeeplinkHandler { } case let .office(id): - return BlockOperation { [weak self] in - self?.openOfficesScreen(forId: id) + return ClosureAsyncOperation { [weak self] completion in + self?.openOfficesScreen() + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { + self?.openOfficesScreen(forId: id) + completion(.success(())) + } + + return Cancellables.nonCancellable() } } } @@ -70,21 +83,26 @@ class MainCoordinator: DeeplinkHandler { /*: **Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту */ - final class ProjectDeeplinksService: TIDeeplinksService { - static let shared = ProjectDeeplinksService() +final class ProjectDeeplinksService: TIDeeplinksService { + static let shared = ProjectDeeplinksService() + + convenience init(mapper: ProjectDeeplinkMapper = .init(), handler: MainCoordinator = .init()) { + self.init(mapper: mapper, handler: handler, operationQueue: .main) + } } /*: Создаем и передаем в сервис Mapper и Handler. > Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()` - */ -let coordinator = MainCoordinator() -let mapper = ProjectDeeplinkMapper() -ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper() -ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler() -/*: + ```swift + let coordinator = MainCoordinator() + let mapper = ProjectDeeplinkMapper() + ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper() + ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler() + ``` + В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков. - `deferredHandle(url:)`: пытается создать из _URL_ диплинк и добавить его в очередь на обработку @@ -138,6 +156,7 @@ let handler = BaseNavigationStackDeeplinkHandler(rootViewContro } //: Далее handler может передаваться для использования в `TIDeeplinksService` +let mapper = ProjectDeeplinkMapper() let service = TIDeeplinksService(mapper: mapper, handler: handler) service.deferredHandle(url: url) diff --git a/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift b/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift new file mode 100644 index 00000000..c2098093 --- /dev/null +++ b/TIDeeplink/Sources/DeeplinkMapper/AnyDeeplinkMapper.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2023 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation +import TISwiftUtils + +public struct AnyDeeplinkMapper: DeeplinkMapper { + private let mappingClosure: Closure + + public init(mapper: Mapper) where Mapper.Deeplink == Deeplink { + self.mappingClosure = mapper.map + } + + public func map(url: URL) -> Deeplink? { + mappingClosure(url) + } +} diff --git a/TIDeepLink/Sources/DeeplinkMapper.swift b/TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift similarity index 80% rename from TIDeepLink/Sources/DeeplinkMapper.swift rename to TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift index 2340f814..1e90c4c8 100644 --- a/TIDeepLink/Sources/DeeplinkMapper.swift +++ b/TIDeeplink/Sources/DeeplinkMapper/DeeplinkMapper.swift @@ -34,15 +34,3 @@ public extension DeeplinkMapper { AnyDeeplinkMapper(mapper: self) } } - -public struct AnyDeeplinkMapper: DeeplinkMapper { - private let mappingClosure: Closure - - public init(mapper: Mapper) where Mapper.Deeplink == Deeplink { - self.mappingClosure = mapper.map - } - - public func map(url: URL) -> Deeplink? { - mappingClosure(url) - } -} diff --git a/TIDeeplink/Sources/TIDeeplinkService.swift b/TIDeeplink/Sources/TIDeeplinkService.swift index 989d05e3..4ba7b069 100644 --- a/TIDeeplink/Sources/TIDeeplinkService.swift +++ b/TIDeeplink/Sources/TIDeeplinkService.swift @@ -23,9 +23,7 @@ import Foundation import TIFoundationUtils -open class TIDeeplinksService where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink { +open class TIDeeplinksService { // MARK: - Private properties @@ -41,7 +39,11 @@ open class TIDeeplinksService( + mapper: Mapper? = nil, + handler: Handler? = nil, + operationQueue: OperationQueue = .main) where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink { + self.deeplinkMapper = mapper?.eraseToAnyDeeplinkMapper() self.deeplinkHandler = handler?.eraseToAnyDeeplinkHandler() self.operationQueue = operationQueue @@ -51,18 +53,14 @@ open class TIDeeplinksService Bool { + open func deferredHandle(url: URL) { let deeplink = deeplinkMapper?.map(url: url) guard let deeplink = deeplink else { - return false + return } deeplinkQueue.append(deeplink) - - return true } open func reset() { @@ -86,12 +84,12 @@ open class TIDeeplinksService - \ No newline at end of file + \ No newline at end of file diff --git a/docs/tideeplink/deeplinks.md b/docs/tideeplink/deeplinks.md index badcc464..593b6ae2 100644 --- a/docs/tideeplink/deeplinks.md +++ b/docs/tideeplink/deeplinks.md @@ -1,15 +1,17 @@ # Deeplink API - + _TIDeeplink_ добавляет сервис `TIDeeplinksService` для обработки диплинков - + ## Как настроить - + 1. Создать объект, представляющий диплинк. Такой объект должен соответствовать протоколу `Hashable` ```swift import Foundation import TIDeeplink +import TIFoundationUtils +import TISwiftUtils import UIKit enum ProjectDeeplink: Hashable { @@ -26,18 +28,18 @@ final class ProjectDeeplinkMapper: DeeplinkMapper { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil } - + switch components.host { case "office": if let id = components.path.split(separator: "/").last { return .office(id: String(id)) } - + return nil - + case "editProfile": return .editProfile - + default: return nil } @@ -52,23 +54,39 @@ class MainCoordinator: DeeplinkHandler { func openEditProfileScreen() { print("Presenting edit profile view controller") } - + + func openOfficesScreen() { + print("Presenting offices view controller") + } + func openOfficesScreen(forId id: String) { print("Presenting offices view controller by id: \(id)") } - + // MARK: DeeplinkHandler - + func handle(deeplink: ProjectDeeplink) -> Operation? { switch deeplink { case .editProfile: return BlockOperation { [weak self] in self?.openEditProfileScreen() } - + case let .office(id): - return BlockOperation { [weak self] in - self?.openOfficesScreen(forId: id) + let presentClosure: ParameterClosure = { [weak self] completion in + self?.openOfficesScreen() + completion() + } + + return ClosureAsyncOperation { [weak self] completion in + presentClosure { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { + self?.openOfficesScreen(forId: id) + completion(.success(())) + } + } + + return Cancellables.nonCancellable() } } } @@ -78,24 +96,28 @@ class MainCoordinator: DeeplinkHandler { **Опционально** 4. Создать сабкласс `TIDeeplinksService` для удобного использования по проекту ```swift - final class ProjectDeeplinksService: TIDeeplinksService { - static let shared = ProjectDeeplinksService() +final class ProjectDeeplinksService: TIDeeplinksService { + static let shared = ProjectDeeplinksService() + + convenience init(mapper: ProjectDeeplinkMapper = .init(), handler: MainCoordinator = .init()) { + self.init(mapper: mapper, handler: handler, operationQueue: .main) + } } ``` Создаем и передаем в сервис Mapper и Handler. - + > Так же можно воспользоваться инициализатором `init(mapper:handler:operationQueue:)`. Если Mapper и Handler передаются не в инициализаторе, то их самостоятельно необходимо привести к виду `AnyDeeplinkHandler` и `AnyDeeplinkMapper` с момощью методов: `eraseToAnyDeeplinkHandler()` и `eraseToAnyDeeplinkMapper()` - -```swift -let coordinator = MainCoordinator() -let mapper = ProjectDeeplinkMapper() -ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper() -ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler() -``` - + + ```swift + let coordinator = MainCoordinator() + let mapper = ProjectDeeplinkMapper() + ProjectDeeplinksService.shared.deeplinkMapper = mapper.eraseToAnyDeeplinkMapper() + ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkHandler() + ``` + В `AppDelegate` использвуем методы `deferredHandle(url:)` и `handlePendingDeeplinks()` для обработки диплинков. - + - `deferredHandle(url:)`: пытается создать из _URL_ диплинк и добавить его в очередь на обработку - `handlePendingDeeplinks()`: обрабатывает первый в череди диплинк @@ -103,7 +125,7 @@ ProjectDeeplinksService.shared.deeplinkHandler = coordinator.eraseToAnyDeeplinkH func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { ProjectDeeplinksService.shared.deferredHandle(url: url) ProjectDeeplinksService.shared.handlePendingDeeplinks() - + return true } @@ -127,7 +149,7 @@ class ViewController: UIViewController, ProjectDeeplinkHandler { return BlockOperation { print("Presenting edit profile view controller") } - + case let .office(id): return BlockOperation { print("Presenting offices view controller by id: \(id)") @@ -146,7 +168,7 @@ let handler = BaseNavigationStackDeeplinkHandler(rootViewContro guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else { return nil } - + return projectHandler.eraseToAnyDeeplinkHandler() } ``` @@ -154,6 +176,7 @@ let handler = BaseNavigationStackDeeplinkHandler(rootViewContro Далее handler может передаваться для использования в `TIDeeplinksService` ```swift +let mapper = ProjectDeeplinkMapper() let service = TIDeeplinksService(mapper: mapper, handler: handler) service.deferredHandle(url: url)