diff --git a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/SingleValueCodableDefaultsStorage.swift b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/SingleValueCodableDefaultsStorage.swift index 2d9d2fc4..e0680bd0 100644 --- a/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/SingleValueCodableDefaultsStorage.swift +++ b/TIFoundationUtils/DataStorage/Sources/SingleValueStorage/Implementations/SingleValueCodableDefaultsStorage.swift @@ -22,8 +22,10 @@ import Foundation -open class SingleValueCodableDefaultsStorage: BaseSingleValueStorage { - public init(defaults: UserDefaults, +open class DefaultSingleValueCodableStorage: + BaseSingleValueStorage { + + public init(storage: CodableStorage, storageKey: StorageKey, decoder: CodableKeyValueDecoder, encoder: CodableKeyValueEncoder) { @@ -44,7 +46,7 @@ open class SingleValueCodableDefaultsStorage: BaseSingleValu $0.removeCodableValue(forKey: $1) } - super.init(storage: defaults, + super.init(storage: storage, storageKey: storageKey, hasValueClosure: hasValueClosure, deleteValueClosure: deleteValueClosure, diff --git a/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Pages/AsyncOperation.xcplaygroundpage/Contents.swift b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Pages/AsyncOperation.xcplaygroundpage/Contents.swift index 37a1769a..119761d9 100644 --- a/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Pages/AsyncOperation.xcplaygroundpage/Contents.swift +++ b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Pages/AsyncOperation.xcplaygroundpage/Contents.swift @@ -32,11 +32,12 @@ let intResultOperation = ClosureAsyncOperation { completion in /*: ## Базовые операторы - На данный момент реализовано всего два оператора: + На данный момент реализовано четыре оператора: - `map(mapOutput:mapFailure:)` - конвертирует ResultType в новый NewResultType и ErrorType в новый NewErrorType - `observe(onSuccess:onFailure)` - просто вызывает переданные callback'и при получении результата или ошибки - + - `flatMap(_:)` - подписывается на результат выполнения нового AsyncOperation полученного из closure + - `flatMapError(_:)` - подписывается на результат выполнения нового AsyncOperation полученного из closure */ //: ### Пример запуска асинхронных операци с применением операторов в последовательной очереди и вывод результатов @@ -75,3 +76,53 @@ ClosureAsyncOperation { completion in "Async operation two has finished with Success" ``` */ + +//: ### Пример использования оператора flatMap у AsyncOperaton + +import TISwiftUtils + +extension StorageKey { + static var loyaltyCardNumber: StorageKey { + .init(rawValue: "loyaltyCardNumber") + } +} + +struct CardService { + enum Failure: Error { + case noCardFound + case cardFetchError + } + + private let operationQueue = OperationQueue() + + private let asyncStorage = StringValueDefaultsStorage(defaults: .standard, storageKey: .loyaltyCardNumber) + .async(on: .global()) + + func requestCardTitle(cardNumber: String) -> AsyncOperation { + .just(success: "Supreme card") +// .just(failure: .cardFetchError) + } + + func getSavedCardTitle(completion: @escaping UIParameterClosure>) -> Cancellable { + ClosureAsyncOperation { completion in + asyncStorage.getValue { + completion($0.mapError { _ in + .noCardFound + }) + } + } + .flatMap { + requestCardTitle(cardNumber: $0) + } + .observe(onResult: completion) + .add(to: operationQueue) + } +} + +let cardService = CardService() + +cardService.getSavedCardTitle { result in + debugPrint(result) +} + +Nef.Playground.needsIndefiniteExecution(true) diff --git a/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Sources/NefPlaygroundSupport.swift b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Sources/NefPlaygroundSupport.swift new file mode 100644 index 00000000..2ffea80a --- /dev/null +++ b/TIFoundationUtils/TIFoundationUtils.app/Contents/MacOS/TIFoundationUtils.playground/Sources/NefPlaygroundSupport.swift @@ -0,0 +1,30 @@ +import UIKit + +public protocol NefPlaygroundLiveViewable {} +extension UIView: NefPlaygroundLiveViewable {} +extension UIViewController: NefPlaygroundLiveViewable {} + +#if NOT_IN_PLAYGROUND +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) {} + public static func needsIndefiniteExecution(_ state: Bool) {} + } +} + +#else +import PlaygroundSupport + +public enum Nef { + public enum Playground { + public static func liveView(_ view: NefPlaygroundLiveViewable) { + PlaygroundPage.current.liveView = (view as! PlaygroundLiveViewable) + } + + public static func needsIndefiniteExecution(_ state: Bool) { + PlaygroundPage.current.needsIndefiniteExecution = state + } + } +} + +#endif diff --git a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift index 8808b79f..e1a757c0 100644 --- a/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift +++ b/TIKeychainUtils/TIKeychainUtils.app/Contents/MacOS/TIKeychainUtils.playground/Pages/SingleValueStorage.xcplaygroundpage/Contents.swift @@ -71,3 +71,46 @@ if appInstallAwareTokenStorage.hasStoredValue() { // app was reinstalled or token is empty // ... } + +/*: + ### `SingleValueExpirationStorage` + + Класс позволяющий добавить дополнительную функциональность очистки значения по конкретному ключу + если истёк срок действия объекта (например refresh token'а) +*/ + +struct Token: Codable, Equatable { + var value: String + var expiration: Date + + init(value: String, expiration: Date) { + self.value = value + self.expiration = expiration + } +} + +extension StorageKey { + static var accessToken: StorageKey { + .init(rawValue: "accessToken") + } +} + +let accessTokenStorage = DefaultSingleValueCodableStorage(storage: keychain, + storageKey: .accessToken, + decoder: JSONKeyValueDecoder(), + encoder: JSONKeyValueEncoder()) + +let expirationCheckStorage = accessTokenStorage.isExpireCheck { $0.expiration.timeIntervalSinceNow > 0 } + +switch expirationCheckStorage.getValue() { +case let .success(token): + // use token + break +case let .failure(storageError) + if .valueNotFound = storageError { + // token is missing or expired, request new token + } else { + // handle storage error + } + break +} diff --git a/docs/tifoundationutils/asyncoperation.md b/docs/tifoundationutils/asyncoperation.md index 5a2175e1..9af67653 100644 --- a/docs/tifoundationutils/asyncoperation.md +++ b/docs/tifoundationutils/asyncoperation.md @@ -30,11 +30,12 @@ let intResultOperation = ClosureAsyncOperation { completion in ## Базовые операторы - На данный момент реализовано всего два оператора: + На данный момент реализовано четыре оператора: - `map(mapOutput:mapFailure:)` - конвертирует ResultType в новый NewResultType и ErrorType в новый NewErrorType - `observe(onSuccess:onFailure)` - просто вызывает переданные callback'и при получении результата или ошибки - + - `flatMap(_:)` - подписывается на результат выполнения нового AsyncOperation полученного из closure + - `flatMapError(_:)` - подписывается на результат выполнения нового AsyncOperation полученного из closure ### Пример запуска асинхронных операци с применением операторов в последовательной очереди и вывод результатов @@ -72,3 +73,55 @@ ClosureAsyncOperation { completion in "Async operation one has finished with 2" "Async operation two has finished with Success" ``` + +### Пример использования оператора flatMap у AsyncOperaton + +```swift +import TISwiftUtils + +extension StorageKey { + static var loyaltyCardNumber: StorageKey { + .init(rawValue: "loyaltyCardNumber") + } +} + +struct CardService { + enum Failure: Error { + case noCardFound + case cardFetchError + } + + private let operationQueue = OperationQueue() + + private let asyncStorage = StringValueDefaultsStorage(defaults: .standard, storageKey: .loyaltyCardNumber) + .async(on: .global()) + + func requestCardTitle(cardNumber: String) -> AsyncOperation { + .just(success: "Supreme card") +// .just(failure: .cardFetchError) + } + + func getSavedCardTitle(completion: @escaping UIParameterClosure>) -> Cancellable { + ClosureAsyncOperation { completion in + asyncStorage.getValue { + completion($0.mapError { _ in + .noCardFound + }) + } + } + .flatMap { + requestCardTitle(cardNumber: $0) + } + .observe(onResult: completion) + .add(to: operationQueue) + } +} + +let cardService = CardService() + +cardService.getSavedCardTitle { result in + debugPrint(result) +} + +Nef.Playground.needsIndefiniteExecution(true) +``` diff --git a/docs/tikeychainutils/singlevaluestorage.md b/docs/tikeychainutils/singlevaluestorage.md index 8ccfebe9..ac420aca 100644 --- a/docs/tikeychainutils/singlevaluestorage.md +++ b/docs/tikeychainutils/singlevaluestorage.md @@ -70,3 +70,46 @@ if appInstallAwareTokenStorage.hasStoredValue() { // ... } ``` + +### `SingleValueExpirationStorage` + + Класс позволяющий добавить дополнительную функциональность очистки значения по конкретному ключу + если истёк срок действия объекта (например refresh token'а) + +```swift +struct Token: Codable, Equatable { + var value: String + var expiration: Date + + init(value: String, expiration: Date) { + self.value = value + self.expiration = expiration + } +} + +extension StorageKey { + static var accessToken: StorageKey { + .init(rawValue: "accessToken") + } +} + +let accessTokenStorage = DefaultSingleValueCodableStorage(storage: keychain, + storageKey: .accessToken, + decoder: JSONKeyValueDecoder(), + encoder: JSONKeyValueEncoder()) + +let expirationCheckStorage = accessTokenStorage.isExpireCheck { $0.expiration.timeIntervalSinceNow > 0 } + +switch expirationCheckStorage.getValue() { +case let .success(token): + // use token + break +case let .failure(storageError) + if .valueNotFound = storageError { + // token is missing or expired, request new token + } else { + // handle storage error + } + break +} +```