测试已测试 | ✓ |
Lang语言 | SwiftSwift |
许可 | MIT |
发布最后发布 | 2016年12月 |
SwiftSwift版本 | 3.0 |
SPM支持SPM | ✗ |
由Vittorio Monaco,Cesar Vargas Casaseca,Ivan Lisovyi维护。
一个简单但灵活的缓存,使用Swift编写,用于
iOS 8+
和WatchOS 2
应用。
Carlos
是一组类和函数,用于在您的应用程序中实现自定义、灵活且强大的缓存层。
使用函数式编程词汇,Carlos构成了一个叠加缓存系统。您可以在这里或在这个视频中找到对其如何实现的最佳解释,感谢@bkase提供幻灯片。
默认情况下,Carlos
附带了内存缓存、磁盘缓存、简单的网络检索器和NSUserDefaults
缓存(磁盘缓存受HanekeSwift的启发)。
使用Carlos
,您可以
Carlos
已经提供了一些常见的值转换器Carlos
可以为您处理这些如果您不使用CocoaPods,仍然可以将Carlos
作为子模块添加,将Carlos.xcodeproj
拖放到您的项目中,并在您的目标中嵌入Carlos.framework
。
Carlos.xcodeproj
拖放到您的项目中Embedded binaries
部分点击+
按钮Carlos.framework
(对于WatchOS 2
应用程序,请使用CarlosWatch.framework
)您可以直接将所需文件拖放到您的项目中,但请注意,这样您将无法自动获得最新的所有Carlos
功能(例如,包括新操作的新文件)。
这些文件包含在Carlos
文件夹中。
如果您想将Carlos
集成到WatchOS 2应用程序中,请不要包含文件MemoryWarning.swift
。
我们提供了一个小型的Xcode playground与项目一起发布,这样您就可以快速看到Carlos
的工作方式,并与您自定义的层、层组合以及请求池化、限制等多种配置进行实验。
要使用我们的Playground,请按照以下步骤操作
Carlos.xcodeproj
Carlos
框架目标,以及一个64位平台(例如,iPhone 6
)⌘+B
构建目标Carlos.playground
要运行示例项目,请克隆存储库。
let cache = MemoryCacheLevel<String, NSData>().compose(DiskCacheLevel())
此行将生成一个缓存,它以 String
作为键,返回 NSData
值。在此缓存上为给定键设置值时,将在两个级别上设置它。在缓存上获取给定键的值时,将首先尝试从内存级别获取它,如果找不到一个,将询问磁盘级别。如果两个级别都没有值,则请求将失败。如果磁盘级别可以获取一个值,这将同时也在内存级别上设置,以便下次获取更快。
Carlos
包含一个 CacheProvider
类,以便标准缓存可以轻松访问。
CacheProvider.dataCache()
创建一个以 URL
作为键并返回 NSData
值的缓存CacheProvider.imageCache()
创建一个以 URL
作为键并返回 UIImage
值的缓存CacheProvider.JSONCache()
创建一个以 URL
作为键并返回 AnyObject
值的缓存(应根据您的应用程序安全地将它们转换成数组或字典)上述方法始终创建新的实例(因此,如果您两次调用 CacheProvider.imageCache()
,则不会返回相同的实例,即使因为使用了相同的磁盘文件夹,磁盘级别将有效共享,但这是一种副作用,不应依赖于它),您应该在您的应用程序层中负责保留结果。如果您始终想要相同的实例,您可以使用以下访问器
CacheProvider.sharedDataCache
以检索共享的数据缓存实例CacheProvider.sharedImageCache
以检索共享的图像缓存实例CacheProvider.sharedJSONCache
以检索共享的 JSON 缓存实例要从缓存获取值,请使用 get
方法。
cache.get("key")
.onSuccess { value in
print("I found \(value)!")
}
.onFailure { error in
print("An error occurred :( \(error)")
}
您还可以将请求存储在某个位置,然后向它附加多个 onSuccess
或 onFailure
监听器。
let request = cache.get("key")
request.onSuccess { value in
print("I found \(value)!")
}
[... somewhere else]
request.onSuccess { value in
print("I can read \(value), too!")
}
请求也可以使用 cancel()
方法取消,并且您可以通过在给定的请求上调用 onCancel
来通知此事件。
let request = cache.get(key).onCancel {
print(""Looks like somebody canceled this request!")
}
[... somewhere else]
request.cancel()
当缓存请求成功时,会调用所有监听器。 并且 即使在请求已经完成工作之后添加监听器,您仍然会收到回调。 在任何给定时间,请求仅处于 正在执行、成功、失败 或 已取消 中的一个状态,并且 不能多次失败、成功或取消。
如果您只是对请求完成时的任何情况都感兴趣,无论是成功、失败还是取消,您可以使用 onCompletion
。
request.onCompletion { result in
switch result {
case .success(let value):
print("Request succeeded with value \(value)")
case .error(let error):
print("Request failed with code \(error)")
case .cancelled:
print("This request has been canceled")
}
print("Nevertheless the request completed")
}
尽管如此,此缓存并不很有用。它永远不会 主动 获取值,只是将其存储起来以供以后使用。让我们尝试使其更吸引人
let cache = MemoryCacheLevel()
.compose(DiskCacheLevel())
.compose(NetworkFetcher())
这将创建一个缓存级别,它以 URL
作为键并存储 NSData
值(类型从 NetworkFetcher
对键和值的强制要求 URL
和 NSData
推断而出,而 MemoryCacheLevel
和 DiskCacheLevel
则更加灵活,如后续所述)。
键转换旨在使将缓存级别连接到您正在构建的任何缓存成为可能。
让我们看看它是如何工作的
// Define your custom ErrorType values
enum URLTransformationError: Error {
case invalidURLString
}
let transformedCache = NetworkFetcher().transformKeys(OneWayTransformationBox(transform: { Future(value: URL(string: $0), error: URLTransformationError.invalidURLString) }))
上面的行表示,所有进入 NetworkFetcher
级别的键都必须首先转换成 URL
值。现在我们可以将此缓存连接到一个之前定义的以 String
作为键的缓存级别。
let cache = MemoryCacheLevel<String, NSData>().compose(transformedCache)
如果这看起来并不太安全(人们总可以通过作为键传递字符串垃圾,而它不会神奇地翻译成 URL
,从而导致 NetworkFetcher
运行出错并以静默失败),我们仍然可以使用特定于域的结构作为键,假设它包含 String
和 URL
值
struct Image {
let identifier: String
let URL: Foundation.URL
}
let imageToString = OneWayTransformationBox(transform: { (image: Image) -> Future<String> in
Future(image.identifier)
})
let imageToURL = OneWayTransformationBox(transform: { (image: Image) -> Future<URL> in
Future(image.URL)
})
let memoryLevel = MemoryCacheLevel<String, NSData>().transformKeys(imageToString)
let diskLevel = DiskCacheLevel<String, NSData>().transformKeys(imageToString)
let networkLevel = NetworkFetcher().transformKeys(imageToURL)
let cache = memoryLevel.compose(diskLevel).compose(networkLevel)
现在我们可以安全地进行如下请求
let image = Image(identifier: "550e8400-e29b-41d4-a716-446655440000", URL: URL(string: "http://goo.gl/KcGz8T")!)
cache.get(image).onSuccess { value in
print("Found \(value)!")
}
自 Carlos 0.5
起您还可以将条件应用于用于键转换的 OneWayTransformers
。只需在转换器上调用 conditioned
函数并传递您的条件。条件也可以是异步的,并必须返回 Future<Bool>
,有机会在转换失败时返回一个特定的错误。
let transformer = OneWayTransformationBox<String, URL>(transform: { key in
Future(value: URL(string: key), error: MyError.stringIsNotURL)
}).conditioned { key in
Future(key.rangeOfString("http") != nil)
}
let cache = CacheProvider.imageCache().transformKeys(transformer)
但不止这些。
如果我们磁盘缓存仅存储 Data
,但我们想方便地将内存缓存中的实例存储为 UIImage
呢?
值转换器让您有一个缓存,例如存储 Data
并将其转换为存储 UIImage
值的缓存。让我们看看如何实现它
let dataTransformer = TwoWayTransformationBox(transform: { (image: UIImage) -> Future<Data> in
Future(UIImagePNGRepresentation(image))
}, inverseTransform: { (data: Data) -> Future<UIImage> in
Future(UIImage(data: data)!)
})
let memoryLevel = MemoryCacheLevel<String, UIImage>().transformKeys(imageToString).transformValues(dataTransformer)
内存级别现在可以替换我们之前的一个,区别在于它将内部存储 UIImage
值!
请注意,与键转换一样,如果您的转换闭包失败(无论是正向转换还是逆向转换),缓存级别将跳过,就像检索失败一样。对于 set
调用也适用相同考虑。
Carlos
内置一些值转换器,例如:
JSONTransformer
将 NSData
实例序列化为 JSONImageTransformer
将 NSData
实例序列化为 UIImage
值(在 Mac OS X 框架中不可用)StringTransformer
将 NSData
实例序列化为有给定编码的 String
值DateFormatter
、NumberFormatter
、MKDistanceFormatter
),以便您可以根据需要使用自定义实例。从 Carlos 0.4
开始,您可以只需使用 OneWayTransformer
(与必要的 TwoWayTransformer
相比,用于常规 CacheLevel
实例)将来自 Fetcher
实例的值进行转换。这是因为 Fetcher
协议不要求 set
)。这意味着您可以轻松地将从互联网获取的 JSON 的 HttpClient 作为 Fetcher 连接,并转换其输出到模型对象(例如 a struct
)成为复杂的缓存管道,而无需创建仅为满足 TwoWayTransformer
协议要求的虚拟反向转换。
从 Carlos 0.5
开始,所有转换器都原生支持异步计算,因此在您的自定义转换器中,昂贵的转换不会阻塞其他操作。实际上,内置的 ImageTransformer
在后台队列上处理图像转换。
从 Carlos 0.5
开始,您还可以将条件应用于用于值转换的 TwoWayTransformers
。只需在转换器上调用 conditioned
函数并传递您的条件(一个用于正向转换,一个用于逆向转换)。条件也可以是异步的,并必须返回 Future<Bool>
,有机会在转换失败时返回一个特定的错误。
let transformer = JSONTransformer().conditioned({ input in
Future(myCondition)
}, inverseCondition: { input in
Future(myCondition)
})
let cache = CacheProvider.dataCache().transformValues(transformer)
在某些情况下,您的缓存级别可能返回正确的值,但格式不理想。例如,您想要对 Cache 返回的整个输出进行清理,而不管确切的层是什么。
对于这些情况,从Carlos 0.4
中引入的postProcess
函数可能有所帮助。该函数作为CacheLevel
协议的协议扩展提供。
postProcess
函数接收一个CacheLevel
和一个参数为OneWayTransformer
且TypeIn == TypeOut
的函数,输出一个封装了后处理步骤的装饰性BasicCache
。
// Let's create a simple "to uppercase" transformer
let transformer = OneWayTransformationBox<NSString, String>(transform: { Future($0.uppercased() as String) })
// Our memory cache
let memoryCache = MemoryCacheLevel<String, NSString>()
// Our decorated cache
let transformedCache = memoryCache.postProcess(transformer)
// Lowercase value set on the memory layer
memoryCache.set("test String", forKey: "key")
// We get the lowercase value from the undecorated memory layer
memoryCache.get("key").onSuccess { value in
let x = value
}
// We get the uppercase value from the decorated cache, though
transformedCache.get("key").onSuccess { value in
let x = value
}
自Carlos 0.5
版本起,您还可以将条件应用于用于后处理转换的OneWayTransformers
。只需在转换器上调用conditioned
函数并传递您的条什。此条件也可以是异步的,必须返回一个Future
conditionedPostProcess
,但请注意,这还不支持使用OneWayTransformer
实例。
let processer = OneWayTransformationBox<NSData, NSData>(transform: { value in
Future(value: String(data: value as Data, encoding: .utf8)?.uppercased().data(using: .utf8) as NSData?, error: FetchError.conditionNotSatisfied)
}).conditioned { value in
Future(value.length < 1000)
}
let cache = CacheProvider.dataCache().postProcess(processer)
在简单输出后处理的情况下扩展,您还可以根据获取值时使用的键应用条件转换。
对于这些情况,从Carlos 0.6
引入的conditionedPostProcess
函数可能有所帮助。该函数作为CacheLevel
协议的协议扩展提供。
conditionedPostProcess
函数接收一个CacheLevel
和一个符合ConditionedOneWayTransformer
的装饰转换器作为参数,并输出一个封装了条件后处理步骤的装饰性CacheLevel
。
// Our memory cache
let memoryCache = MemoryCacheLevel<String, NSString>()
// Our decorated cache
let transformedCache = memoryCache.conditionedPostProcess(ConditionedOneWayTransformationBox(conditionalTransformClosure: { (key, value) in
if key == "some sentinel value" {
return Future(value.uppercased())
} else {
return Future(value)
}
})
// Lowercase value set on the memory layer
memoryCache.set("test String", forKey: "some sentinel value")
// We get the lowercase value from the undecorated memory layer
memoryCache.get("some sentinel value").onSuccess { value in
let x = value
}
// We get the uppercase value from the decorated cache, though
transformedCache.get("some sentinel value").onSuccess { value in
let x = value
}
在简单值转换的情况下扩展,您还可以根据获取或设置值时使用的键应用条件转换。
对于这些情况,从Carlos 0.6
引入的conditionedValueTransformation
函数可能有所帮助。该函数作为CacheLevel
协议的协议扩展提供。
conditionedValueTransformation
函数接收一个CacheLevel
和一个符合ConditionedTwoWayTransformer
的装饰转换器作为参数,并输出一个具有修改后的OutputType
(与正常值转换情况中的转换器TypeOut
相等)的装饰性CacheLevel
,其中封装了条件值转换步骤。
// Our memory cache
let memoryCache = MemoryCacheLevel<String, NSString>()
// Our decorated cache
let transformedCache = memoryCache.conditionedValueTransformation(ConditionedTwoWayTransformationBox(conditionalTransformClosure: { (key, value) in
if key == "some sentinel value" {
return Future(1)
} else {
return Future(0)
}
}, conditionalInverseTransformClosure: { (key, value) in
if key > 0 {
return Future("Positive")
} else {
return Future("Null or negative")
}
})
// Value set on the memory layer
memoryCache.set("test String", forKey: "some sentinel value")
// We get the same value from the undecorated memory layer
memoryCache.get("some sentinel value").onSuccess { value in
let x = value
}
// We get 1 from the decorated cache, though
transformedCache.get("some sentinel value").onSuccess { value in
let x = value
}
// We set "Positive" on the decorated cache
transformedCache.set(5, forKey: "test")
自Carlos 0.4
起,可以组合多个OneWayTransformer
对象。这样,可以创建多个转换模块来构建一个小型库,然后根据应用方便组合它们。
可以使用与正常CacheLevel
相同的方谜来组合转换器:使用compose
协议扩展。
let firstTransformer = ImageTransformer() // NSData -> UIImage
let secondTransformer = ImageTransformer().invert() // Trivial UIImage -> NSData
let identityTransformer = firstTransformer.compose(secondTransformer)
相同的做法可以应用于TwoWayTransformer
对象(顺便说一就要是,它已经是OneWayTransformer
)。
Carlos
将默认提供许多转换模块。
当您有一个正常运行的缓存,但某些级别(比如网络请求器或数据库请求器)成本较高时,您可能希望以某种方式将请求池化,使得对于同一个键的多个请求在其中一个请求完成之前合并,以便当一个请求完成时,所有其他请求也自动完成,无需实际多次执行昂贵的操作。
这种功能由Carlos
提供。
let cache = (memoryLevel.compose(diskLevel).compose(networkLevel)).pooled()
请注意,键必须遵循Hashable
协议,才能使pooled
函数正常工作。
extension Image: Hashable {
var hashValue: Int {
return identifier.hashValue
}
}
extension Image: Equatable {}
func ==(lhs: Image, rhs: Image) -> Bool {
return lhs.identifier == rhs.identifier && lhs.URL == rhs.URL
}
现在我们可以为同一Image
值执行多个获取操作,并确保仅启动一个网络请求。
自Carlos 0.7
起,您可以通过batchGetSome
将键列表传递给您的CacheLevel
。这返回一个Future
,当指定的所有键的请求都完成后将成功,但不一定是成功。尽管如此,您只需在成功回调和错误回调中获取成功值。
自Carlos 0.9
起,您可以通过allBatch
将您的CacheLevel
转换为接受键列表的形式。在这样一个CacheLevel
上调用get
将返回一个只有在所有指定的键的请求都成功时才会成功的Future
,而在任何一个指定的键的请求失败时立即失败。如果您取消由这个CacheLevel
返回的Future
,所有挂起的请求也将被取消。
使用示例
let cache = MemoryCacheLevel<String, Int>()
for iter in 0..<99 {
cache.set(iter, forKey: "key_\(iter)")
}
let keysToBatch = (0..<100).map { "key_\($0)" }
cache.batchGetSome().get(keysToBatch)
.onSuccess { values in
print("Got \(values.count) values in total")
}.onFailure {
print("Failed because \($0)")
}
在这种情况下,当只有一个请求时,allBatch().get
会失败,因为只有99个键已设置,最后一个请求将使整个批量失败并产生valueNotInCache
错误。而batchGetSome().get
将成功,并打印“总共获取99个值”。
因为allBatch
返回一个新的CacheLevel
实例,所以它可以像其他缓存一样组合或转换。
let cache = MemoryCacheLevel<String, Int>()
.allBatch()
.capRequests(3)
在这种情况下,cache
是一种接受字符串键序列的缓存,并返回一个包含整数的列表的Future
,但限制了3个并发请求(下一段中将有更多关于限制并发请求的信息)。
如果您想限制缓存级别可以接受的并发请求的数量,而不是依赖于键(否则请参阅请求池化部分),您可能需要查看capRequests
函数。
下面是在实际中的应用示例。
let myCache = MyFirstLevel().compose(MySecondLevel())
let cappedCache = myCache.capRequests(3)
cappedCache
现在将仅接受最多3个并发的get
操作。如果到达第4个请求,它将被排队,只有在执行中的一个请求完成后才会执行。当资源只能由有限数量的消费者同时访问时,并且建立到资源的另一个连接可能很昂贵或降低已执行请求的性能时,这可能有用。
有时我们可能有一些只在某些条件下才会查询的级别。比如,我们有一个DatabaseLevel
,只有当用户在应用中启用某个设置,且实际上开始将数据存储在数据库中时,才会被触发。我们可能希望避免在设置首先启用的情况下访问数据库。
let conditionedCache = cache.conditioned { key in
Future(appSettingIsEnabled)
}
闭包会得到缓存请求的键,并必须返回一个表示请求是否可以继续或跳过级别的Future<Bool>
对象,还有可能通过特定的Error
失败以将错误信息传递给调用者。
在运行时,如果变量 appSettingIsEnabled
的值为 false
,则会跳过该层级(或如果这是唯一的或最后的层级,则操作将失败)。如果为 true
,则会执行 get
请求。
从 Carlos 0.5
版本发布以来,可以通过 dispatch
协议扩展将给定 CacheLevel
的所有操作调度到特定的 GCD 队列中。
let queue = DispatchQueue(label: "com.vendor.customQueue", attributes: .concurrent)
let cache = CacheProvider.imageCache().dispatch(queue)
结果 CacheLevel
将在指定的队列上调度 get
,set
,onMemoryWarning
和 clear
操作。
如果您有一个复杂的场景,根据键或某些外部条件,要么使用一个缓存,要么使用另一个缓存,那么 switchLevels
函数可能很有用。
使用方法
let lane1 = MemoryCacheLevel<URL, NSData>() // The two lanes have to be equivalent (same key type, same value type).
let lane2 = CacheProvider.dataCache() // Keep in mind that you can always use key transformation or value transformations if two lanes don't match by default
let switched = switchLevels(lane1, lane2) { key in
if key.scheme == "http" {
return .cacheA
} else {
return .cacheB // The example is just meant to show how to return different lanes
}
}
现在,根据键 URL 的方案,将使用第一条通道或第二条通道。
如果我们在缓存层级中存储大对象,我们可能希望收到内存警告事件的通知。这就是 listenToMemoryWarnings
和 unsubscribeToMemoryWarnings
函数起作用的地方。
let token = cache.listenToMemoryWarnings()
稍后
unsubscribeToMemoryWarnings(token)
在第一个调用中,当出现内存警告时,缓存层级及其所有组成层级将收到对 onMemoryWarning
的调用。
在第二个调用中,该行为将停止。
请注意,此功能尚不支持 CarlosWatch.framework
框架的 WatchOS 2
。
如果您需要将多个 Carlos
组合调用的结果存储在属性中,将属性的类型设置为 BasicCache
可能会很烦琐,因为有些调用返回不同的类型(例如,PoolCache
,RequestCapperCache
)。在这种情况下,您可以在将缓存层级分配给属性之前对其 normalize
并将其转换为 BasicCache
值。
import Carlos
import PiedPiper
class CacheManager {
let cache: BasicCache<URL, NSData>
init(injectedCache: BasicCache<URL, NSData>) {
self.cache = injectedCache
}
}
[...]
let manager = CacheManager(injectedCache: CacheProvider.dataCache().pooled().capRequests(3)) // This won't compile
let manager = CacheManager(injectedCache: CacheProvider.dataCache().pooled().capRequests(3).normalize()) // This will
作为提示,始终在您需要将多个组合调用的结果分配给属性时使用 normalize
。如果值已经是 BasicCache
,则调用将不执行任何操作,因此在那种情况下不会损失性能。
创建自定义层级非常简单,并受到鼓励(毕竟,如果只需要内存、磁盘和网络功能,已经有多个缓存库可用!)
让我们看看如何做到这一点。
class MyLevel: CacheLevel {
typealias KeyType = Int
typealias OutputType = Float
func get(_ key: KeyType) -> Future<OutputType> {
let request = Promise<OutputType>()
// Perform the fetch and either succeed or fail
[...]
request.succeed(1.0)
return request.future
}
func set(_ value: OutputType, forKey key: KeyType) -> Future<()> {
let promise = Promise<OutputType>()
// Store the value (db, memory, file, etc) and call this on completion:
promise.succeed()
return promise.future
}
func clear() {
// Clear the stored values
}
func onMemoryWarning() {
// A memory warning event came. React appropriately
}
}
上面的类符合 CacheLevel
协议。首先,我们需要声明我们接受的键类型和返回的输出类型。在这个示例中,我们有 Int
键和 Float
输出值。
需要实现的方法有 4 个:get
,set
,clear
和 onMemoryWarning
。
get
需要返回一个 Future
,我们可以在方法体开始时创建一个 Promise
,并通过调用 future
返回其关联的 Future
。然后我们根据获取的结果通过调用其上的 succeed
或 fail
通知监听器。这些调用可以是(并且在大多数情况下将是)异步的。
set
需要返回一个表示操作结果的 Future
。它还必须存储给定键的给定值。
clear
表示删除缓存层级的意图。
onMemoryWarning
会在之前调用过 listenToMemoryWarning
方法的情况下,通知内存压力事件。
这个样例缓存现在可以被连接到其他缓存的列表中,如果需要的话,可以像前面段落中看到的那样转换其键或值。
你可以通过几种方式来创建一个 Promise
succeed
或 fail
,根据操作的结果来决定;记住在你的 return
语句中调用 future
在你的 Promise
上!
//#1
let result: Promise<String>()
[...]
result.succeed("success")
// or
result.fail(MyError.invalidData)
return result.future
//#2
return Future("success")
//#3
return Future(MyError.invalidData)
//#4
return Future<String>(value: optionalString, error: MyError.invalidData)
从 Carlos 0.4
版本开始,引入了 Fetcher
协议,使库的用户更容易创建自定义抓取器,这些抓取器可以用作缓存中的只读级别。一个“伪装成 Fetcher
”的例子在 Carlos
中一直存在,即 NetworkFetcher
:你只能用它来从网络读取,不能写入(set
,clear
和 onMemoryWarning
是 无操作)。
这就是现在实现你自己的自定义抓取器有多简单了
class CustomFetcher: Fetcher {
typealias KeyType = String
typealias OutputType = String
func get(_ key: KeyType) -> Future<OutputType> {
return Future("Found an hardcoded value :)")
}
}
当然,你仍然需要声明你的 CacheLevel
处理的 KeyType
和 OutputType
,但你只需要实现 get
。减少你的空白代码!
默认情况下,Carlos
提供了 3 个缓存级别
MemoryCacheLevel
DiskCacheLevel
NetworkFetcher
0.5
版本开始,增加了 UserDefaultsCacheLevel
MemoryCacheLevel 是一个易失性缓存,在内部使用 NSCache
实例存储其值。可以通过初始化器指定容量,并且支持在内存压力下清除(如果级别已订阅了内存警告通知)。它接受符合 StringConvertible
协议的任何类型的键,可以存储符合 ExpensiveObject
协议的任何类型的值。在默认情况下,Data
,NSData
,String
,NSString
,UIImage
,URL
已经符合后者协议,而 String
,NSString
和 URL
符合 StringConvertible
协议。这个缓存级别是线程安全的。
DiskCacheLevel 是一个持久性缓存,将其值异步存储在磁盘上。可以通过初始化器指定容量,以确保磁盘大小不会太大。它接受符合 StringConvertible
协议的任何类型的键,可以存储符合 NSCoding
协议的任何类型的值。这个缓存级别是线程安全的,并且目前是唯一在调用 set
时可能会失败的 CacheLevel
,会返回 DiskCacheLevelError.diskArchiveWriteFailed
错误。
NetworkFetcher 是一个缓存级别,它异步通过网络获取值。它接受 URL
键并返回 NSData
值。这个缓存级别是线程安全的。
NSUserDefaultsCacheLevel 是一个持久性缓存,它将值存储在具有特定名称的 UserDefaults
持久域中。它接受符合 StringConvertible
协议的任何类型的键,可以存储符合 NSCoding
协议的任何类型的值。它使用内部软缓存来避免频繁击中持久存储,并且可以在不影响保存在 standardUserDefaults
或其他持久域中的其他值的情况下进行清除。这个缓存级别是线程安全的。
在决定如何处理Carlos中的登录问题时,我们选择了最灵活的方法,这种方法不需要我们编写完整的日志框架代码,即能够插入自己的日志库。如果你想使Carlos的输出仅当超过给定级别时才打印,如果你想完全在发布构建中静默它,或者你想将其路由到文件,或者任何其他情况:只需将你的日志处理闭包分配给Carlos.Logger.output
Carlos.Logger.output = { message, level in
myLibrary.log(message) //Plug here your logging library
}
我们在生产中使用了XCGLogger,并能够轻松地在调试构建中进行日志记录。
Carlos
经过彻底测试,以确保它设计提供的功能在重构时安全,尽可能无错误。
我们使用Quick和Nimble而不是XCTest
,以便拥有良好的BDD测试布局。
到目前为止,为Carlos
编写了大约1000项测试(请参阅Tests
文件夹),整体而言,测试代码库的大小是生产代码库的两倍。
Carlos
正在开发中,您可以在这里查看所有开放的问题。它们被分配到里程碑,以便您可以了解特定功能何时将发货。
如果您想为此仓库做出贡献,请
正在使用Carlos?请通过拉取请求联系我们,我们将很高兴提及您的应用!
Carlos
由WeltN24内部制作。
维托里奥·莫内科,[email protected],在Github上的@vittoriom,在Twitter上的@Vittorio_Monaco
艾萨德·哈吉德雷达奇克,@esad
Carlos
可在MIT许可证下获得。有关更多信息,请参阅LICENSE文件。
Carlos
内部使用
类强化的灵感来自Haneke。源代码已大幅修改,但采用原始文件对Carlos
的发展已被证明非常有价值。