Skip to content

Commit

Permalink
Added the DictionaryCachableEncoder
Browse files Browse the repository at this point in the history
  • Loading branch information
acecilia committed Apr 27, 2020
1 parent 360a8fc commit 78fbd0d
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 2 deletions.
45 changes: 45 additions & 0 deletions Sources/DictionaryCachableEncoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Foundation

/*
An encoder that, if the value to encode is hashable, caches the encoded result.
This is useful to speed up applications that have to encode the same hashable value numerous times
The cache key is calculated from the combination of:
1- The hashable value to enconde
2- The userInfo dictionary (note that you have to provide a closure to convert it to a hashable object)
*/
open class DictionaryCachableEncoder: DictionaryEncoder {
open var userInfoHasher: ([CodingUserInfoKey: Any]) -> AnyHashable = { _ in AnyHashable(0) }
open var cache: CacheProtocol = DefaultCache.shared

override func box<T: Encodable>(_ value: T) throws -> Any {
if let hashableValue = value as? AnyHashable {
let userInfoHash = userInfoHasher(userInfo)
let cacheKey = AnyHashable([hashableValue, userInfoHash])
if let cached = cache.storage[cacheKey] {
return cached
} else {
let container = try super.box(value)
cache.storage[cacheKey] = container
return container
}
} else {
return try super.box(value)
}
}
}

// MARK: cache related

public protocol CacheProtocol: class {
var storage: [AnyHashable: Any] { get set }
}

extension DictionaryCachableEncoder {
open class DefaultCache: CacheProtocol {
public static let shared = DefaultCache()

open var storage: [AnyHashable: Any] = [:]

public init() { }
}
}
4 changes: 2 additions & 2 deletions Sources/DictionaryEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
open class DictionaryEncoder: Encoder {
open var codingPath: [CodingKey] = []
open var userInfo: [CodingUserInfoKey: Any] = [:]
private var storage = Storage()
private(set) var storage = Storage()

public init() {}

Expand All @@ -27,7 +27,7 @@ open class DictionaryEncoder: Encoder {
return SingleValueContainer(encoder: self, codingPath: codingPath)
}

private func box<T: Encodable>(_ value: T) throws -> Any {
func box<T: Encodable>(_ value: T) throws -> Any {
try value.encode(to: self)
return storage.popContainer()
}
Expand Down
74 changes: 74 additions & 0 deletions Tests/DictionaryCachableEncoderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Foundation
import XCTest
import MoreCodable

class DictionaryCachableEncoderTests: XCTestCase {
struct HashableUser: Encodable, Hashable {
let name: String
let age: Int
}

let hashableUser = HashableUser(name: "Tatsuya Tanaka", age: 24)

let cache = DictionaryCachableEncoder.DefaultCache()

func buildEncoder() -> DictionaryCachableEncoder {
let encoder = DictionaryCachableEncoder()
encoder.cache = cache
return encoder
}

func testSimpleModel() throws {
// First
do {
XCTAssertEqual(cache.storage.count, 0)

let encoder = buildEncoder()
let dictionary = try encoder.encode(hashableUser)
XCTAssertEqual(hashableUser.name, dictionary["name"] as? String)
XCTAssertEqual(hashableUser.age, dictionary["age"] as? Int)
XCTAssertEqual(dictionary.keys.count, 2)

XCTAssertEqual(cache.storage.count, 1)
}

// Second
do {
XCTAssertEqual(cache.storage.count, 1)

let encoder = buildEncoder()
let dictionary = try encoder.encode(hashableUser)
XCTAssertEqual(hashableUser.name, dictionary["name"] as? String)
XCTAssertEqual(hashableUser.age, dictionary["age"] as? Int)
XCTAssertEqual(dictionary.keys.count, 2)

XCTAssertEqual(cache.storage.count, 1)
}
}

func testSimpleModelWithCustomUserInfo() throws {
let encoder = buildEncoder()
encoder.userInfoHasher = { _ in 0 }

do {
XCTAssertEqual(cache.storage.count, 0)
_ = try encoder.encode(hashableUser)
XCTAssertEqual(cache.storage.count, 1)
}

do {
XCTAssertEqual(cache.storage.count, 1)
_ = try encoder.encode(hashableUser)
XCTAssertEqual(cache.storage.count, 1)
}

encoder.userInfoHasher = { _ in 1 }

do {
XCTAssertEqual(cache.storage.count, 1)
_ = try encoder.encode(hashableUser)
XCTAssertEqual(cache.storage.count, 2)
}
}

}

0 comments on commit 78fbd0d

Please sign in to comment.