fix: code review notes

This commit is contained in:
Nikita Semenov 2023-04-12 13:02:20 +03:00
parent af044aa591
commit 56527b6dba
9 changed files with 171 additions and 76 deletions

7
TIDeepLink/.metadoc.yaml Normal file
View File

@ -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"

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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'/>

View File

@ -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)