Skip to content
This repository has been archived by the owner on Aug 8, 2024. It is now read-only.

Commit

Permalink
Fix an issue when cache intsance reference was nil in some cases
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanlisovyi committed Jul 9, 2021
1 parent 497a318 commit 3c2bf25
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 70 deletions.
38 changes: 15 additions & 23 deletions Sources/Carlos/CacheLevels/Composed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ extension CacheLevel {
*/
public func compose<A: CacheLevel>(_ cache: A) -> BasicCache<A.KeyType, A.OutputType> where A.KeyType == KeyType, A.OutputType == OutputType {
BasicCache(
getClosure: { [weak self] key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return self.get(key)
getClosure: { key in
self.get(key)
.catch { _ -> AnyPublisher<OutputType, Error> in
Logger.log("Composed| error on getting value for key \(key) on cache \(String(describing: self)).", .info)

return cache.get(key)
.flatMap { value -> AnyPublisher<(OutputType, Void), Error> in
.flatMap { [weak self] value -> AnyPublisher<(OutputType, Void), Error> in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

let get = Just(value).setFailureType(to: Error.self)
let set = self.set(value, forKey: key)
return Publishers.Zip(get, set)
Expand All @@ -31,33 +31,25 @@ extension CacheLevel {
.eraseToAnyPublisher()
}.eraseToAnyPublisher()
},
setClosure: { [weak self] value, key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return Publishers.Zip(
setClosure: { value, key in
Publishers.Zip(
self.set(value, forKey: key),
cache.set(value, forKey: key)
)
.map { _ in () }
.eraseToAnyPublisher()
},
removeClosure: { [weak self] key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return Publishers.Zip(self.remove(key), cache.remove(key))
removeClosure: { key in
Publishers.Zip(self.remove(key), cache.remove(key))
.map { _ in () }
.eraseToAnyPublisher()
},
clearClosure: { [weak self] in
self?.clear()
clearClosure: {
self.clear()
cache.clear()
},
memoryClosure: { [weak self] in
self?.onMemoryWarning()
memoryClosure: {
self.onMemoryWarning()
cache.onMemoryWarning()
}
)
Expand Down
8 changes: 2 additions & 6 deletions Sources/Carlos/Core/FetcherValueTransformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ extension Fetcher {
*/
public func transformValues<A: OneWayTransformer>(_ transformer: A) -> BasicFetcher<KeyType, A.TypeOut> where OutputType == A.TypeIn {
BasicFetcher(
getClosure: { [weak self] key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return self.get(key)
getClosure: { key in
self.get(key)
.flatMap(transformer.transform)
.eraseToAnyPublisher()
}
Expand Down
24 changes: 6 additions & 18 deletions Sources/Carlos/Operations/KeyTransformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,20 @@ extension CacheLevel {
*/
public func transformKeys<A: OneWayTransformer>(_ transformer: A) -> BasicCache<A.TypeIn, OutputType> where KeyType == A.TypeOut {
BasicCache(
getClosure: { [weak self] key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return transformer.transform(key)
getClosure: { key in
transformer.transform(key)
.flatMap(self.get)
.eraseToAnyPublisher()
},
setClosure: { [weak self] value, key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return transformer.transform(key)
setClosure: { value, key in
transformer.transform(key)
.flatMap { transformedKey in
self.set(value, forKey: transformedKey)
}
.eraseToAnyPublisher()
},
removeClosure: { [weak self] in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return transformer.transform($0)
removeClosure: {
transformer.transform($0)
.flatMap(self.remove)
.eraseToAnyPublisher()
},
Expand Down
16 changes: 4 additions & 12 deletions Sources/Carlos/Operations/ValueTransformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,13 @@ extension CacheLevel {
*/
public func transformValues<A: TwoWayTransformer>(_ transformer: A) -> BasicCache<KeyType, A.TypeOut> where OutputType == A.TypeIn {
BasicCache(
getClosure: { [weak self] key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return self.get(key)
getClosure: { key in
self.get(key)
.flatMap(transformer.transform)
.eraseToAnyPublisher()
},
setClosure: { [weak self] value, key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return transformer.inverseTransform(value)
setClosure: { value, key in
transformer.inverseTransform(value)
.flatMap { transformedValue in
self.set(transformedValue, forKey: key)
}
Expand Down
14 changes: 3 additions & 11 deletions Sources/Carlos/Transformers/ConditionedValueTransformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,12 @@ extension CacheLevel {
*/
public func conditionedValueTransformation<A: ConditionedTwoWayTransformer>(transformer: A) -> BasicCache<KeyType, A.TypeOut> where OutputType == A.TypeIn, A.KeyType == KeyType {
BasicCache(
getClosure: { [weak self] key -> AnyPublisher<A.TypeOut, Error> in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

return self.get(key)
getClosure: { key -> AnyPublisher<A.TypeOut, Error> in
self.get(key)
.flatMap { transformer.conditionalTransform(key: key, value: $0) }
.eraseToAnyPublisher()
},
setClosure: { [weak self] value, key in
guard let self = self else {
return Empty(completeImmediately: true).eraseToAnyPublisher()
}

setClosure: { value, key in
return transformer.conditionalInverseTransform(key: key, value: value)
.flatMap { transformedValue in
self.set(transformedValue, forKey: key)
Expand Down
65 changes: 65 additions & 0 deletions Tests/CarlosTests/CompositionTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import XCTest

import Nimble
import Quick
Expand Down Expand Up @@ -752,3 +753,67 @@ final class CacheLevelCompositionTests: QuickSpec {
}
}
}

final class ComposedCacheTests: XCTestCase {
var cancellables: Set<AnyCancellable>!
var cache: BasicCache<String, NSData>!

override func setUp() {
super.setUp()

cancellables = Set()
cache = MemoryCacheLevel().compose(DiskCacheLevel<String, NSData>())
}

override func tearDown() {
cache.clear()

super.tearDown()
}

func testGet_whenKeyDoesNotExistInCache_shallCompleteWithError() {
let expectation = self.expectation(description: #function)

cache.get("does_not_exist")
.sink(receiveCompletion: { completion in
switch completion {
case let .failure(error):
XCTAssertEqual(error as! FetchError, FetchError.valueNotInCache)

expectation.fulfill()
case .finished:
XCTFail("Shall not finish")
}
}, receiveValue: { _ in
XCTFail("Shall not receive any value")
})
.store(in: &cancellables)

wait(for: [expectation], timeout: 1.0)
}

func testGet_whenKeyDoesExistInCache_shallReturnCachedValue() {
let expectation = self.expectation(description: #function)
let key = "does_exist"
let expectedValue = "value"

cache.set(expectedValue.data(using: .utf8)! as NSData, forKey: key)
.flatMap {
self.cache.get(key)
}
.sink(receiveCompletion: { completion in
switch completion {
case let .failure(error):
XCTFail("Shall not fail with error \(error)")
case .finished:
expectation.fulfill()
}
}, receiveValue: { value in
let resultValue = String(data: value as Data, encoding: .utf8)
XCTAssertEqual(resultValue, expectedValue)
})
.store(in: &cancellables)

wait(for: [expectation], timeout: 1.0)
}
}

0 comments on commit 3c2bf25

Please sign in to comment.