fix: code review notes
This commit is contained in:
parent
af044aa591
commit
56527b6dba
|
|
@ -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"
|
||||
|
|
@ -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<Deeplink>: DeeplinkHandler {
|
||||
private let handlerClosure: Closure<Deeplink, Operation?>
|
||||
|
||||
public init<Handler: DeeplinkHandler>(handler: Handler) where Handler.Deeplink == Deeplink {
|
||||
self.handlerClosure = handler.handle
|
||||
}
|
||||
|
||||
public func handle(deeplink: Deeplink) -> Operation? {
|
||||
handlerClosure(deeplink)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,15 +34,3 @@ public extension DeeplinkHandler {
|
|||
AnyDeeplinkHandler(handler: self)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnyDeeplinkHandler<Deeplink>: DeeplinkHandler {
|
||||
private let handlerClosure: Closure<Deeplink, Operation?>
|
||||
|
||||
public init<Handler: DeeplinkHandler>(handler: Handler) where Handler.Deeplink == Deeplink {
|
||||
self.handlerClosure = handler.handle
|
||||
}
|
||||
|
||||
public func handle(deeplink: Deeplink) -> Operation? {
|
||||
handlerClosure(deeplink)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Void, Never> { [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<ProjectDeeplink, MainCoordinator, ProjectDeeplinkMapper> {
|
||||
static let shared = ProjectDeeplinksService()
|
||||
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink> {
|
||||
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<ProjectDeeplink>(rootViewContro
|
|||
}
|
||||
|
||||
//: Далее handler может передаваться для использования в `TIDeeplinksService`
|
||||
let mapper = ProjectDeeplinkMapper()
|
||||
let service = TIDeeplinksService(mapper: mapper, handler: handler)
|
||||
|
||||
service.deferredHandle(url: url)
|
||||
|
|
|
|||
|
|
@ -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<Deeplink>: DeeplinkMapper {
|
||||
private let mappingClosure: Closure<URL, Deeplink?>
|
||||
|
||||
public init<Mapper: DeeplinkMapper>(mapper: Mapper) where Mapper.Deeplink == Deeplink {
|
||||
self.mappingClosure = mapper.map
|
||||
}
|
||||
|
||||
public func map(url: URL) -> Deeplink? {
|
||||
mappingClosure(url)
|
||||
}
|
||||
}
|
||||
|
|
@ -34,15 +34,3 @@ public extension DeeplinkMapper {
|
|||
AnyDeeplinkMapper(mapper: self)
|
||||
}
|
||||
}
|
||||
|
||||
public struct AnyDeeplinkMapper<Deeplink>: DeeplinkMapper {
|
||||
private let mappingClosure: Closure<URL, Deeplink?>
|
||||
|
||||
public init<Mapper: DeeplinkMapper>(mapper: Mapper) where Mapper.Deeplink == Deeplink {
|
||||
self.mappingClosure = mapper.map
|
||||
}
|
||||
|
||||
public func map(url: URL) -> Deeplink? {
|
||||
mappingClosure(url)
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,7 @@
|
|||
import Foundation
|
||||
import TIFoundationUtils
|
||||
|
||||
open class TIDeeplinksService<Deeplink: Hashable,
|
||||
Handler: DeeplinkHandler,
|
||||
Mapper: DeeplinkMapper> where Mapper.Deeplink == Deeplink, Handler.Deeplink == Deeplink {
|
||||
open class TIDeeplinksService<Deeplink: Hashable> {
|
||||
|
||||
// MARK: - Private properties
|
||||
|
||||
|
|
@ -41,7 +39,11 @@ open class TIDeeplinksService<Deeplink: Hashable,
|
|||
|
||||
// MARK: - Init
|
||||
|
||||
public init(mapper: Mapper? = nil, handler: Handler? = nil, operationQueue: OperationQueue = .main) {
|
||||
public init<Mapper: DeeplinkMapper, Handler: DeeplinkHandler>(
|
||||
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<Deeplink: Hashable,
|
|||
|
||||
/// Mapping the given url to deeplink model and appending it to the queue.
|
||||
/// - Parameter url: URL to map into deeplink model
|
||||
/// - Returns: true if a mapper mapped url to deeplink model successfully. Also returns false if `deeplinkMapper` is nil
|
||||
@discardableResult
|
||||
open func deferredHandle(url: URL) -> 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<Deeplink: Hashable,
|
|||
|
||||
inProcessingSet.formUnion([deeplink])
|
||||
|
||||
let competionOperation = BlockOperation { [weak self] in
|
||||
let completionOperation = BlockOperation { [weak self] in
|
||||
self?.inProcessingSet.subtract([deeplink])
|
||||
}
|
||||
|
||||
competionOperation.addDependency(operation)
|
||||
completionOperation.addDependency(operation)
|
||||
|
||||
competionOperation.add(to: operationQueue)
|
||||
completionOperation.add(to: operationQueue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true'/>
|
||||
<playground version='6.0' target-platform='ios' display-mode='raw' buildActiveScheme='true' executeOnSourceChanges='true'/>
|
||||
|
|
@ -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<VoidClosure> = { [weak self] completion in
|
||||
self?.openOfficesScreen()
|
||||
completion()
|
||||
}
|
||||
|
||||
return ClosureAsyncOperation<Void, Never> { [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<ProjectDeeplink, MainCoordinator, ProjectDeeplinkMapper> {
|
||||
static let shared = ProjectDeeplinksService()
|
||||
final class ProjectDeeplinksService: TIDeeplinksService<ProjectDeeplink> {
|
||||
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<ProjectDeeplink>(rootViewContro
|
|||
guard let projectHandler = $0 as? (any ProjectDeeplinkHandler) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return projectHandler.eraseToAnyDeeplinkHandler()
|
||||
}
|
||||
```
|
||||
|
|
@ -154,6 +176,7 @@ let handler = BaseNavigationStackDeeplinkHandler<ProjectDeeplink>(rootViewContro
|
|||
Далее handler может передаваться для использования в `TIDeeplinksService`
|
||||
|
||||
```swift
|
||||
let mapper = ProjectDeeplinkMapper()
|
||||
let service = TIDeeplinksService(mapper: mapper, handler: handler)
|
||||
|
||||
service.deferredHandle(url: url)
|
||||
|
|
|
|||
Loading…
Reference in New Issue