├── .DS_Store ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SwiftlyCache.podspec ├── SwiftlyCache ├── CacheAware.swift ├── DiskCache.swift ├── DiskStorage.swift ├── MemoryCache.swift ├── MemoryStorage.swift └── MultiCache.swift ├── SwiftlyCacheDemo ├── Podfile ├── SwiftlyCacheDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── SwiftlyCacheDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── SwiftlyCacheDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.swift │ └── ViewController.swift └── frameworks ├── Info.plist └── SwiftlyCache.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings └── xcshareddata └── xcschemes └── SwiftlyCache.xcscheme /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hlc0000/SwiftlyCache/7b52359b646b9a6c5e024bf445e2cad182106891/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode11.3 2 | language: objective-c 3 | script: 4 | - | 5 | xcodebuild test \ 6 | -project frameworks/SwiftlyCache.xcodeproj \ 7 | -scheme SwiftlyCache \ 8 | -sdk iphonesimulator \ 9 | -destination 'platform=iOS Simulator,name=iPhone X,OS=latest' \ 10 | | xcpretty -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 hlc0000 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftlyCache 2 | 3 | [![Build Status](https://travis-ci.org/hlc0000/SwiftlyCache.svg?branch=master)](https://travis-ci.org/hlc0000/SwiftlyCache) 4 | ![](https://img.shields.io/cocoapods/p/SwiftlyCache.svg?style=flat) 5 | ![](https://img.shields.io/cocoapods/v/SwiftlyCache.svg?style=flat) 6 | 7 | `Swiftlycache` is a thread safe IOS general cache library written with `swift 5`. 8 | 9 | Features 10 | =============== 11 | 12 | - Support all data types complying with the `codable` protocol 13 | - Objects can be evicted with least-recently-used algorithm 14 | - It can be configured to automatically recycle objects or manually recycle objects when receiving memory warnings or when the application enters the background 15 | - Using subscript can make reading and writing data more convenient 16 | - Support reading data using sequence generator 17 | 18 | Installation 19 | ============== 20 | CocoaPods 21 | ------------------------------- 22 | 1.Add `pod 'SwiftlyCache'` to your Podfile 23 | 2.Run `pod install` or `pod update` 24 | 3.import `SwiftlyCache` 25 | 26 | Manually 27 | ------------------------------- 28 | 1.Download all the files in the `SwiftlyCache` subdirectory 29 | 2.Add the source files to your Xcode project 30 | 31 | Example: 32 | ------------------------------- 33 | Cache a struct complying with the `codable` protocol 34 | 35 | ``` 36 | struct Student:Codable { 37 | var name:String 38 | var age:Int 39 | 40 | init(name:String,age:Int) { 41 | self.name = name 42 | self.age = age 43 | } 44 | } 45 | ``` 46 | ``` 47 | let cache = MultiCache() 48 | 49 | let shirley = Student(name: "shirley", age: 30) 50 | 51 | ``` 52 | 53 | Set `key` and `value` to be cached 54 | 55 | ``` 56 | cache.set(forKey: "shirley10", value: shirley) 57 | 58 | ``` 59 | 60 | Query the corresponding value according to the given `key` 61 | 62 | ``` 63 | if let object = cache.object(forKey: "shirley1"){ 64 | print("当前Student是:\(object)") 65 | } 66 | ``` 67 | 68 | Query whether the corresponding `value` exists in the cache according to `key` 69 | 70 | ``` 71 | 72 | let isExists = cache.isExistsObjectForKey(forKey: "shirley20") 73 | 74 | ``` 75 | See `swiftlycachedemo` for more test code and cases 76 | 77 |

78 | --- 79 | 80 | 中文介绍 81 | ============================ 82 | 83 | `SwiftlyCache`是用 `Swift 5`编写的一个线程安全的iOS通用缓存库。 84 | 85 | 特性: 86 | ============== 87 | 88 | - 支持所有遵守 `Codable`协议的数据类型 89 | - 支持LRU淘汰算法 90 | - 当收到内存警告或者App进入后台时,内存缓存可以配置为自动清空或者手动清空 91 | - 支持使用 `Subscript`,使读写数据更加方便 92 | - 提供了 `MultiCacheGennerator、` `MemoryCacheGenerator、` `DiskCacheGenerator`用于支持 `for..in、` 93 | `compactMap、` `map、` `filter`等方法 94 | 95 | 使用方法: 96 | ============= 97 | CocoaPods: 98 | ------------------------------ 99 | 1.在Podfile中添加 `pod 'SwiftlyCache'` 100 | 2.执行 `pod install`或者 `pod update` 101 | 3.导入 `SwiftlyCache` 102 | 103 | 手动导入: 104 | ------------------------------ 105 | 1.下载 `SwiftlyCache`文件夹内所有内容 106 | 2.将 `SwiftlyCache`内的源文件添加到你的工程 107 | 108 | 示例: 109 | ------------------------------ 110 | 将一个遵守`Codable`协议的struct进行缓存 111 | 112 | ``` 113 | struct Student:Codable { 114 | var name:String 115 | var age:Int 116 | 117 | init(name:String,age:Int) { 118 | self.name = name 119 | self.age = age 120 | } 121 | } 122 | ``` 123 | ``` 124 | let cache = MultiCache() 125 | 126 | let shirley = Student(name: "shirley", age: 30) 127 | 128 | ``` 129 | 130 | 设置需要缓存的`Key`和`Value` 131 | 132 | ``` 133 | cache.set(forKey: "shirley10", value: shirley) 134 | 135 | ``` 136 | 137 | 根据给定的`Key`查询对应的Value 138 | 139 | ``` 140 | if let object = cache.object(forKey: "shirley1"){ 141 | print("当前Student是:\(object)") 142 | } 143 | ``` 144 | 145 | 根据`Key`查询缓存中是否存在对应的`Value` 146 | 147 | ``` 148 | 149 | let isExists = cache.isExistsObjectForKey(forKey: "shirley20") 150 | 151 | ``` 152 | 153 | 更多测试代码和用例见 `SwiftlyCacheDemo` 154 | 155 | 相关链接: 156 | ============== 157 | [SwiftlyCache实现](https://juejin.im/post/5e7084886fb9a07c7b784f7f) 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /SwiftlyCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SwiftlyCache" 4 | s.version = "1.1.8" 5 | s.summary = "SwiftlyCache is a thread safe IOS general cache Library" 6 | s.description = "SwiftlyCache is a thread safe IOS general cache Library" 7 | s.homepage = "https://github.com/hlc0000/SwiftlyCache" 8 | s.license = { :type => "MIT", :file=> "LICENSE" } 9 | s.author = { "LC" => "hlc8902@163.com" } 10 | s.platform = :ios, "9.0" 11 | s.source = { :git => "https://github.com/hlc0000/SwiftlyCache.git", :tag => "#{s.version}" } 12 | s.source_files = "SwiftlyCache/*.swift" 13 | s.requires_arc = true 14 | s.swift_version = "5" 15 | 16 | end -------------------------------------------------------------------------------- /SwiftlyCache/CacheAware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CacheAware.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2020/2/10. 6 | // Copyright © 2020 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CacheAware { 12 | associatedtype Value 13 | 14 | func set(forKey key:String,value:Value?,cost:vm_size_t)->Bool 15 | func set(forKey key:String,value:Value?,cost:vm_size_t,completionHandler:@escaping((_ key:String,_ finished:Bool) -> Void)) 16 | 17 | func object(forKey key:String)->Value? 18 | func object(forKey key:String,completionHandler:@escaping((_ key:String,_ value:Value?) -> Void)) 19 | 20 | func isExistsObject(forKey key:String)->Bool 21 | func isExistsObject(forKey key:String,completionHandler:@escaping((_ key:String,_ contain:Bool) -> Void)) 22 | 23 | func removeAll() 24 | func removeAll(completionHandler:@escaping(() -> Void)) 25 | 26 | func removeObject(forKey key:String) 27 | func removeObject(forKey key:String,completionHandler:@escaping(() -> Void)) 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /SwiftlyCache/DiskCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiskCache.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2019/12/27. 6 | // Copyright © 2019 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /** 13 | 支持for...in循环 14 | */ 15 | public class DiskCacheGenerator:IteratorProtocol{ 16 | public typealias Element = (key:String,object:Value) 17 | 18 | private let diskCache:DiskCache 19 | 20 | var index:Int 21 | 22 | public func next() -> Element? { 23 | if index == 0{ diskCache.getAllKey() } 24 | guard index < diskCache.keys.endIndex else { 25 | index = diskCache.keys.startIndex 26 | return nil 27 | } 28 | let key = diskCache.keys[index] 29 | diskCache.keys.formIndex(after: &index) 30 | if let element = diskCache.object(forKey: key){ 31 | return (key,element) 32 | } 33 | return nil 34 | } 35 | fileprivate init(diskCache:DiskCache) { 36 | self.diskCache = diskCache 37 | self.index = self.diskCache.keys.startIndex 38 | } 39 | } 40 | 41 | public class ConvertibleFactory{ 42 | public func toData(value:Value)throws -> Data?{ 43 | let data = try? JSONEncoder().encode(value) 44 | return data 45 | } 46 | 47 | public func fromData(data:Data)throws -> Value?{ 48 | let object = try? JSONDecoder().decode(Value.self, from: data) 49 | return object 50 | } 51 | } 52 | 53 | private let cacheIdentifier: String = "com.swiftcache.disk" 54 | public class DiskCache{ 55 | /** 56 | 设置最大的磁盘缓存容量(0为不限制) 57 | */ 58 | public var maxSize:vm_size_t = 0 59 | 60 | /** 61 | 设置最大的磁盘缓存数量 62 | */ 63 | public var maxCountLimit:vm_size_t = 0 64 | /** 65 | 缓存的过期时间(默认是一周) 66 | */ 67 | public var maxCachePeriodInSecond:TimeInterval = 60 * 60 * 24 * 7 68 | 69 | fileprivate let storage:DiskStorage 70 | 71 | private let semaphoreSignal = DispatchSemaphore(value: 1) 72 | 73 | private let convertible:ConvertibleFactory = ConvertibleFactory() 74 | 75 | private let dataMaxSize = 20 * 1024 76 | 77 | public var autoInterval:TimeInterval = 120 78 | 79 | var keys = [String]() 80 | 81 | private let queue: DispatchQueue = DispatchQueue(label: cacheIdentifier, attributes: DispatchQueue.Attributes.concurrent) 82 | 83 | public init(path:String) { 84 | storage = DiskStorage(currentPath: path) 85 | recursively() 86 | } 87 | 88 | private func recursively(){ 89 | DispatchQueue.global().asyncAfter(deadline: .now() + autoInterval) {[weak self] in 90 | guard let strongSelf = self else { return } 91 | strongSelf.discardedData() 92 | strongSelf.recursively() 93 | } 94 | } 95 | 96 | private func discardedData(){ 97 | queue.async { 98 | self.semaphoreSignal.wait() 99 | self.discardedToCost() 100 | self.discardedToCount() 101 | self.removeExpired() 102 | self.semaphoreSignal.signal() 103 | } 104 | } 105 | 106 | /** 107 | 超过限定张数,需要丢弃一部分内容 108 | */ 109 | private func discardedToCount(){ 110 | if maxCountLimit == 0{ return } 111 | var totalCount = storage.dbTotalItemCount() 112 | if totalCount <= maxCountLimit{ return } 113 | var fin = false 114 | repeat{ 115 | let items = storage.dbGetSizeExceededValues() 116 | for item in items{ 117 | if totalCount > maxCountLimit{ 118 | if let filename = item?.filename{ 119 | if storage.removeFile(filename: filename){ 120 | if let key = item?.key{ fin = storage.dbRemoveItem(key: key) } 121 | } 122 | }else if let key = item?.key{ fin = storage.dbRemoveItem(key: key )} 123 | if fin{ totalCount -= 1 } 124 | else { break } 125 | }else{ break } 126 | } 127 | 128 | }while totalCount > maxCountLimit 129 | if fin{ storage.dbCheckpoint() } 130 | } 131 | 132 | /** 133 | 超过限定容量,需要丢弃一部分内容 134 | */ 135 | private func discardedToCost(){ 136 | if maxSize == 0{ return } 137 | var totalCost = storage.dbTotalItemSize() 138 | if totalCost < maxSize{ return } 139 | var fin = false 140 | repeat{ 141 | let items = storage.dbGetSizeExceededValues() 142 | for item in items{ 143 | if totalCost > maxSize{ 144 | if let filename = item?.filename{ 145 | if storage.removeFile(filename: filename){ 146 | if let key = item?.key{ fin = storage.dbRemoveItem(key: key) } 147 | } 148 | }else if let key = item?.key{ fin = storage.dbRemoveItem(key: key) } 149 | if fin{ totalCost -= item!.size } 150 | else { break } 151 | }else{ break } 152 | } 153 | }while totalCost > maxSize 154 | if fin{ storage.dbCheckpoint() } 155 | } 156 | 157 | /** 158 | 移除过期数据 159 | @return 移除成功,返回true,否则返回false 160 | */ 161 | @discardableResult 162 | private func removeExpired()->Bool{ 163 | var currentTime = Date().timeIntervalSince1970 164 | currentTime -= maxCachePeriodInSecond 165 | let fin = storage.dbRemoveAllExpiredData(time: currentTime) 166 | return fin 167 | } 168 | 169 | @discardableResult 170 | public func removeAllExpired()->Bool{ 171 | semaphoreSignal.wait() 172 | let fin = removeExpired() 173 | semaphoreSignal.signal() 174 | return fin 175 | } 176 | } 177 | 178 | extension DiskCache:Sequence{ 179 | /** 180 | 通过下标方式set和get 181 | @param key: value关联的键 182 | @return Value:根据key查询对应的value,如果查询到则返回对应value,否则返回nil 183 | */ 184 | public subscript(key:String) ->Value?{ 185 | set{ 186 | if let newValue = newValue{ set(forKey: key, value: newValue) } 187 | }get{ 188 | if let object = object(forKey: key){ return object } 189 | return nil 190 | } 191 | } 192 | 193 | /** 194 | 返回该序列元素迭代器 195 | */ 196 | public func makeIterator() -> DiskCacheGenerator { 197 | semaphoreSignal.wait() 198 | let generator = DiskCacheGenerator(diskCache: self) 199 | semaphoreSignal.signal() 200 | return generator 201 | } 202 | } 203 | 204 | extension DiskCache:CacheAware{ 205 | 206 | /** 207 | 设置需要缓存的key和value 208 | @param key: 与value关联的键 209 | @param value: 需要缓存的对象,如果为nil,则直接返回false 210 | @cost: 缓存对象所占用的字节(默认为0,在磁盘缓存中此参数暂不使用) 211 | @return 缓存成功则返回true,否则返回false 212 | */ 213 | @discardableResult 214 | public func set(forKey key: String, value: Value?, cost: vm_size_t = 0)->Bool{ 215 | guard let object = value else { return false } 216 | guard let encodedData = try? convertible.toData(value: object) else{ return false } 217 | var filename:String? = nil 218 | if encodedData.count > dataMaxSize{ 219 | filename = storage.makeFileName(forKey: key) 220 | } 221 | semaphoreSignal.wait() 222 | let fin = storage.save(forKey: key, value: encodedData,filename: filename) 223 | semaphoreSignal.signal() 224 | return fin 225 | } 226 | 227 | /** 228 | 设置需要缓存的key和value 229 | @param key: 与value关联的键 230 | @param value: 需要缓存的对象,如果为nil,则直接返回false 231 | @cost: 缓存对象所占用的字节(默认为0,在磁盘缓存中此参数暂不使用) 232 | @param completionHandler: 缓存数据写入完成回调 233 | */ 234 | public func set(forKey key:String,value:Value?,cost:vm_size_t = 0,completionHandler:@escaping((_ key:String,_ finished:Bool) -> Void)){ 235 | queue.async { 236 | let fin = self.set(forKey: key, value: value,cost: cost) 237 | completionHandler(key,fin) 238 | } 239 | } 240 | 241 | /** 242 | 根据key查询对应的value 243 | @param key: 与value关联的键 244 | @return 返回与key关联的value,如果没有与key对应的value,返回nil 245 | */ 246 | public func object(forKey key: String) -> Value? { 247 | semaphoreSignal.wait() 248 | let item = storage.dbGetItemForKey(forKey: key) 249 | semaphoreSignal.signal() 250 | guard let value = item?.data else{ return nil } 251 | return try? convertible.fromData(data: value) 252 | } 253 | 254 | /** 255 | 根据key查询对应的value 256 | @param key: 与value关联的键 257 | @param completionHandler 查询完成回调 258 | */ 259 | public func object(forKey key:String,completionHandler:@escaping((_ key:String,_ value:Value?) -> Void)){ 260 | queue.async { 261 | if let object = self.object(forKey: key){ completionHandler(key,object) } 262 | else { completionHandler(key,nil) } 263 | } 264 | } 265 | 266 | func getAllKey(){ 267 | semaphoreSignal.wait() 268 | keys = storage.dbGetAllkey() 269 | semaphoreSignal.signal() 270 | } 271 | 272 | /** 273 | 获取存储总的数量 274 | @return 返回存储总的数量 275 | */ 276 | public func getTotalItemCount()->Int{ 277 | semaphoreSignal.wait() 278 | let count = storage.dbTotalItemCount() 279 | semaphoreSignal.signal() 280 | return count 281 | } 282 | 283 | /** 284 | 获取存储总的数量 285 | @param completionHandler 查询存储数量成功回调 286 | */ 287 | public func getTotalItemCount(completionHandler:@escaping((_ count:Int)->Void)){ 288 | queue.async { 289 | let count = self.getTotalItemCount() 290 | completionHandler(count) 291 | } 292 | } 293 | 294 | /** 295 | 获取磁盘数据占用容量 296 | @return 返回磁盘数据占用容量 297 | */ 298 | public func getTotalItemSize()->Int32{ 299 | self.semaphoreSignal.wait() 300 | let size = storage.dbTotalItemSize() 301 | self.semaphoreSignal.signal() 302 | return size 303 | } 304 | 305 | /** 306 | 获取数据占用容量 307 | @param completionHandler: 查询存储数量成功回调 308 | */ 309 | public func getTotalItemSize(completionHandler:@escaping((_ size:Int32)->Void)){ 310 | queue.async { 311 | let size = self.getTotalItemSize() 312 | completionHandler(size) 313 | } 314 | } 315 | 316 | /** 317 | 根据key查询缓存中是否存在对应的value 318 | @return 如果缓存中存在与key对应的value,返回true,否则返回false 319 | */ 320 | public func isExistsObject(forKey key: String) -> Bool { 321 | semaphoreSignal.wait() 322 | let exists = self.storage.dbIsExistsForKey(forKey: key) 323 | semaphoreSignal.signal() 324 | return exists 325 | } 326 | 327 | /** 328 | 根据key查询缓存中是否存在对应的value 329 | @param completionHandler: 查询完成后回调 330 | */ 331 | public func isExistsObject(forKey key:String,completionHandler:@escaping((_ key:String,_ contain:Bool) -> Void)) { 332 | queue.async { 333 | let exists = self.isExistsObject(forKey: key) 334 | completionHandler(key,exists) 335 | } 336 | } 337 | 338 | /** 339 | 移除所有缓存 340 | */ 341 | public func removeAll(){ 342 | semaphoreSignal.wait() 343 | storage.removeAll() 344 | semaphoreSignal.signal() 345 | } 346 | 347 | /** 348 | 移除所有缓存 349 | @param completionHandler 移除完成后回调 350 | */ 351 | public func removeAll(completionHandler: @escaping (() -> Void)) { 352 | queue.async { 353 | self.removeAll() 354 | completionHandler() 355 | } 356 | } 357 | 358 | /** 359 | 移除所有缓存 360 | @param completionHandler 移除完成后回调 361 | */ 362 | public func removeObject(forKey key:String){ 363 | semaphoreSignal.wait() 364 | storage.removeObject(key: key) 365 | semaphoreSignal.signal() 366 | } 367 | 368 | /** 369 | 根据key移除缓存中对应的value 370 | @param key:要移除的value对应的键 371 | @param completionHandler:移除完成后回调 372 | */ 373 | public func removeObject(forKey key:String,completionHandler:@escaping(() -> Void)){ 374 | queue.async { 375 | self.removeObject(forKey: key) 376 | completionHandler() 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /SwiftlyCache/DiskStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiskStorage.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2019/12/23. 6 | // Copyright © 2019 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SQLite3 11 | import CommonCrypto 12 | 13 | class DiskStorageItem{ 14 | var key:String? 15 | var data:Data? 16 | var filename:String? 17 | var size:Int32 = 0 18 | var accessTime:Int32 = 0 19 | } 20 | 21 | fileprivate extension Date{ 22 | var timeStamp:Int{ 23 | return Int(timeIntervalSince1970) 24 | } 25 | } 26 | 27 | fileprivate extension String { 28 | var md5:String { 29 | let utf8 = cString(using: .utf8) 30 | var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) 31 | CC_MD5(utf8, CC_LONG(utf8!.count - 1), &digest) 32 | return digest.reduce("") { $0 + String(format:"%02x", $1) } 33 | } 34 | } 35 | 36 | class DiskStorage{ 37 | let dbFileName = "default.sqlite" 38 | let dbWalFileName = "default.sqlite-wal" 39 | let dbShmFileName = "default.sqlite-shm" 40 | let foldername = "data" 41 | var filePath:String 42 | var dbPath:String 43 | var db:OpaquePointer? 44 | let dataMaxSize = 1024 * 20 45 | var dbStmtCache:Dictionary = [String:OpaquePointer]() 46 | let fileManager:FileManager = FileManager.default 47 | init(path:String) { 48 | filePath = path 49 | dbPath = filePath 50 | filePath = filePath + ("/\(foldername)") 51 | } 52 | 53 | convenience init(currentPath:String){ 54 | self.init(path: currentPath) 55 | guard createDirectory() else{ return } 56 | guard dbOpen() else{ return } 57 | guard dbCreateTable() else{ return } 58 | } 59 | 60 | deinit { 61 | dbClose() 62 | } 63 | 64 | /** 65 | md5 66 | */ 67 | func makeFileName(forKey key:String)->String{ 68 | return key.md5 69 | } 70 | 71 | /** 72 | 创建文件夹 73 | */ 74 | func createDirectory()->Bool{ 75 | do{ 76 | try fileManager.createDirectory(atPath: self.filePath, withIntermediateDirectories: true, attributes: nil) 77 | }catch{ 78 | print("Failed to create folder \(error.localizedDescription)") 79 | return false 80 | } 81 | return true 82 | } 83 | 84 | /** 85 | 拼接文件路径 86 | */ 87 | func appendingPath(filename:String)->String{ 88 | let path = self.filePath 89 | return path + ("/\(filename)") 90 | } 91 | 92 | /** 93 | 判断是否存在该文件 94 | */ 95 | func isfileExists()->Bool{ 96 | return fileManager.fileExists(atPath: self.filePath) 97 | } 98 | 99 | /** 100 | 创建文件并写入数据 101 | filename 通过md5后作为文件名 102 | */ 103 | func createFile(filename:String?,data:Data) -> Bool{ 104 | if let filename = filename{ 105 | let path = appendingPath(filename: filename) 106 | if ((try? data.write(to: URL(fileURLWithPath: path))) != nil) { return true } 107 | } 108 | return false 109 | } 110 | 111 | /** 112 | 读取指定文件数据 113 | @param filename: 文件名 114 | @return 根据指定路径成功读取到数据后直接把数据返回,否则返回nil 115 | */ 116 | func readData(filename:String)->Data?{ 117 | let path = appendingPath(filename: filename) 118 | guard let data = fileManager.contents(atPath: path) else{ return nil } 119 | return data 120 | } 121 | 122 | /** 123 | 根据key移除指定数据 124 | @param key:与value关联的key 125 | @return 移除成功返回true,否则返回false 126 | */ 127 | @discardableResult 128 | func removeObject(key:String)->Bool{ 129 | if let filename = self.dbGetFilename(key: key){ 130 | removeFile(filename: filename) 131 | } 132 | return self.dbRemoveItem(key: key) 133 | } 134 | 135 | /** 136 | 移除指定文件 137 | @param filename: 指定文件名 138 | @return 移除成功返回true,否则返回false 139 | */ 140 | @discardableResult 141 | func removeFile(filename:String) -> Bool{ 142 | let path = appendingPath(filename: filename) 143 | if ((try? fileManager.removeItem(atPath: path)) == nil) { return false } 144 | return true 145 | } 146 | 147 | /** 148 | 移除全部文件数据 149 | */ 150 | func removeAll(){ 151 | if !dbRemoveAllItem(){ return } 152 | if dbStmtCache.count > 0{ dbStmtCache.removeAll(keepingCapacity: true) } 153 | if !dbClose() { return } 154 | if ((try? fileManager.removeItem(atPath: self.filePath)) == nil) { return } 155 | if !createDirectory() { return } 156 | if !dbOpen() { return } 157 | if !dbCreateTable() { return } 158 | } 159 | 160 | /** 161 | 获取文件大小 162 | */ 163 | func fileSize(filename:String) -> UInt64{ 164 | let path = appendingPath(filename: filename) 165 | guard let attr = try? fileManager.attributesOfItem(atPath: path) else{ return 0 } 166 | let fileSize = Double((attr as NSDictionary).fileSize()) 167 | return UInt64(fileSize) 168 | } 169 | 170 | /** 171 | 获取文件总的大小 172 | */ 173 | func fileTotalSize() throws -> UInt64 { 174 | var size: UInt64 = 0 175 | let contents = try fileManager.contentsOfDirectory(atPath: self.filePath) 176 | for pathComponent in contents { 177 | let filePath = NSString(string: self.filePath).appendingPathComponent(pathComponent) 178 | let attributes = try fileManager.attributesOfItem(atPath: filePath) 179 | if let fileSize = attributes[.size] as? UInt64 { size += fileSize } 180 | } 181 | return size 182 | } 183 | 184 | 185 | /** 186 | 打开数据库 187 | */ 188 | func dbOpen() ->Bool{ 189 | guard sqlite3_open(dbPath + ("/\(dbFileName)"), &db) == SQLITE_OK else{ return false } 190 | return true 191 | } 192 | 193 | /** 194 | 关闭数据库 195 | */ 196 | @discardableResult 197 | func dbClose()->Bool{ 198 | var isCont = true 199 | guard db == nil else{ 200 | let result = sqlite3_close(db) 201 | if result == SQLITE_BUSY || result == SQLITE_LOCKED{ 202 | var stmt:OpaquePointer? 203 | while isCont { 204 | stmt = sqlite3_next_stmt(db, nil) 205 | if stmt != nil{ 206 | sqlite3_finalize(stmt) 207 | }else{ isCont = false } 208 | } 209 | }else if result != SQLITE_OK{ 210 | print("sqlite close failed \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 211 | return false 212 | } 213 | db = nil 214 | return true 215 | } 216 | return true 217 | } 218 | 219 | /** 220 | 创建数据库表 221 | */ 222 | func dbCreateTable() -> Bool{ 223 | let sql = "pragma journal_mode = wal; pragma synchronous = normal; create table if not exists detailed (key text primary key,filename text,inline_data blob,size integer,last_access_time integer); create index if not exists last_access_time_idx on detailed(last_access_time);" 224 | guard dbExcuSql(sql: sql) else{ return false } 225 | return true 226 | } 227 | 228 | 229 | @discardableResult 230 | func dbExcuSql(sql:String) -> Bool{ 231 | guard sqlite3_exec(db,sql.cString(using: .utf8),nil,nil,nil) == SQLITE_OK else{ 232 | print("sqlite exec error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 233 | return false 234 | } 235 | return true 236 | } 237 | 238 | /** 239 | 保存数据 240 | @param key: value关联的键 241 | @param value: 要缓存的数据对象 242 | @param filename:文件名称(如果为nil,则缓存到数据库,否则value则写入到文件中,元数据则写入数据库中) 243 | @return 缓存成功返回true,否则返回false 244 | */ 245 | @discardableResult 246 | func save(forKey key:String,value:Data,filename:String?)->Bool{ 247 | if filename != nil{ 248 | guard createFile(filename: filename, data: value) else{ return false } 249 | guard dbSave(forKey: key, value: value, filename: filename) else { removeFile(filename: filename!); return false } 250 | return true 251 | } 252 | if let currentFilename = dbGetFilename(key: key){ removeFile(filename: currentFilename) } 253 | guard dbSave(forKey: key, value: value, filename: filename) else { return false } 254 | return true 255 | } 256 | 257 | func dbSave(forKey key:String,value:Data,filename:String?)->Bool{ 258 | let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) 259 | let sql = "insert or replace into detailed" + "(key,filename,inline_data,size,last_access_time)" + "values(?1,?2,?3,?4,?5);" 260 | guard let stmt = dbPrepareStmt(sql: sql) else{ return false } 261 | sqlite3_bind_text(stmt, 1, key.cString(using: .utf8), -1, SQLITE_TRANSIENT) 262 | if let filename = filename{ 263 | sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_TRANSIENT) 264 | sqlite3_bind_blob(stmt, 3, nil, 0, SQLITE_TRANSIENT) 265 | }else{ 266 | sqlite3_bind_text(stmt, 2, nil, -1, SQLITE_TRANSIENT) 267 | sqlite3_bind_blob(stmt,3,[UInt8](value),Int32(value.count),SQLITE_TRANSIENT) 268 | } 269 | sqlite3_bind_int(stmt, 4, Int32(value.count)) 270 | sqlite3_bind_int(stmt, 5,Int32(Date().timeStamp)) 271 | guard sqlite3_step(stmt) == SQLITE_DONE else{ 272 | print("sqlite insert error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 273 | return false 274 | } 275 | return true 276 | } 277 | 278 | /** 279 | 根据sql语句查询对应的stmt 280 | @param sql: sql语句 281 | */ 282 | func dbPrepareStmt(sql:String) -> OpaquePointer?{ 283 | guard sql.count != 0 || dbStmtCache.count != 0 else{ return nil } 284 | var stmt:OpaquePointer? = dbStmtCache[sql] 285 | guard stmt != nil else{ 286 | if sqlite3_prepare_v2(db, sql.cString(using: .utf8), -1, &stmt, nil) != SQLITE_OK{ 287 | print("sqlite stmt prepare error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 288 | return nil 289 | } 290 | dbStmtCache[sql] = stmt 291 | return stmt 292 | } 293 | sqlite3_reset(stmt) 294 | return stmt 295 | } 296 | 297 | /** 298 | 根据指定key获取对应的数据并更新最后访问时间 299 | @return 获取到对应的数据后则使用DiskStorageItem进行封装后返回 300 | */ 301 | func dbGetItemForKey(forKey key:String)->DiskStorageItem?{ 302 | guard let item = dbQuery(forKey: key) else{ return nil } 303 | dbUpdataLastAccessTime(key: key) 304 | if let filename = item.filename{ 305 | item.data = readData(filename: filename) } 306 | return item 307 | } 308 | 309 | 310 | /** 311 | 使用DiskStorageItem进行数据的封装 312 | @return 返回封装后的item 313 | */ 314 | func dbGetItemFromStmt(stmt:OpaquePointer?)->DiskStorageItem{ 315 | let item = DiskStorageItem() 316 | let currentKey = String(cString: sqlite3_column_text(stmt, 0)) 317 | if let name = sqlite3_column_text(stmt, 1){ 318 | let filename = String(cString: name) 319 | item.filename = filename 320 | } 321 | let size = sqlite3_column_int(stmt, 3) 322 | if let blob = sqlite3_column_blob(stmt, 2){ item.data = Data(bytes: blob, count: Int(size)) } 323 | let last_access_time = sqlite3_column_int(stmt, 4) 324 | item.key = currentKey 325 | item.size = size 326 | item.accessTime = last_access_time 327 | return item 328 | } 329 | 330 | /** 331 | 根据指定key查询对应数据 332 | @return 如果没有找到对应的数据,则返回nil 333 | */ 334 | func dbQuery(forKey key:String) -> DiskStorageItem?{ 335 | let sql = "select key,filename,inline_data,size,last_access_time from detailed where key=?1;" 336 | guard let stmt = dbPrepareStmt(sql: sql) else { return nil } 337 | 338 | /** 339 | 如果第5个参数传递 nil 或者 SQLITE_STATIC ,SQlite 会假定这块 buffer 是静态内存,所以SQlite放手不管 340 | 如果第5个参数传递的是 SQLITE_TRANSIENT,则SQlite会在内部复制这块buffer的内容。这就允许客户应用程序在调用完 bind 函数之后,立刻释放这块 buffer(或者是一块栈上的 buffer 在离开作用域之后自动销毁)。SQlite会自动在合适的时机释放它内部复制的这块 buffer 341 | 由于在 SQLite.h 中 SQLITE_TRANSIENT 是以宏的形式定义的,而在 swift 中无法直接利用宏传递函数指针,因此需要使用以下代码转换一下 342 | */ 343 | let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) 344 | sqlite3_bind_text(stmt, 1, key.cString(using: .utf8), -1, SQLITE_TRANSIENT) 345 | guard sqlite3_step(stmt) == SQLITE_ROW else { 346 | return nil 347 | } 348 | let item = dbGetItemFromStmt(stmt: stmt) 349 | return item 350 | } 351 | 352 | /** 353 | 获取所有数据key 354 | @return 如果没有获取到则返回一个空数组 355 | */ 356 | func dbGetAllkey()->[String]{ 357 | var keys = [String]() 358 | let sql = "select key from detailed;" 359 | guard let stmt = dbPrepareStmt(sql: sql) else { return keys } 360 | repeat{ 361 | let result = sqlite3_step(stmt) 362 | if result == SQLITE_ROW{ 363 | let key = String(cString: sqlite3_column_text(stmt,0)) 364 | keys.append(key) 365 | }else if result == SQLITE_DONE{ 366 | break 367 | }else{ 368 | print("sqlite query keys error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 369 | break 370 | } 371 | }while(true) 372 | return keys 373 | } 374 | 375 | /** 376 | 根据key获取数据的文件名 377 | @return 如果没有找到指定文件名,则返回nil 378 | */ 379 | func dbGetFilename(key:String)->String?{ 380 | let sql = "select filename from detailed where key = ?1;" 381 | guard let stmt = dbPrepareStmt(sql: sql) else { return nil } 382 | sqlite3_bind_text(stmt, 1, key.cString(using: .utf8), -1, nil) 383 | guard sqlite3_step(stmt) == SQLITE_ROW else{ 384 | return nil 385 | } 386 | guard let filename = sqlite3_column_text(stmt, 0) else{ return nil } 387 | return String(cString: filename) 388 | } 389 | 390 | /** 391 | 移除所有过期数据 392 | @return 移除成功返回true,否则返回false 393 | */ 394 | func dbRemoveAllExpiredData(time:TimeInterval)->Bool{ 395 | let filenames = dbGetExpiredFiles(time: time) 396 | for filename in filenames { removeFile(filename:filename) } 397 | if dbRemoveExpired(time: time){ dbCheckpoint();return true } 398 | return false 399 | } 400 | 401 | /** 402 | 直接把日志数据同步到数据库中 403 | */ 404 | func dbCheckpoint(){ 405 | sqlite3_wal_checkpoint(db, nil); 406 | } 407 | 408 | /** 409 | 获取过期文件名 410 | @return 如果没有获取到不为nil的文件名,则返回一个空的数组 411 | */ 412 | func dbGetExpiredFiles(time:TimeInterval)->[String]{ 413 | var filenames = [String]() 414 | let sql = "select filename from detailed where last_access_time < ?1 and filename is not null;" 415 | guard let stmt = dbPrepareStmt(sql: sql) else { return filenames } 416 | sqlite3_bind_int(stmt,1,Int32(time)) 417 | repeat{ 418 | let result = sqlite3_step(stmt) 419 | if result == SQLITE_ROW{ 420 | let filename = String(cString: sqlite3_column_text(stmt,0)) 421 | filenames.append(filename) 422 | }else if result == SQLITE_DONE{ break }else{ 423 | print("sqlite query expired file error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 424 | break 425 | } 426 | }while(true) 427 | return filenames 428 | } 429 | 430 | /** 431 | 移除数据库中过期的数据 432 | @return 移除成功返回true,否则返回false 433 | */ 434 | func dbRemoveExpired(time:TimeInterval)->Bool{ 435 | let sql = "delete from detailed where last_access_time < ?1;" 436 | guard let stmt = dbPrepareStmt(sql: sql) else { return false } 437 | sqlite3_bind_int(stmt, 1, Int32(time)) 438 | guard sqlite3_step(stmt) == SQLITE_DONE else{ 439 | print("sqlite remove expired data error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 440 | return false 441 | } 442 | return true 443 | } 444 | 445 | 446 | func dbGetSizeExceededValueFromStmt(stmt:OpaquePointer?)->DiskStorageItem{ 447 | let item = DiskStorageItem() 448 | let currentKey = String(cString: sqlite3_column_text(stmt, 0)) 449 | if let name = sqlite3_column_text(stmt, 1){ 450 | let filename = String(cString: name) 451 | item.filename = filename 452 | } 453 | let size = sqlite3_column_int(stmt, 2) 454 | item.key = currentKey 455 | item.size = size 456 | return item 457 | } 458 | 459 | /** 460 | 删除超过指定大小的值 461 | */ 462 | func dbGetSizeExceededValues()->[DiskStorageItem?]{ 463 | let sql = "select key,filename,size from detailed order by last_access_time asc limit ?1;" 464 | let stmt = dbPrepareStmt(sql: sql) 465 | let count = 16 466 | var items = [DiskStorageItem]() 467 | sqlite3_bind_int(stmt, 1, Int32(count)) 468 | repeat{ 469 | let result = sqlite3_step(stmt) 470 | if result == SQLITE_ROW{ 471 | let item = dbGetSizeExceededValueFromStmt(stmt: stmt) 472 | items.append(item) 473 | }else if result == SQLITE_OK{ break } 474 | else{ break } 475 | }while true 476 | return items 477 | } 478 | 479 | /** 480 | 根据key查询是否存在对应的值 481 | @param key: value关联的键 482 | @return 查询成功返回true,否则返回false 483 | */ 484 | func dbIsExistsForKey(forKey key:String) -> Bool{ 485 | let sql = "select count(key) from detailed where key = ?1" 486 | guard let stmt = dbPrepareStmt(sql: sql) else { return false } 487 | sqlite3_bind_text(stmt, 1, key.cString(using: .utf8), -1, nil) 488 | guard sqlite3_step(stmt) == SQLITE_ROW else{ 489 | return false 490 | } 491 | return Int(sqlite3_column_int(stmt, 0)) > 0 492 | } 493 | 494 | /** 495 | @return 获取数据总大小 496 | */ 497 | func dbTotalItemSize() -> Int32{ 498 | let sql = "select sum(size) from detailed;" 499 | guard let stmt = dbPrepareStmt(sql: sql) else { return -1 } 500 | guard sqlite3_step(stmt) == SQLITE_ROW else{ 501 | return -1 502 | } 503 | return Int32(sqlite3_column_int(stmt, 0)) 504 | } 505 | 506 | /** 507 | @return 获取数据总个数 508 | */ 509 | func dbTotalItemCount()->Int{ 510 | let sql = "select count(*) from detailed;" 511 | guard let stmt = dbPrepareStmt(sql: sql) else { return -1 } 512 | guard sqlite3_step(stmt) == SQLITE_ROW else{ 513 | return -1 514 | } 515 | return Int(sqlite3_column_int(stmt, 0)) 516 | } 517 | 518 | /** 519 | 根据key更新最后访问时间 520 | */ 521 | func dbUpdataLastAccessTime(key:String){ 522 | let sql = "update detailed set last_access_time=?1 where key=?2;" 523 | guard let stmt = dbPrepareStmt(sql: sql) else { return } 524 | sqlite3_bind_int(stmt, 1, Int32(Date().timeStamp)) 525 | sqlite3_bind_text(stmt, 2, key.cString(using: .utf8), -1, nil) 526 | guard sqlite3_step(stmt) == SQLITE_DONE else{ 527 | print("sqlite update accessTime error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 528 | return 529 | } 530 | } 531 | 532 | /** 533 | 移除key指定数据 534 | @return 成功返回true,否则返回false 535 | */ 536 | func dbRemoveItem(key:String)->Bool{ 537 | //删除sql语句 538 | let sql = "delete from detailed where key = ?1"; 539 | guard let stmt = dbPrepareStmt(sql: sql) else { return false} 540 | sqlite3_bind_text(stmt, 1, key.cString(using: .utf8), -1, nil) 541 | //step执行 542 | guard sqlite3_step(stmt) == SQLITE_DONE else{ 543 | print("sqlite remove data error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 544 | return false 545 | } 546 | return true 547 | } 548 | 549 | func dbRemoveAllItem()->Bool{ 550 | //删除sql语句 551 | let sql = "delete from detailed"; 552 | guard let stmt = dbPrepareStmt(sql: sql) else { return false} 553 | //step执行 554 | guard sqlite3_step(stmt) == SQLITE_DONE else{ 555 | print("sqlite remove data error \(String(describing: String(validatingUTF8: sqlite3_errmsg(db))))") 556 | return false 557 | } 558 | return true 559 | } 560 | } 561 | 562 | 563 | 564 | -------------------------------------------------------------------------------- /SwiftlyCache/MemoryCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryCache.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2019/12/27. 6 | // Copyright © 2019 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | /** 13 | 支持for...in循环 14 | */ 15 | public class MemoryCacheGenerator:IteratorProtocol{ 16 | public typealias Element = (key:String,object:Value) 17 | 18 | private var memoryCache:MemoryCache 19 | 20 | fileprivate init(memoryCache:MemoryCache) { 21 | self.memoryCache = memoryCache 22 | } 23 | public func next() -> Element? { 24 | guard let node = memoryCache.storage.next() else { 25 | memoryCache.storage.setCurrentNode() 26 | return nil 27 | } 28 | memoryCache.storage.moveNode(node: node) 29 | return (node.key,node.object) 30 | } 31 | 32 | } 33 | private let cacheIdentifier: String = "com.swiftcache.memory" 34 | 35 | public class MemoryCache{ 36 | /** 37 | 设置最大的内存缓存容量(0为不限制) 38 | */ 39 | public var totalCostLimit:vm_size_t = 0 40 | /** 41 | 设置最大的内存缓存数量 42 | */ 43 | public var totalCountLimit:vm_size_t = 0 44 | /** 45 | 系统内存警告是否删除所有内存数据,默认为true 46 | */ 47 | public var autoRemoveAllObjectWhenMemoryWarning = true 48 | /** 49 | 进入后台是否删除所有内存数据,默认为true 50 | */ 51 | public var autoRemoveAllObjectWhenEnterBackground = true 52 | 53 | fileprivate let storage:MemoryStorage = MemoryStorage() 54 | 55 | private let semaphoreSignal = DispatchSemaphore(value: 1) 56 | 57 | private let queue: DispatchQueue = DispatchQueue(label: cacheIdentifier, attributes: DispatchQueue.Attributes.concurrent) 58 | 59 | public init() { 60 | NotificationCenter.default.addObserver(self, selector: #selector(didReceiveMemoryWarningNotification), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) 61 | NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil) 62 | } 63 | 64 | deinit { 65 | NotificationCenter.default.removeObserver(self, name: UIApplication.didReceiveMemoryWarningNotification, object: nil) 66 | NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) 67 | } 68 | 69 | @objc fileprivate func didReceiveMemoryWarningNotification(){ 70 | if self.autoRemoveAllObjectWhenMemoryWarning{ 71 | removeAll() 72 | } 73 | } 74 | 75 | @objc fileprivate func didEnterBackgroundNotification(){ 76 | if self.autoRemoveAllObjectWhenEnterBackground{ 77 | removeAll() 78 | } 79 | } 80 | 81 | /** 82 | 超过限定张数,需要丢弃一部分内容 83 | */ 84 | private func discardedToCount(){ 85 | if self.totalCountLimit != 0{ 86 | if self.storage.totalCountLimit > self.totalCountLimit{ 87 | storage.removeTailNode() 88 | } 89 | } 90 | } 91 | 92 | /** 93 | 超过限定容量,需要丢弃一部分内容 94 | */ 95 | private func discardedToCost(){ 96 | if self.totalCostLimit != 0{ 97 | while self.storage.totalCostLimit > self.totalCostLimit { 98 | self.storage.removeTailNode() 99 | } 100 | } 101 | } 102 | } 103 | 104 | extension MemoryCache:Sequence{ 105 | /** 106 | 通过下标方式set和get 107 | @param key: value关联的键 108 | @return Value:根据key查询对应的value,如果查询到则返回对应value,否则返回nil 109 | */ 110 | public subscript(key:String) ->Value?{ 111 | set{ 112 | if let newValue = newValue{ set(forKey: key, value: newValue) } 113 | }get{ 114 | if let object = object(forKey: key){ return object } 115 | return nil 116 | } 117 | } 118 | 119 | /** 120 | 返回该序列元素迭代器 121 | */ 122 | public func makeIterator() -> MemoryCacheGenerator { 123 | semaphoreSignal.wait() 124 | self.storage.setCurrentNode() 125 | let generator = MemoryCacheGenerator(memoryCache: self) 126 | semaphoreSignal.signal() 127 | return generator 128 | } 129 | } 130 | 131 | extension MemoryCache:CacheAware{ 132 | /** 133 | 设置需要缓存的key和value 134 | @param key: 与value关联的键 135 | @param value: 需要缓存的对象,如果为nil,则直接返回false 136 | @cost: 缓存对象所占用的字节(默认为0) 137 | @return 内存缓存与磁盘缓存只要其中一个缓存成功则返回true 138 | */ 139 | 140 | @discardableResult 141 | public func set(forKey key: String, value: Value?, cost: vm_size_t = 0) ->Bool{ 142 | guard let object = value else { return false } 143 | semaphoreSignal.wait() 144 | if let node:LinkedNode = storage.dic[key]{ 145 | node.object = object 146 | node.cost = cost 147 | //移动到链表头 148 | storage.moveNode(node: node) 149 | }else{ 150 | let node:LinkedNode = LinkedNode(key: key, object: object, cost: cost) 151 | storage.dic[key] = node 152 | storage.insertNodeAtHead(node: node) 153 | } 154 | discardedToCount() 155 | discardedToCost() 156 | semaphoreSignal.signal() 157 | return true 158 | } 159 | 160 | /** 161 | 设置需要缓存的key和value 162 | @param key: 与value关联的键 163 | @param value: 需要缓存的对象,如果为nil,则该方法无效 164 | @param cost:缓存对象所占用的字节(默认为0) 165 | @param completionHandler: 缓存数据写入完成回调 166 | */ 167 | public func set(forKey key:String,value:Value?,cost:vm_size_t = 0,completionHandler:@escaping((_ key:String,_ finished:Bool) -> Void)){ 168 | queue.async { 169 | let fin = self.set(forKey: key, value: value,cost: cost) 170 | completionHandler(key,fin) 171 | } 172 | } 173 | 174 | /** 175 | 根据key查询对应的value 176 | @param key: 与value关联的键 177 | @return 返回与key关联的value,如果没有与key对应的value,返回nil 178 | */ 179 | public func object(forKey key: String) -> Value? { 180 | semaphoreSignal.wait() 181 | guard let node = storage.dic[key] else { 182 | semaphoreSignal.signal() 183 | return nil 184 | } 185 | //移动到链表头 186 | storage.moveNode(node: node) 187 | semaphoreSignal.signal() 188 | return node.object 189 | } 190 | 191 | /** 192 | 根据key查询对应的value 193 | @param key: 与value关联的键 194 | @param completionHandler 查询完成回调 195 | */ 196 | public func object(forKey key:String,completionHandler:@escaping((_ key:String,_ value:Value?) -> Void)){ 197 | queue.async { 198 | if let object = self.object(forKey: key){ completionHandler(key,object) } 199 | else{ completionHandler(key,nil) } 200 | } 201 | } 202 | 203 | /** 204 | 根据key查询缓存中是否存在对应的value 205 | @return 如果缓存中存在与key对应的value,返回true,否则返回false 206 | */ 207 | public func isExistsObject(forKey key: String) -> Bool { 208 | semaphoreSignal.wait() 209 | let exists = storage.dic.keys.contains(key) 210 | semaphoreSignal.signal() 211 | return exists 212 | } 213 | 214 | /** 215 | 根据key查询缓存中是否存在对应的value 216 | @param completionHandler: 查询完成后回调 217 | */ 218 | public func isExistsObject(forKey key:String,completionHandler:@escaping((_ key:String,_ contain:Bool) -> Void)) { 219 | queue.async { 220 | let exists = self.isExistsObject(forKey: key) 221 | completionHandler(key,exists) 222 | } 223 | } 224 | 225 | /** 226 | 移除所有缓存 227 | */ 228 | public func removeAll(){ 229 | if storage.dic.isEmpty{ return } 230 | semaphoreSignal.wait() 231 | storage.removeAllObject() 232 | semaphoreSignal.signal() 233 | } 234 | 235 | /** 236 | 移除所有缓存 237 | completionHandler: 移除完成回调 238 | */ 239 | public func removeAll(completionHandler: @escaping (() -> Void)) { 240 | queue.async { 241 | self.removeAll() 242 | completionHandler() 243 | } 244 | } 245 | 246 | /** 247 | 移除指定缓存 248 | key:通过key删除对应的value 249 | */ 250 | public func removeObject(forKey key:String){ 251 | semaphoreSignal.wait() 252 | if let node:LinkedNode = storage.dic[key]{ 253 | storage.removeObject(node: node) 254 | } 255 | semaphoreSignal.signal() 256 | } 257 | 258 | /** 259 | 移除指定缓存 260 | key:通过key删除对应的value 261 | completionHandler: 移除完成回调 262 | */ 263 | public func removeObject(forKey key: String, completionHandler:@escaping(() -> Void)) { 264 | queue.async { 265 | self.removeObject(forKey: key) 266 | completionHandler() 267 | } 268 | } 269 | } 270 | 271 | 272 | -------------------------------------------------------------------------------- /SwiftlyCache/MemoryStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryStorage.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2019/12/23. 6 | // Copyright © 2019 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LinkedNode:Equatable{ 12 | static func == (lhs: LinkedNode, rhs: LinkedNode) -> Bool { 13 | return lhs.key == rhs.key 14 | } 15 | var key:String 16 | var object:Value 17 | var cost:vm_size_t 18 | weak var prev:LinkedNode? 19 | weak var next:LinkedNode? 20 | 21 | init(key:String,object:Value,cost:vm_size_t) { 22 | self.key = key 23 | self.object = object 24 | self.cost = cost 25 | } 26 | } 27 | 28 | class MemoryStorage{ 29 | var head:LinkedNode? 30 | var tail:LinkedNode? 31 | var totalCostLimit:vm_size_t = 0 32 | var totalCountLimit:vm_size_t = 0 33 | var dic = [String:LinkedNode]() 34 | typealias Element = (String,Value) 35 | var currentNode:LinkedNode? 36 | 37 | /** 38 | 插入数据 39 | @param node:缓存对象 40 | */ 41 | func insertNodeAtHead(node:LinkedNode){ 42 | totalCostLimit+=node.cost 43 | totalCountLimit+=1 44 | if head == nil{ 45 | head = node 46 | tail = head 47 | }else{ 48 | node.next = head 49 | head?.prev = node 50 | head = node 51 | 52 | } 53 | } 54 | 55 | /** 56 | 移除最后的节点 57 | */ 58 | @discardableResult 59 | func removeTailNode()->Bool{ 60 | if tail == nil{ 61 | head = tail 62 | totalCostLimit = 0 63 | totalCountLimit = 0 64 | return false 65 | }else{ 66 | if let currentKey = tail?.key{ 67 | if let node = dic.removeValue(forKey: currentKey){ 68 | tail?.prev?.next = nil 69 | tail = tail?.prev 70 | node.prev = nil 71 | node.next = nil 72 | totalCostLimit -= node.cost 73 | totalCountLimit -= 1 74 | return true 75 | } 76 | } 77 | } 78 | return false 79 | } 80 | 81 | /** 82 | 移动节点 83 | */ 84 | func moveNode(node:LinkedNode){ 85 | if head == node{ return } 86 | if tail == node{ 87 | //在链表尾部 88 | node.prev?.next = nil 89 | tail = node.prev 90 | node.next = head 91 | head?.prev = node 92 | head = node 93 | }else{ 94 | //在链表中间 95 | node.prev?.next = node.next 96 | node.next?.prev = node.prev 97 | node.next = head 98 | head?.prev = node 99 | head = node 100 | } 101 | } 102 | 103 | 104 | func removeObject(node:LinkedNode){ 105 | guard head != nil else{ return } 106 | if node.prev != nil && node.next != nil{ 107 | node.prev?.next = node.next 108 | node.next?.prev = node.prev 109 | totalCostLimit -= node.cost 110 | totalCountLimit -= 1 111 | node.prev = nil 112 | node.next = nil 113 | dic.removeValue(forKey: node.key) 114 | }else if node.prev == nil{ 115 | head = node.next 116 | node.next = nil 117 | node.prev = nil 118 | head?.prev = nil 119 | dic.removeValue(forKey: node.key) 120 | }else if node.next == nil{ 121 | removeTailNode() 122 | } 123 | } 124 | 125 | func removeAllObject(){ 126 | totalCountLimit = 0 127 | totalCostLimit = 0 128 | head = nil 129 | tail = nil 130 | currentNode = nil 131 | if dic.count>0{ self.dic.removeAll(keepingCapacity: true) } 132 | } 133 | 134 | func setCurrentNode(){ 135 | currentNode = head 136 | } 137 | 138 | func next()->LinkedNode?{ 139 | let node = currentNode 140 | currentNode = currentNode?.next 141 | return node 142 | } 143 | } 144 | 145 | -------------------------------------------------------------------------------- /SwiftlyCache/MultiCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridCache.swift 3 | // HummingBird 4 | // 5 | // Created by 黄琳川 on 2020/1/10. 6 | // Copyright © 2020 黄琳川. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | 支持for...in循环 13 | */ 14 | public class MultiCacheGenerator:IteratorProtocol{ 15 | public typealias Element = (String,Value) 16 | 17 | private let memoryCache:MemoryCache 18 | 19 | private let diskCache:DiskCache 20 | 21 | private let memoryCacheGenerator: MemoryCacheGenerator 22 | 23 | private let diskCacheGenerator: DiskCacheGenerator 24 | 25 | public func next() -> Element? { 26 | if diskCacheGenerator.index == 0{ diskCache.getAllKey() } 27 | guard diskCacheGenerator.index < diskCache.keys.endIndex else { 28 | diskCacheGenerator.index = diskCache.keys.startIndex 29 | return nil 30 | } 31 | let key = diskCache.keys[diskCacheGenerator.index] 32 | diskCache.keys.formIndex(after: &diskCacheGenerator.index) 33 | if let element = memoryCache.object(forKey: key){ 34 | return (key,element) 35 | }else if let element = diskCache.object(forKey: key){ 36 | memoryCache.set(forKey: key, value: element) 37 | return (key,element) 38 | } 39 | return nil 40 | } 41 | 42 | fileprivate init(memoryCache:MemoryCache,diskCache:DiskCache, 43 | memoryCacheGenerator:MemoryCacheGenerator,diskCacheGenerator:DiskCacheGenerator){ 44 | self.memoryCache = memoryCache 45 | self.diskCache = diskCache 46 | self.memoryCacheGenerator = memoryCacheGenerator 47 | self.diskCacheGenerator = diskCacheGenerator 48 | } 49 | } 50 | 51 | private let cacheIdentifier: String = "com.swiftcache.hybrid" 52 | public class MultiCache{ 53 | 54 | public let memoryCache:MemoryCache = MemoryCache() 55 | 56 | public let diskCache:DiskCache 57 | 58 | /** 59 | 磁盘缓存路径 60 | */ 61 | private var diskCachePath:String 62 | 63 | private let queue: DispatchQueue = DispatchQueue(label: cacheIdentifier, attributes: DispatchQueue.Attributes.concurrent) 64 | 65 | public init(cacheName:String="default") { 66 | self.diskCachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] 67 | self.diskCachePath = self.diskCachePath + ("/\(cacheName)") 68 | self.diskCache = DiskCache(path: self.diskCachePath) 69 | } 70 | } 71 | 72 | extension MultiCache:Sequence{ 73 | /** 74 | 通过下标方式set和get 75 | @param key: value关联的键 76 | @return Value:根据key查询对应的value,如果查询到则返回对应value,否则返回nil 77 | */ 78 | public subscript(key:String) ->Value?{ 79 | set{ 80 | if let newValue = newValue{ set(forKey: key, value: newValue) } 81 | }get{ 82 | if let object = object(forKey: key){ return object } 83 | return nil 84 | } 85 | } 86 | 87 | /** 88 | 返回该序列元素迭代器 89 | */ 90 | public func makeIterator() -> MultiCacheGenerator { 91 | let generator = MultiCacheGenerator(memoryCache:memoryCache,diskCache: diskCache,memoryCacheGenerator: memoryCache.makeIterator(), diskCacheGenerator: diskCache.makeIterator()) 92 | return generator 93 | } 94 | } 95 | 96 | extension MultiCache:CacheAware{ 97 | /** 98 | 设置需要缓存的key和value 99 | @param key: 与value关联的键 100 | @param value: 需要缓存的对象,如果为nil,则直接返回false 101 | @cost: 缓存对象所占用的字节(默认为0) 102 | @return 内存缓存与磁盘缓存只要其中一个缓存成功则返回true 103 | */ 104 | 105 | @discardableResult 106 | public func set(forKey key: String, value: Value?, cost: vm_size_t = 0)->Bool { 107 | let memoryCacheFin = memoryCache.set(forKey: key, value: value,cost: cost) 108 | let diskCacheFin = diskCache.set(forKey: key, value: value) 109 | if memoryCacheFin || diskCacheFin{ return true } 110 | return false 111 | } 112 | 113 | /** 114 | 设置需要缓存的key和value 115 | @param key: 与value关联的键 116 | @param value: 需要缓存的对象,如果为nil,则该方法无效 117 | @param cost:缓存对象所占用的字节(默认为0) 118 | @param completionHandler: 缓存数据写入完成回调 119 | */ 120 | public func set(forKey key:String,value:Value?,cost:vm_size_t = 0,completionHandler:@escaping((_ key:String,_ finished:Bool) -> Void)){ 121 | queue.async { 122 | let memoryCacheFin = self.memoryCache.set(forKey: key, value: value,cost: cost) 123 | let diskCacheFin = self.diskCache.set(forKey: key, value: value) 124 | if memoryCacheFin || diskCacheFin{ completionHandler(key,true) } 125 | else{ completionHandler(key,false) } 126 | } 127 | } 128 | 129 | /** 130 | 根据key查询对应的value 131 | @param key: 与value关联的键 132 | @return 返回与key关联的value,如果没有与key对应的value,返回nil 133 | */ 134 | public func object(forKey key: String) -> Value? { 135 | if let object = self.memoryCache.object(forKey: key){ return object } 136 | if let object = diskCache.object(forKey: key){ 137 | memoryCache.set(forKey: key, value: object) 138 | return object 139 | } 140 | return nil 141 | } 142 | 143 | /** 144 | 根据key查询对应的value 145 | @param key: 与value关联的键 146 | @param completionHandler 查询完成回调 147 | */ 148 | public func object(forKey key:String,completionHandler:@escaping((_ key:String,_ value:Value?) -> Void)){ 149 | queue.async { 150 | if let object = self.memoryCache.object(forKey: key){ 151 | completionHandler(key,object) 152 | }else if let object = self.diskCache.object(forKey: key){ 153 | self.memoryCache.set(forKey: key, value: object) 154 | completionHandler(key,object) 155 | }else{ completionHandler(key,nil) } 156 | } 157 | } 158 | 159 | /** 160 | 根据key查询缓存中是否存在对应的value 161 | @return 如果缓存中存在与key对应的value,返回true,否则返回false 162 | */ 163 | public func isExistsObject(forKey key: String) -> Bool { 164 | return memoryCache.isExistsObject(forKey: key) || diskCache.isExistsObject(forKey: key) 165 | } 166 | 167 | /** 168 | 根据key查询缓存中是否存在对应的value 169 | @param completionHandler: 查询完成后回调 170 | */ 171 | public func isExistsObject(forKey key:String,completionHandler:@escaping((_ key:String,_ contain:Bool) -> Void)) { 172 | queue.async { 173 | let isExists = self.memoryCache.isExistsObject(forKey: key) || self.diskCache.isExistsObject(forKey: key) 174 | completionHandler(key,isExists) 175 | } 176 | } 177 | 178 | /** 179 | 移除所有缓存 180 | */ 181 | public func removeAll(){ 182 | memoryCache.removeAll() 183 | diskCache.removeAll() 184 | } 185 | 186 | /** 187 | 移除所有缓存 188 | @param completionHandler 移除完成后回调 189 | */ 190 | public func removeAll(completionHandler:@escaping(() -> Void)){ 191 | queue.async { 192 | self.memoryCache.removeAll() 193 | self.diskCache.removeAll() 194 | completionHandler() 195 | } 196 | } 197 | 198 | /** 199 | 根据key移除缓存中对应的value 200 | @param key: 要移除的value对应的键 201 | */ 202 | public func removeObject(forKey key: String){ 203 | memoryCache.removeObject(forKey: key) 204 | diskCache.removeObject(forKey: key) 205 | } 206 | 207 | /** 208 | 根据key移除缓存中对应的value 209 | @param key:要移除的value对应的键 210 | @param completionHandler:移除完成后回调 211 | */ 212 | public func removeObject(forKey key: String, completionHandler: @escaping (() -> Void)) { 213 | queue.async { 214 | self.memoryCache.removeObject(forKey: key) 215 | self.diskCache.removeObject(forKey: key) 216 | completionHandler() 217 | } 218 | } 219 | } 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios,'9.0' 2 | use_frameworks! 3 | target 'SwiftlyCacheDemo' do 4 | pod 'SwiftlyCache', '~>1.1.8' 5 | end -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6E2C5BE2242ACF6D0095F3FB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2C5BE1242ACF6D0095F3FB /* AppDelegate.swift */; }; 11 | 6E2C5BE4242ACF6D0095F3FB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2C5BE3242ACF6D0095F3FB /* SceneDelegate.swift */; }; 12 | 6E2C5BE6242ACF6D0095F3FB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2C5BE5242ACF6D0095F3FB /* ViewController.swift */; }; 13 | 6E2C5BE9242ACF6D0095F3FB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2C5BE7242ACF6D0095F3FB /* Main.storyboard */; }; 14 | 6E2C5BEB242ACF6E0095F3FB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6E2C5BEA242ACF6E0095F3FB /* Assets.xcassets */; }; 15 | 6E2C5BEE242ACF6E0095F3FB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E2C5BEC242ACF6E0095F3FB /* LaunchScreen.storyboard */; }; 16 | B79C1BC2B987440EDE7E4083 /* Pods_SwiftlyCacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3BBF9A0DA1DC793B70AB2764 /* Pods_SwiftlyCacheDemo.framework */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 3BBF9A0DA1DC793B70AB2764 /* Pods_SwiftlyCacheDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftlyCacheDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 6E2C5BDE242ACF6D0095F3FB /* SwiftlyCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftlyCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 6E2C5BE1242ACF6D0095F3FB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 6E2C5BE3242ACF6D0095F3FB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 24 | 6E2C5BE5242ACF6D0095F3FB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 6E2C5BE8242ACF6D0095F3FB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 6E2C5BEA242ACF6E0095F3FB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 6E2C5BED242ACF6E0095F3FB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 6E2C5BEF242ACF6E0095F3FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | F9E48B9E18C953F937FB459A /* Pods-SwiftlyCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftlyCacheDemo.release.xcconfig"; path = "Target Support Files/Pods-SwiftlyCacheDemo/Pods-SwiftlyCacheDemo.release.xcconfig"; sourceTree = ""; }; 30 | FCE7DB6A66445FF889B2E8DD /* Pods-SwiftlyCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftlyCacheDemo.debug.xcconfig"; path = "Target Support Files/Pods-SwiftlyCacheDemo/Pods-SwiftlyCacheDemo.debug.xcconfig"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 6E2C5BDB242ACF6D0095F3FB /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | B79C1BC2B987440EDE7E4083 /* Pods_SwiftlyCacheDemo.framework in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 6E2C5BD5242ACF6D0095F3FB = { 46 | isa = PBXGroup; 47 | children = ( 48 | 6E2C5BE0242ACF6D0095F3FB /* SwiftlyCacheDemo */, 49 | 6E2C5BDF242ACF6D0095F3FB /* Products */, 50 | 8D6D37D4FD4432436FDB105F /* Pods */, 51 | 9AB194FAC2C2B619B7D3BB21 /* Frameworks */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 6E2C5BDF242ACF6D0095F3FB /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 6E2C5BDE242ACF6D0095F3FB /* SwiftlyCacheDemo.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 6E2C5BE0242ACF6D0095F3FB /* SwiftlyCacheDemo */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 6E2C5BE1242ACF6D0095F3FB /* AppDelegate.swift */, 67 | 6E2C5BE3242ACF6D0095F3FB /* SceneDelegate.swift */, 68 | 6E2C5BE5242ACF6D0095F3FB /* ViewController.swift */, 69 | 6E2C5BE7242ACF6D0095F3FB /* Main.storyboard */, 70 | 6E2C5BEA242ACF6E0095F3FB /* Assets.xcassets */, 71 | 6E2C5BEC242ACF6E0095F3FB /* LaunchScreen.storyboard */, 72 | 6E2C5BEF242ACF6E0095F3FB /* Info.plist */, 73 | ); 74 | path = SwiftlyCacheDemo; 75 | sourceTree = ""; 76 | }; 77 | 8D6D37D4FD4432436FDB105F /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | FCE7DB6A66445FF889B2E8DD /* Pods-SwiftlyCacheDemo.debug.xcconfig */, 81 | F9E48B9E18C953F937FB459A /* Pods-SwiftlyCacheDemo.release.xcconfig */, 82 | ); 83 | path = Pods; 84 | sourceTree = ""; 85 | }; 86 | 9AB194FAC2C2B619B7D3BB21 /* Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 3BBF9A0DA1DC793B70AB2764 /* Pods_SwiftlyCacheDemo.framework */, 90 | ); 91 | name = Frameworks; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXNativeTarget section */ 97 | 6E2C5BDD242ACF6D0095F3FB /* SwiftlyCacheDemo */ = { 98 | isa = PBXNativeTarget; 99 | buildConfigurationList = 6E2C5BF2242ACF6E0095F3FB /* Build configuration list for PBXNativeTarget "SwiftlyCacheDemo" */; 100 | buildPhases = ( 101 | 25D1F93F293140DA0C3D9EB9 /* [CP] Check Pods Manifest.lock */, 102 | 6E2C5BDA242ACF6D0095F3FB /* Sources */, 103 | 6E2C5BDB242ACF6D0095F3FB /* Frameworks */, 104 | 6E2C5BDC242ACF6D0095F3FB /* Resources */, 105 | D8D432A9D07CDA9DCBF4EDBC /* [CP] Embed Pods Frameworks */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = SwiftlyCacheDemo; 112 | productName = SwiftlyCacheDemo; 113 | productReference = 6E2C5BDE242ACF6D0095F3FB /* SwiftlyCacheDemo.app */; 114 | productType = "com.apple.product-type.application"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | 6E2C5BD6242ACF6D0095F3FB /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | LastSwiftUpdateCheck = 1130; 123 | LastUpgradeCheck = 1130; 124 | ORGANIZATIONNAME = "黄琳川"; 125 | TargetAttributes = { 126 | 6E2C5BDD242ACF6D0095F3FB = { 127 | CreatedOnToolsVersion = 11.3; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = 6E2C5BD9242ACF6D0095F3FB /* Build configuration list for PBXProject "SwiftlyCacheDemo" */; 132 | compatibilityVersion = "Xcode 9.3"; 133 | developmentRegion = en; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = 6E2C5BD5242ACF6D0095F3FB; 140 | productRefGroup = 6E2C5BDF242ACF6D0095F3FB /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | 6E2C5BDD242ACF6D0095F3FB /* SwiftlyCacheDemo */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | 6E2C5BDC242ACF6D0095F3FB /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 6E2C5BEE242ACF6E0095F3FB /* LaunchScreen.storyboard in Resources */, 155 | 6E2C5BEB242ACF6E0095F3FB /* Assets.xcassets in Resources */, 156 | 6E2C5BE9242ACF6D0095F3FB /* Main.storyboard in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXShellScriptBuildPhase section */ 163 | 25D1F93F293140DA0C3D9EB9 /* [CP] Check Pods Manifest.lock */ = { 164 | isa = PBXShellScriptBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | ); 168 | inputFileListPaths = ( 169 | ); 170 | inputPaths = ( 171 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 172 | "${PODS_ROOT}/Manifest.lock", 173 | ); 174 | name = "[CP] Check Pods Manifest.lock"; 175 | outputFileListPaths = ( 176 | ); 177 | outputPaths = ( 178 | "$(DERIVED_FILE_DIR)/Pods-SwiftlyCacheDemo-checkManifestLockResult.txt", 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | shellPath = /bin/sh; 182 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 183 | showEnvVarsInLog = 0; 184 | }; 185 | D8D432A9D07CDA9DCBF4EDBC /* [CP] Embed Pods Frameworks */ = { 186 | isa = PBXShellScriptBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | ); 190 | inputFileListPaths = ( 191 | "${PODS_ROOT}/Target Support Files/Pods-SwiftlyCacheDemo/Pods-SwiftlyCacheDemo-frameworks-${CONFIGURATION}-input-files.xcfilelist", 192 | ); 193 | name = "[CP] Embed Pods Frameworks"; 194 | outputFileListPaths = ( 195 | "${PODS_ROOT}/Target Support Files/Pods-SwiftlyCacheDemo/Pods-SwiftlyCacheDemo-frameworks-${CONFIGURATION}-output-files.xcfilelist", 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | shellPath = /bin/sh; 199 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftlyCacheDemo/Pods-SwiftlyCacheDemo-frameworks.sh\"\n"; 200 | showEnvVarsInLog = 0; 201 | }; 202 | /* End PBXShellScriptBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | 6E2C5BDA242ACF6D0095F3FB /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 6E2C5BE6242ACF6D0095F3FB /* ViewController.swift in Sources */, 210 | 6E2C5BE2242ACF6D0095F3FB /* AppDelegate.swift in Sources */, 211 | 6E2C5BE4242ACF6D0095F3FB /* SceneDelegate.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 6E2C5BE7242ACF6D0095F3FB /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 6E2C5BE8242ACF6D0095F3FB /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | 6E2C5BEC242ACF6E0095F3FB /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 6E2C5BED242ACF6E0095F3FB /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 6E2C5BF0242ACF6E0095F3FB /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_ENABLE_OBJC_WEAK = YES; 248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_COMMA = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 263 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 264 | CLANG_WARN_STRICT_PROTOTYPES = YES; 265 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 266 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | COPY_PHASE_STRIP = NO; 270 | DEBUG_INFORMATION_FORMAT = dwarf; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | ENABLE_TESTABILITY = YES; 273 | GCC_C_LANGUAGE_STANDARD = gnu11; 274 | GCC_DYNAMIC_NO_PIC = NO; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_OPTIMIZATION_LEVEL = 0; 277 | GCC_PREPROCESSOR_DEFINITIONS = ( 278 | "DEBUG=1", 279 | "$(inherited)", 280 | ); 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 288 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 289 | MTL_FAST_MATH = YES; 290 | ONLY_ACTIVE_ARCH = YES; 291 | SDKROOT = iphoneos; 292 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | }; 295 | name = Debug; 296 | }; 297 | 6E2C5BF1242ACF6E0095F3FB /* Release */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ALWAYS_SEARCH_USER_PATHS = NO; 301 | CLANG_ANALYZER_NONNULL = YES; 302 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_ENABLE_OBJC_WEAK = YES; 308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 309 | CLANG_WARN_BOOL_CONVERSION = YES; 310 | CLANG_WARN_COMMA = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 314 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | MTL_FAST_MATH = YES; 344 | SDKROOT = iphoneos; 345 | SWIFT_COMPILATION_MODE = wholemodule; 346 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 347 | VALIDATE_PRODUCT = YES; 348 | }; 349 | name = Release; 350 | }; 351 | 6E2C5BF3242ACF6E0095F3FB /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | baseConfigurationReference = FCE7DB6A66445FF889B2E8DD /* Pods-SwiftlyCacheDemo.debug.xcconfig */; 354 | buildSettings = { 355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 356 | CODE_SIGN_STYLE = Automatic; 357 | INFOPLIST_FILE = SwiftlyCacheDemo/Info.plist; 358 | LD_RUNPATH_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "@executable_path/Frameworks", 361 | ); 362 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaolin.SwiftlyCacheDemo; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_VERSION = 5.0; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 6E2C5BF4242ACF6E0095F3FB /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | baseConfigurationReference = F9E48B9E18C953F937FB459A /* Pods-SwiftlyCacheDemo.release.xcconfig */; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | CODE_SIGN_STYLE = Automatic; 375 | INFOPLIST_FILE = SwiftlyCacheDemo/Info.plist; 376 | LD_RUNPATH_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "@executable_path/Frameworks", 379 | ); 380 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaolin.SwiftlyCacheDemo; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | SWIFT_VERSION = 5.0; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Release; 386 | }; 387 | /* End XCBuildConfiguration section */ 388 | 389 | /* Begin XCConfigurationList section */ 390 | 6E2C5BD9242ACF6D0095F3FB /* Build configuration list for PBXProject "SwiftlyCacheDemo" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 6E2C5BF0242ACF6E0095F3FB /* Debug */, 394 | 6E2C5BF1242ACF6E0095F3FB /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | 6E2C5BF2242ACF6E0095F3FB /* Build configuration list for PBXNativeTarget "SwiftlyCacheDemo" */ = { 400 | isa = XCConfigurationList; 401 | buildConfigurations = ( 402 | 6E2C5BF3242ACF6E0095F3FB /* Debug */, 403 | 6E2C5BF4242ACF6E0095F3FB /* Release */, 404 | ); 405 | defaultConfigurationIsVisible = 0; 406 | defaultConfigurationName = Release; 407 | }; 408 | /* End XCConfigurationList section */ 409 | }; 410 | rootObject = 6E2C5BD6242ACF6D0095F3FB /* Project object */; 411 | } 412 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftlyCacheDemo 4 | // 5 | // Created by 黄琳川 on 2020/3/25. 6 | // Copyright © 2020 黄琳川. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SwiftlyCacheDemo 4 | // 5 | // Created by 黄琳川 on 2020/3/25. 6 | // Copyright © 2020 黄琳川. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /SwiftlyCacheDemo/SwiftlyCacheDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftlyCacheDemo 4 | // 5 | // Created by 黄琳川 on 2020/3/25. 6 | // Copyright © 2020 黄琳川. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftlyCache 11 | 12 | struct Student:Codable { 13 | var name:String 14 | var age:Int 15 | 16 | init(name:String,age:Int) { 17 | self.name = name 18 | self.age = age 19 | } 20 | } 21 | 22 | class ViewController: UIViewController { 23 | let cache = MultiCache() 24 | let memoryCache = MemoryCache() 25 | let diskCache = DiskCache(path: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] + "SwiftlyDiskCache") 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | // Do any additional setup after loading the view. 30 | 31 | /** 32 | MultiCache 33 | 34 | setObjectTest() 35 | MultiCacheGenerator() 36 | isExistsObjectForKeyTest() 37 | removeAll() 38 | getObjectTest() 39 | multiCacheGenerator() 40 | */ 41 | 42 | /** 43 | memorySetObjectTest() 44 | memoryGetObjectTest() 45 | memoryRemoveObject() 46 | memoryIsExistsObjectForKeyTest() 47 | memoryRemoveAll() 48 | memoryCacheGenerator() 49 | */ 50 | 51 | /** 52 | diskCacheGenerator() 53 | diskCacheIsExistsObjectForKeyTest() 54 | diskCacheRemoveObject() 55 | diskCacheRemoveAll() 56 | diskCacheSetObjectTest() 57 | diskCacheGetObjectTest() 58 | */ 59 | } 60 | /** 61 | MultiCache测试用例 62 | */ 63 | 64 | func multiCacheGenerator(){ 65 | let flatMapResult = cache.compactMap { $0 } 66 | print("flatMapResult:\(flatMapResult)") 67 | 68 | let filterResult = cache.filter { (key,object) -> Bool in 69 | return key == "shirley2" 70 | } 71 | print(filterResult) 72 | 73 | cache.forEach { print($0)} 74 | 75 | let values = cache.map { return $0 } 76 | print(values) 77 | 78 | for (key,object) in cache { 79 | print("key1:\(key),object1:\(object)") 80 | } 81 | } 82 | 83 | func setObjectTest(){ 84 | let shirley = Student(name: "shirley", age: 30) 85 | /** 86 | 返回值也可以不需要 87 | */ 88 | let fin = cache.set(forKey: "shirley10", value: shirley) 89 | print("当前数据缓存是否成功(fin为Bool类型):\(fin)") 90 | /* 91 | cost:缓存对象大小(字节为单位) 92 | 一般是在需要设置totalCostLimit才需要设置cost,默认为0 93 | 当所有缓存对象总大小超出totalCostLimit,会丢弃掉一些缓存数据 94 | **/ 95 | cache.set(forKey: "shirley1", value: shirley, cost: 5) 96 | 97 | /** 98 | 异步调用set 99 | */ 100 | cache.set(forKey: "shirley2", value: shirley) { (key, fin) in 101 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 102 | } 103 | cache.set(forKey: "shirley3", value: shirley, cost: 0) { (key, fin) in 104 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 105 | } 106 | cache.set(forKey: "shirley4", value: nil, cost: 0) { (key, fin) in 107 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 108 | } 109 | } 110 | 111 | func MultiCacheGenerator(){ 112 | for (key,object) in cache{ 113 | print("key:\(key),object:\(object)") 114 | } 115 | } 116 | 117 | func getObjectTest(){ 118 | if let object = cache.object(forKey: "shirley1"){ 119 | print("当前Student是:\(object)") 120 | } 121 | 122 | cache.object(forKey: "shirley2") { (key, value) in 123 | if let object = value{ 124 | print("当前Student是:\(object)") 125 | } 126 | } 127 | 128 | let object1 = cache.object(forKey: "shirley30") 129 | print("有没有这个stundent:\(object1)") 130 | 131 | cache.object(forKey: "shirley20") { (key, value) in 132 | print("有没有这个stundent1:\(value)") 133 | } 134 | } 135 | 136 | func isExistsObjectForKeyTest(){ 137 | let fin = cache.isExistsObjectForKey(forKey: "shirley20") 138 | print("是否存在key对应的value:\(fin)") 139 | 140 | cache.isExistsObjectForKey(forKey: "shirley10") { (key, fin) in 141 | print("是否存在key\(key)对应的value:\(fin)") 142 | } 143 | } 144 | 145 | func removeObject(){ 146 | cache.removeObject(forKey: "shirley20") 147 | cache.removeObject(forKey: "shirley2") { 148 | print("") 149 | } 150 | } 151 | 152 | func removeAll(){ 153 | cache.removeAll() 154 | cache.removeAll { 155 | print("") 156 | } 157 | } 158 | 159 | /** 160 | MemoryCache测试用例 161 | */ 162 | 163 | func memoryCacheGenerator(){ 164 | 165 | let flatMapResult = memoryCache.compactMap { $0 } 166 | print("flatMapResult:\(flatMapResult)") 167 | 168 | let filterResult = memoryCache.filter { (key,object) -> Bool in 169 | return key == "shirley2" 170 | } 171 | print(filterResult) 172 | 173 | memoryCache.forEach { print($0)} 174 | 175 | let values = memoryCache.map { return $0 } 176 | print(values) 177 | 178 | for (key,object) in memoryCache { 179 | print("key1:\(key),object1:\(object)") 180 | } 181 | } 182 | 183 | func memorySetObjectTest(){ 184 | let shirley = Student(name: "shirley", age: 50) 185 | /** 186 | 返回值也可以不需要 187 | */ 188 | let fin = memoryCache.set(forKey: "shirley19", value: shirley,cost: 0) 189 | print("当前数据缓存是否成功(fin为Bool类型):\(fin)") 190 | /* 191 | cost:缓存对象大小(字节为单位) 192 | 一般是在需要设置totalCostLimit才需要设置cost,默认为0 193 | 当所有缓存对象总大小超出totalCostLimit,会丢弃掉一些缓存数据 194 | **/ 195 | memoryCache.set(forKey: "shirley1", value: shirley, cost: 5) 196 | /** 197 | 异步调用set 198 | */ 199 | memoryCache.set(forKey: "shirley2", value: shirley) { (key, fin) in 200 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 201 | } 202 | memoryCache.set(forKey: "shirley3", value: shirley, cost: 0) { (key, fin) in 203 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 204 | } 205 | memoryCache.set(forKey: "shirley4", value: nil, cost: 0) { (key, fin) in 206 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 207 | } 208 | } 209 | 210 | func memoryGetObjectTest(){ 211 | if let object = memoryCache.object(forKey: "shirley1"){ 212 | print("当前Student是:\(object)") 213 | } 214 | 215 | memoryCache.object(forKey: "shirley2") { (key, value) in 216 | if let object = value{ 217 | print("当前Student是:\(object)") 218 | } 219 | } 220 | 221 | let object1 = memoryCache.object(forKey: "shirley30") 222 | print("有没有这个stundent:\(object1)") 223 | 224 | memoryCache.object(forKey: "shirley20") { (key, value) in 225 | print("有没有这个stundent1:\(value)") 226 | } 227 | } 228 | 229 | func memoryIsExistsObjectForKeyTest(){ 230 | let fin = memoryCache.isExistsObjectForKey(forKey: "shirley2") 231 | print("是否存在key对应的value:\(fin)") 232 | 233 | memoryCache.isExistsObjectForKey(forKey: "shirley1") { (key, fin) in 234 | print("是否存在key\(key)对应的value:\(fin)") 235 | } 236 | } 237 | 238 | func memoryRemoveObject(){ 239 | memoryCache.removeObject(forKey: "shirley20") 240 | memoryCache.removeObject(forKey: "shirley2") { 241 | print("") 242 | } 243 | 244 | } 245 | 246 | func memoryRemoveAll(){ 247 | memoryCache.removeAll() 248 | memoryCache.removeAll { 249 | print("") 250 | } 251 | } 252 | 253 | 254 | /** 255 | DiskCache 256 | */ 257 | func diskCacheGenerator(){ 258 | 259 | let flatMapResult = diskCache.compactMap { $0 } 260 | print("flatMapResult:\(flatMapResult)") 261 | 262 | let filterResult = diskCache.filter { (key,object) -> Bool in 263 | return key == "shirley222" 264 | } 265 | print(filterResult) 266 | 267 | diskCache.forEach { print($0)} 268 | 269 | let values = diskCache.map { return $0 } 270 | print(values) 271 | 272 | for (key,object) in diskCache { 273 | print("key1:\(key),object1:\(object)") 274 | } 275 | } 276 | 277 | func diskCacheSetObjectTest(){ 278 | let shirley = Student(name: "shirley", age: 50) 279 | /** 280 | 返回值也可以不需要 281 | */ 282 | let fin = diskCache.set(forKey: "shirley19", value: shirley,cost: 5) 283 | print("当前数据缓存是否成功(fin为Bool类型):\(fin)") 284 | /* 285 | cost:缓存对象大小(字节为单位) 286 | 一般是在需要设置totalCostLimit才需要设置cost,默认为0 287 | 当所有缓存对象总大小超出totalCostLimit,会丢弃掉一些缓存数据 288 | **/ 289 | // diskCache.maxCountLimit = 1 290 | // diskCache.maxSize = 80 291 | diskCache.set(forKey: "shirley1", value: shirley, cost: 5) 292 | /** 293 | 异步调用set 294 | */ 295 | diskCache.set(forKey: "shirley222", value: shirley) { (key, fin) in 296 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 297 | } 298 | diskCache.set(forKey: "shirley3333", value: shirley, cost: 0) { (key, fin) in 299 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 300 | } 301 | diskCache.set(forKey: "shirley444", value: shirley, cost: 0) { (key, fin) in 302 | print("当前缓存对象对应的key:\(key),当前数据缓存是否成功(fin为Bool类型):\(fin)") 303 | } 304 | } 305 | 306 | func diskCacheGetObjectTest(){ 307 | if let object = diskCache.object(forKey: "shirley1"){ 308 | print("当前Student是:\(object)") 309 | } 310 | 311 | diskCache.object(forKey: "shirley2") { (key, value) in 312 | if let object = value{ 313 | print("当前Student是:\(object)") 314 | } 315 | } 316 | 317 | let object1 = diskCache.object(forKey: "shirley30") 318 | print("有没有这个stundent:\(object1)") 319 | 320 | diskCache.object(forKey: "shirley19") { (key, value) in 321 | print("有没有这个stundent1:\(value)") 322 | } 323 | } 324 | 325 | func diskCacheIsExistsObjectForKeyTest(){ 326 | let fin = diskCache.isExistsObjectForKey(forKey: "shirley2") 327 | print("是否存在key对应的value:\(fin)") 328 | 329 | diskCache.isExistsObjectForKey(forKey: "shirley1") { (key, fin) in 330 | print("是否存在key\(key)对应的value:\(fin)") 331 | } 332 | } 333 | 334 | func diskCacheRemoveObject(){ 335 | diskCache.removeObject(forKey: "shirley20") 336 | diskCache.removeObject(forKey: "shirley2") { 337 | print("") 338 | } 339 | } 340 | 341 | func diskCacheRemoveAll(){ 342 | diskCache.removeAll() 343 | diskCache.removeAll { 344 | print("") 345 | } 346 | } 347 | } 348 | 349 | -------------------------------------------------------------------------------- /frameworks/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /frameworks/SwiftlyCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6E18423F2427B4DE00814B97 /* MemoryStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1842392427B4DE00814B97 /* MemoryStorage.swift */; }; 11 | 6E1842402427B4DE00814B97 /* CacheAware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E18423A2427B4DE00814B97 /* CacheAware.swift */; }; 12 | 6E1842412427B4DE00814B97 /* DiskStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E18423B2427B4DE00814B97 /* DiskStorage.swift */; }; 13 | 6E1842422427B4DE00814B97 /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E18423C2427B4DE00814B97 /* MemoryCache.swift */; }; 14 | 6E1842432427B4DE00814B97 /* DiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E18423D2427B4DE00814B97 /* DiskCache.swift */; }; 15 | 6E1842442427B4DE00814B97 /* MultiCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E18423E2427B4DE00814B97 /* MultiCache.swift */; }; 16 | 6E202850242530FD0042081D /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E20284F242530FD0042081D /* libsqlite3.tbd */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 6E0E749824226DE400547166 /* SwiftlyCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftlyCache.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 6E0E74A324226E3800547166 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 22 | 6E1842392427B4DE00814B97 /* MemoryStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStorage.swift; sourceTree = ""; }; 23 | 6E18423A2427B4DE00814B97 /* CacheAware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheAware.swift; sourceTree = ""; }; 24 | 6E18423B2427B4DE00814B97 /* DiskStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskStorage.swift; sourceTree = ""; }; 25 | 6E18423C2427B4DE00814B97 /* MemoryCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = ""; }; 26 | 6E18423D2427B4DE00814B97 /* DiskCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiskCache.swift; sourceTree = ""; }; 27 | 6E18423E2427B4DE00814B97 /* MultiCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiCache.swift; sourceTree = ""; }; 28 | 6E20284F242530FD0042081D /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 6E0E749524226DE400547166 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 6E202850242530FD0042081D /* libsqlite3.tbd in Frameworks */, 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | 6E0E748E24226DE400547166 = { 44 | isa = PBXGroup; 45 | children = ( 46 | 6E1842382427B4DE00814B97 /* SwiftlyCache */, 47 | 6E0E74A324226E3800547166 /* Info.plist */, 48 | 6E0E749924226DE400547166 /* Products */, 49 | 6E20284E242530FD0042081D /* Frameworks */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 6E0E749924226DE400547166 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 6E0E749824226DE400547166 /* SwiftlyCache.framework */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 6E1842382427B4DE00814B97 /* SwiftlyCache */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 6E1842392427B4DE00814B97 /* MemoryStorage.swift */, 65 | 6E18423A2427B4DE00814B97 /* CacheAware.swift */, 66 | 6E18423B2427B4DE00814B97 /* DiskStorage.swift */, 67 | 6E18423C2427B4DE00814B97 /* MemoryCache.swift */, 68 | 6E18423D2427B4DE00814B97 /* DiskCache.swift */, 69 | 6E18423E2427B4DE00814B97 /* MultiCache.swift */, 70 | ); 71 | name = SwiftlyCache; 72 | path = ../SwiftlyCache; 73 | sourceTree = ""; 74 | }; 75 | 6E20284E242530FD0042081D /* Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 6E20284F242530FD0042081D /* libsqlite3.tbd */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | /* End PBXGroup section */ 84 | 85 | /* Begin PBXHeadersBuildPhase section */ 86 | 6E0E749324226DE400547166 /* Headers */ = { 87 | isa = PBXHeadersBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXHeadersBuildPhase section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 6E0E749724226DE400547166 /* SwiftlyCache */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 6E0E74A024226DE400547166 /* Build configuration list for PBXNativeTarget "SwiftlyCache" */; 99 | buildPhases = ( 100 | 6E0E749324226DE400547166 /* Headers */, 101 | 6E0E749424226DE400547166 /* Sources */, 102 | 6E0E749524226DE400547166 /* Frameworks */, 103 | 6E0E749624226DE400547166 /* Resources */, 104 | ); 105 | buildRules = ( 106 | ); 107 | dependencies = ( 108 | ); 109 | name = SwiftlyCache; 110 | productName = SwiftlyCache; 111 | productReference = 6E0E749824226DE400547166 /* SwiftlyCache.framework */; 112 | productType = "com.apple.product-type.framework"; 113 | }; 114 | /* End PBXNativeTarget section */ 115 | 116 | /* Begin PBXProject section */ 117 | 6E0E748F24226DE400547166 /* Project object */ = { 118 | isa = PBXProject; 119 | attributes = { 120 | LastUpgradeCheck = 1130; 121 | ORGANIZATIONNAME = "黄琳川"; 122 | TargetAttributes = { 123 | 6E0E749724226DE400547166 = { 124 | CreatedOnToolsVersion = 11.3; 125 | }; 126 | }; 127 | }; 128 | buildConfigurationList = 6E0E749224226DE400547166 /* Build configuration list for PBXProject "SwiftlyCache" */; 129 | compatibilityVersion = "Xcode 9.3"; 130 | developmentRegion = en; 131 | hasScannedForEncodings = 0; 132 | knownRegions = ( 133 | en, 134 | Base, 135 | ); 136 | mainGroup = 6E0E748E24226DE400547166; 137 | productRefGroup = 6E0E749924226DE400547166 /* Products */; 138 | projectDirPath = ""; 139 | projectRoot = ""; 140 | targets = ( 141 | 6E0E749724226DE400547166 /* SwiftlyCache */, 142 | ); 143 | }; 144 | /* End PBXProject section */ 145 | 146 | /* Begin PBXResourcesBuildPhase section */ 147 | 6E0E749624226DE400547166 /* Resources */ = { 148 | isa = PBXResourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXResourcesBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | 6E0E749424226DE400547166 /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 6E18423F2427B4DE00814B97 /* MemoryStorage.swift in Sources */, 162 | 6E1842422427B4DE00814B97 /* MemoryCache.swift in Sources */, 163 | 6E1842442427B4DE00814B97 /* MultiCache.swift in Sources */, 164 | 6E1842402427B4DE00814B97 /* CacheAware.swift in Sources */, 165 | 6E1842412427B4DE00814B97 /* DiskStorage.swift in Sources */, 166 | 6E1842432427B4DE00814B97 /* DiskCache.swift in Sources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXSourcesBuildPhase section */ 171 | 172 | /* Begin XCBuildConfiguration section */ 173 | 6E0E749E24226DE400547166 /* Debug */ = { 174 | isa = XCBuildConfiguration; 175 | buildSettings = { 176 | ALWAYS_SEARCH_USER_PATHS = NO; 177 | CLANG_ANALYZER_NONNULL = YES; 178 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 179 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 180 | CLANG_CXX_LIBRARY = "libc++"; 181 | CLANG_ENABLE_MODULES = YES; 182 | CLANG_ENABLE_OBJC_ARC = YES; 183 | CLANG_ENABLE_OBJC_WEAK = YES; 184 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 185 | CLANG_WARN_BOOL_CONVERSION = YES; 186 | CLANG_WARN_COMMA = YES; 187 | CLANG_WARN_CONSTANT_CONVERSION = YES; 188 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 189 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 190 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 191 | CLANG_WARN_EMPTY_BODY = YES; 192 | CLANG_WARN_ENUM_CONVERSION = YES; 193 | CLANG_WARN_INFINITE_RECURSION = YES; 194 | CLANG_WARN_INT_CONVERSION = YES; 195 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 196 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 197 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 200 | CLANG_WARN_STRICT_PROTOTYPES = YES; 201 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 202 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 203 | CLANG_WARN_UNREACHABLE_CODE = YES; 204 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 205 | COPY_PHASE_STRIP = NO; 206 | CURRENT_PROJECT_VERSION = 1; 207 | DEBUG_INFORMATION_FORMAT = dwarf; 208 | ENABLE_STRICT_OBJC_MSGSEND = YES; 209 | ENABLE_TESTABILITY = YES; 210 | GCC_C_LANGUAGE_STANDARD = gnu11; 211 | GCC_DYNAMIC_NO_PIC = NO; 212 | GCC_NO_COMMON_BLOCKS = YES; 213 | GCC_OPTIMIZATION_LEVEL = 0; 214 | GCC_PREPROCESSOR_DEFINITIONS = ( 215 | "DEBUG=1", 216 | "$(inherited)", 217 | ); 218 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 219 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 220 | GCC_WARN_UNDECLARED_SELECTOR = YES; 221 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 222 | GCC_WARN_UNUSED_FUNCTION = YES; 223 | GCC_WARN_UNUSED_VARIABLE = YES; 224 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 226 | MTL_FAST_MATH = YES; 227 | ONLY_ACTIVE_ARCH = YES; 228 | SDKROOT = iphoneos; 229 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 230 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 231 | VERSIONING_SYSTEM = "apple-generic"; 232 | VERSION_INFO_PREFIX = ""; 233 | }; 234 | name = Debug; 235 | }; 236 | 6E0E749F24226DE400547166 /* Release */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_ENABLE_OBJC_WEAK = YES; 247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 248 | CLANG_WARN_BOOL_CONVERSION = YES; 249 | CLANG_WARN_COMMA = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 254 | CLANG_WARN_EMPTY_BODY = YES; 255 | CLANG_WARN_ENUM_CONVERSION = YES; 256 | CLANG_WARN_INFINITE_RECURSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 260 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 262 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 263 | CLANG_WARN_STRICT_PROTOTYPES = YES; 264 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 265 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 266 | CLANG_WARN_UNREACHABLE_CODE = YES; 267 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 268 | COPY_PHASE_STRIP = NO; 269 | CURRENT_PROJECT_VERSION = 1; 270 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 271 | ENABLE_NS_ASSERTIONS = NO; 272 | ENABLE_STRICT_OBJC_MSGSEND = YES; 273 | GCC_C_LANGUAGE_STANDARD = gnu11; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 276 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 277 | GCC_WARN_UNDECLARED_SELECTOR = YES; 278 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 279 | GCC_WARN_UNUSED_FUNCTION = YES; 280 | GCC_WARN_UNUSED_VARIABLE = YES; 281 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 282 | MTL_ENABLE_DEBUG_INFO = NO; 283 | MTL_FAST_MATH = YES; 284 | SDKROOT = iphoneos; 285 | SWIFT_COMPILATION_MODE = wholemodule; 286 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 287 | VALIDATE_PRODUCT = YES; 288 | VERSIONING_SYSTEM = "apple-generic"; 289 | VERSION_INFO_PREFIX = ""; 290 | }; 291 | name = Release; 292 | }; 293 | 6E0E74A124226DE400547166 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | CODE_SIGN_STYLE = Automatic; 297 | DEFINES_MODULE = YES; 298 | DYLIB_COMPATIBILITY_VERSION = 1; 299 | DYLIB_CURRENT_VERSION = 1; 300 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 301 | INFOPLIST_FILE = Info.plist; 302 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 303 | LD_RUNPATH_SEARCH_PATHS = ( 304 | "$(inherited)", 305 | "@executable_path/Frameworks", 306 | "@loader_path/Frameworks", 307 | ); 308 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaolin.SwiftlyCache; 309 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 310 | SKIP_INSTALL = YES; 311 | SWIFT_VERSION = 5.0; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Debug; 315 | }; 316 | 6E0E74A224226DE400547166 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | CODE_SIGN_STYLE = Automatic; 320 | DEFINES_MODULE = YES; 321 | DYLIB_COMPATIBILITY_VERSION = 1; 322 | DYLIB_CURRENT_VERSION = 1; 323 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 324 | INFOPLIST_FILE = Info.plist; 325 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 326 | LD_RUNPATH_SEARCH_PATHS = ( 327 | "$(inherited)", 328 | "@executable_path/Frameworks", 329 | "@loader_path/Frameworks", 330 | ); 331 | PRODUCT_BUNDLE_IDENTIFIER = com.xiaolin.SwiftlyCache; 332 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 333 | SKIP_INSTALL = YES; 334 | SWIFT_VERSION = 5.0; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | }; 337 | name = Release; 338 | }; 339 | /* End XCBuildConfiguration section */ 340 | 341 | /* Begin XCConfigurationList section */ 342 | 6E0E749224226DE400547166 /* Build configuration list for PBXProject "SwiftlyCache" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 6E0E749E24226DE400547166 /* Debug */, 346 | 6E0E749F24226DE400547166 /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | 6E0E74A024226DE400547166 /* Build configuration list for PBXNativeTarget "SwiftlyCache" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | 6E0E74A124226DE400547166 /* Debug */, 355 | 6E0E74A224226DE400547166 /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | /* End XCConfigurationList section */ 361 | }; 362 | rootObject = 6E0E748F24226DE400547166 /* Project object */; 363 | } 364 | -------------------------------------------------------------------------------- /frameworks/SwiftlyCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frameworks/SwiftlyCache.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frameworks/SwiftlyCache.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frameworks/SwiftlyCache.xcodeproj/xcshareddata/xcschemes/SwiftlyCache.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | --------------------------------------------------------------------------------