Cache - Nothing but Cache.

Overview

Cache

CI Status Version Carthage Compatible License Platform Documentation Swift

Table of Contents

Description

Cache Icon

Cache doesn't claim to be unique in this area, but it's not another monster library that gives you a god's power. It does nothing but caching, but it does it well. It offers a good public API with out-of-box implementations and great customization possibilities. Cache utilizes Codable in Swift 4 to perform serialization.

Read the story here Open Source Stories: From Cachable to Generic Storage in Cache

Key features

  • Work with Swift 4 Codable. Anything conforming to Codable will be saved and loaded easily by Storage.
  • Hybrid with memory and disk storage.
  • Many options via DiskConfig and MemoryConfig.
  • Support expiry and clean up of expired objects.
  • Thread safe. Operations can be accessed from any queue.
  • Sync by default. Also support Async APIs.
  • Extensive unit test coverage and great documentation.
  • iOS, tvOS and macOS support.

Usage

Storage

Cache is built based on Chain-of-responsibility pattern, in which there are many processing objects, each knows how to do 1 task and delegates to the next one, so can you compose Storages the way you like.

For now the following Storage are supported

  • MemoryStorage: save object to memory.
  • DiskStorage: save object to disk.
  • HybridStorage: save object to memory and disk, so you get persistented object on disk, while fast access with in memory objects.
  • SyncStorage: blocking APIs, all read and write operations are scheduled in a serial queue, all sync manner.
  • AsyncStorage: non-blocking APIs, operations are scheduled in an internal queue for serial processing. No read and write should happen at the same time.

Although you can use those Storage at your discretion, you don't have to. Because we also provide a convenient Storage which uses HybridStorage under the hood, while exposes sync and async APIs through SyncStorage and AsyncStorage.

All you need to do is to specify the configuration you want with DiskConfig and MemoryConfig. The default configurations are good to go, but you can customise a lot.

) ">
let diskConfig = DiskConfig(name: "Floppy")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)

let storage = try? Storage(
  diskConfig: diskConfig,
  memoryConfig: memoryConfig,
  transformer: TransformerFactory.forCodable(ofType: User.self) // Storage
)

Generic, Type safety and Transformer

All Storage now are generic by default, so you can get a type safety experience. Once you create a Storage, it has a type constraint that you don't need to specify type for each operation afterwards.

If you want to change the type, Cache offers transform functions, look for Transformer and TransformerFactory for built-in transformers.

imageStorage.setObject(image, forKey: "image") let stringStorage = storage.transformCodable(ofType: String.self) // Storage stringStorage.setObject("hello world", forKey: "string") ">
let storage: Storage<String, User> = ...
storage.setObject(superman, forKey: "user")

let imageStorage = storage.transformImage() // Storage
imageStorage.setObject(image, forKey: "image")

let stringStorage = storage.transformCodable(ofType: String.self) // Storage
stringStorage.setObject("hello world", forKey: "string")

Each transformation allows you to work with a specific type, however the underlying caching mechanism remains the same, you're working with the same Storage, just with different type annotation. You can also create different Storage for each type if you want.

Transformer is necessary because the need of serialising and deserialising objects to and from Data for disk persistency. Cache provides default Transformer for Data, Codable and UIImage/NSImage

Codable types

Storage supports any objects that conform to Codable protocol. You can make your own things conform to Codable so that can be saved and loaded from Storage.

The supported types are

  • Primitives like Int, Float, String, Bool, ...
  • Array of primitives like [Int], [Float], [Double], ...
  • Set of primitives like Set, Set, ...
  • Simply dictionary like [String: Int], [String: String], ...
  • Date
  • URL
  • Data

Error handling

Error handling is done via try catch. Storage throws errors in terms of StorageError.

public enum StorageError: Error {
  /// Object can not be found
  case notFound
  /// Object is found, but casting to requested type failed
  case typeNotMatch
  /// The file attributes are malformed
  case malformedFileAttributes
  /// Can't perform Decode
  case decodingFailed
  /// Can't perform Encode
  case encodingFailed
  /// The storage has been deallocated
  case deallocated
  /// Fail to perform transformation to or from Data
  case transformerFail
}

There can be errors because of disk problem or type mismatch when loading from storage, so if want to handle errors, you need to do try catch

do {
  let storage = try Storage(diskConfig: diskConfig, memoryConfig: memoryConfig)
} catch {
  print(error)
}

Configuration

Here is how you can play with many configuration options

let diskConfig = DiskConfig(
  // The name of disk storage, this will be used as folder name within directory
  name: "Floppy",
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*3600)),
  // Maximum size of the disk cache storage (in bytes)
  maxSize: 10000,
  // Where to store the disk cache. If nil, it is placed in `cachesDirectory` directory.
  directory: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
    appropriateFor: nil, create: true).appendingPathComponent("MyPreferences"),
  // Data protection is used to store files in an encrypted format on disk and to decrypt them on demand
  protectionType: .complete
)
let memoryConfig = MemoryConfig(
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*60)),
  /// The maximum number of objects in memory the cache should hold
  countLimit: 50,
  /// The maximum total cost that the cache can hold before it starts evicting objects
  totalCostLimit: 0
)

On iOS, tvOS we can also specify protectionType on DiskConfig to add a level of security to files stored on disk by your app in the app’s container. For more information, see FileProtectionType

Sync APIs

Storage is sync by default and is thread safe, you can access it from any queues. All Sync functions are constrained by StorageAware protocol.

// Save to storage
try? storage.setObject(10, forKey: "score")
try? storage.setObject("Oslo", forKey: "my favorite city", expiry: .never)
try? storage.setObject(["alert", "sounds", "badge"], forKey: "notifications")
try? storage.setObject(data, forKey: "a bunch of bytes")
try? storage.setObject(authorizeURL, forKey: "authorization URL")

// Load from storage
let score = try? storage.object(forKey: "score")
let favoriteCharacter = try? storage.object(forKey: "my favorite city")

// Check if an object exists
let hasFavoriteCharacter = try? storage.existsObject(forKey: "my favorite city")

// Remove an object in storage
try? storage.removeObject(forKey: "my favorite city")

// Remove all objects
try? storage.removeAll()

// Remove expired objects
try? storage.removeExpiredObjects()

Entry

There is time you want to get object together with its expiry information and meta data. You can use Entry

let entry = try? storage.entry(forKey: "my favorite city")
print(entry?.object)
print(entry?.expiry)
print(entry?.meta)

meta may contain file information if the object was fetched from disk storage.

Custom Codable

Codable works for simple dictionary like [String: Int], [String: String], ... It does not work for [String: Any] as Any is not Codable conformance, it will raise fatal error at runtime. So when you get json from backend responses, you need to convert that to your custom Codable objects and save to Storage instead.

struct User: Codable {
  let firstName: String
  let lastName: String
}

let user = User(fistName: "John", lastName: "Snow")
try? storage.setObject(user, forKey: "character")

Async APIs

In async fashion, you deal with Result instead of try catch because the result is delivered at a later time, in order to not block the current calling queue. In the completion block, you either have value or error.

You access Async APIs via storage.async, it is also thread safe, and you can use Sync and Async APIs in any order you want. All Async functions are constrained by AsyncStorageAware protocol.

storage.async.setObject("Oslo", forKey: "my favorite city") { result in
  switch result {
    case .success:
      print("saved successfully")
    case .failure(let error):
      print(error)
  }
}

storage.async.object(forKey: "my favorite city") { result in
  switch result {
    case .success(let city):
      print("my favorite city is \(city)")
    case .failure(let error):
      print(error)
  }
}

storage.async.existsObject(forKey: "my favorite city") { result in
  if case .success(let exists) = result, exists {
    print("I have a favorite city")
  }
}

storage.async.removeAll() { result in
  switch result {
    case .success:
      print("removal completes")
    case .failure(let error):
      print(error)
  }
}

storage.async.removeExpiredObjects() { result in
  switch result {
    case .success:
      print("removal completes")
    case .failure(let error):
      print(error)
  }
}

Expiry date

By default, all saved objects have the same expiry as the expiry you specify in DiskConfig or MemoryConfig. You can overwrite this for a specific object by specifying expiry for setObject

// Default expiry date from configuration will be applied to the item
try? storage.setObject("This is a string", forKey: "string")

// A given expiry date will be applied to the item
try? storage.setObject(
  "This is a string",
  forKey: "string"
  expiry: .date(Date().addingTimeInterval(2 * 3600))
)

// Clear expired objects
storage.removeExpiredObjects()

Observations

Storage allows you to observe changes in the cache layer, both on a store and a key levels. The API lets you pass any object as an observer, while also passing an observation closure. The observation closure will be removed automatically when the weakly captured observer has been deallocated.

Storage observations

// Add observer
let token = storage.addStorageObserver(self) { observer, storage, change in
  switch change {
  case .add(let key):
    print("Added \(key)")
  case .remove(let key):
    print("Removed \(key)")
  case .removeAll:
    print("Removed all")
  case .removeExpired:
    print("Removed expired")
  }
}

// Remove observer
token.cancel()

// Remove all observers
storage.removeAllStorageObservers()

Key observations

let key = "user1"

let token = storage.addObserver(self, forKey: key) { observer, storage, change in
  switch change {
  case .edit(let before, let after):
    print("Changed object for \(key) from \(String(describing: before)) to \(after)")
  case .remove:
    print("Removed \(key)")
  }
}

// Remove observer by token
token.cancel()

// Remove observer for key
storage.removeObserver(forKey: key)

// Remove all observers
storage.removeAllKeyObservers()

Handling JSON response

Most of the time, our use case is to fetch some json from backend, display it while saving the json to storage for future uses. If you're using libraries like Alamofire or Malibu, you mostly get json in the form of dictionary, string, or data.

Storage can persist String or Data. You can even save json to Storage using JSONArrayWrapper and JSONDictionaryWrapper, but we prefer persisting the strong typed objects, since those are the objects that you will use to display in UI. Furthermore, if the json data can't be converted to strongly typed objects, what's the point of saving it ? 😉

You can use these extensions on JSONDecoder to decode json dictionary, string or data to objects.

let user = JSONDecoder.decode(jsonString, to: User.self)
let cities = JSONDecoder.decode(jsonDictionary, to: [City].self)
let dragons = JSONDecoder.decode(jsonData, to: [Dragon].self)

This is how you perform object converting and saving with Alamofire

Alamofire.request("https://gameofthrones.org/mostFavoriteCharacter").responseString { response in
  do {
    let user = try JSONDecoder.decode(response.result.success, to: User.self)
    try storage.setObject(user, forKey: "most favorite character")
  } catch {
    print(error)
  }
}

What about images

If you want to load image into UIImageView or NSImageView, then we also have a nice gift for you. It's called Imaginary and uses Cache under the hood to make you life easier when it comes to working with remote images.

Installation

Cocoapods

Cache is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Cache'

Carthage

Cache is also available through Carthage. To install just write into your Cartfile:

github "hyperoslo/Cache"

You also need to add SwiftHash.framework in your copy-frameworks script.

Author

  • Hyper made this with ❤️
  • Inline MD5 implementation from SwiftHash

Contributing

We would love you to contribute to Cache, check the CONTRIBUTING file for more info.

License

Cache is available under the MIT license. See the LICENSE file for more info.

Comments
  • Doesnt save to disk

    Doesnt save to disk

    Hello,

    I am using your cache for images. The type-safe one. However it doesnt persisted cached images to object. When I restart the app, the image is not there :( I am using .disk for back cache.

    let config = Config(frontKind: .memory, backKind: .disk)
    cache = Cache<UIImage>(name: SnapshotManager.CACHE_NAME, config: config)
    
    opened by zdnk 24
  • Xcode 12 / Swift 5

    Xcode 12 / Swift 5

    I forked Cache because I got a Carthage error when building my main project. In the fork, I migrated to newest Swift (e.g., 5) syntax and set iOS 12 and macOS Mojave as OS versions.

    In Xcode, everything builds fine, no problems, everything's clean.

    When executing a carthage build (carthage build --no-skip-current --platform iOS), I get this error: `*** xcodebuild output can be found in /var/folders/hk/9cl_0hwx1czbgzsb26_tl2lc295kd8/T/carthage-xcodebuild.5LIvOf.log *** Building scheme "Cache-iOS" in Cache.xcodeproj Build Failed Task failed with exit code 65: /usr/bin/xcrun xcodebuild -project /Users/.../Cache/Cache.xcodeproj -scheme Cache-iOS -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath /var/folders/hk/9cl_0hwx1czbgzsb26_tl2lc295kd8/T/Cache SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO STRIP_INSTALLED_PRODUCT=NO (launched in /Users/.../Cache)

    This usually indicates that project itself failed to compile. Please check the xcodebuild log for more details: /var/folders/hk/9cl_0hwx1czbgzsb26_tl2lc295kd8/T/carthage-xcodebuild.5LIvOf.log`

    Ths log does not give a helpful indication for the reason: `error: Bus error: 10

    :0: error: unable to execute command: Bus error: 10 :0: error: compile command failed due to signal 10 (use -v to see invocation) ** ARCHIVE FAILED **

    The following build commands failed: CompileSwift normal arm64 CompileSwiftSources normal arm64 com.apple.xcode.tools.swift.compiler (2 failures) `

    opened by innoreq 19
  • ImageWrapper overhead

    ImageWrapper overhead

    Thanks for the excellent cache library. When using for caching images I noticed that you take the UIImage data as PNG or JPEG then serialize it into a JSON like

    {"object":{"image":"\/9j\/4AAQSkZJRgABAQAASABIAAD\/4QBYRXhpZ...

    This transformation I'm afraid is expensive, the resulting cache files also are easily double in size from the original binary. Then when we retrieve the data from the cache we have to deserialize it again.

    So what happens now:

    1. Fetch data from URL
    2. Load it into UIImage
    3. Send it to Cache: 3.1 Convert back to Data as PNG or JSON, depending on Alpha channel, again 3.2 Wrap it into a Codable 3.3 Serialize into JSON text (larger size) 3.4 Save to cache (disk and memory)

    then in reverse when retrieving..

    I prefer the way your previous Swift 3 version of the library worked where it would cache the actual data we fetch from a URL.

    opened by gabors 13
  • Bugfix/cannot save file with long URL key

    Bugfix/cannot save file with long URL key

    If you want to store an image (using Imaginary) in the disk storage and use the file’s remote URL as key, saving will fail if the URL is long. This PR tries to fix the issue by using the URL’s MD5 digest as file name instead of the Base64 encoded version of the URL

    opened by attila-at-hyper 13
  • New transformer parameter

    New transformer parameter

    let diskConfig = DiskConfig(name: "Floppy")
    let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
    
    let storage = try? Storage(
      diskConfig: diskConfig, 
      memoryConfig: memoryConfig, 
      transformer: TransformerFactory.forCodable(ofType: User.self) // Storage<User>
    )
    

    Docs indicate to have a separate instance of storage for every type in the new version, why? In the previous version, I used one storage to store all Codable entities, not sure how to migrate it now...

    opened by vburojevic 12
  • Cache.swift can not compile w/ Xcode 8.3 beta

    Cache.swift can not compile w/ Xcode 8.3 beta

    Xcode 8.3 beta will complain about override statement on these methods:

      public override func add(_ key: String, object: T, expiry: Expiry? = nil, completion: (() -> Void)? = nil) {
      public override func object(_ key: String, completion: @escaping (_ object: T?) -> Void) {
    

    Xcode 8.2 is fine with it. Probably a Swift compiler upgrade so it should be helpful to check it soon.

    opened by ened 10
  • Shouldn't the default cache folder be /library or /documents not /library/caches?

    Shouldn't the default cache folder be /library or /documents not /library/caches?

    So I've been chasing a weird issue and I think I've gotten to the bottom of it. My app launches in the background periodically and I need to recover user data. After some logging it appeared my users were being logged out way too often/not having user credentials frequently.

    So keychain periodically (~5%) of the time fails in the background (longstanding iOS issue), so I store a backup copy in cache to catch it.

    Thing is, periodically cache fails too (with a not-found error for a user that definitely exists, and successfully wrote some info to disk)

    I discovered that the default directory is Library/Caches, and according to:

    https://stackoverflow.com/questions/7155964/will-application-home-library-caches-be-clear-on-app-update

    and various other places iOS can periodically clear those files.

    This seems counter-intuitive as I would expect most devs to expect a .never expiry to not be at risk of deletion from the OS itself.

    All in all it's not a big deal to switch to /Documents (it's nice that this will be backed up by itunes/icloud... library/caches is not!) I am wondering if a change to the default location wouldn't make sense.

    Thanks for such a great tool!

    Edit: For those who find this later, the correct folder for this is Library/Application Support. I will update this post if moving to this folder solves my issue. I have a build rolling out now that uses the new location so I will see if this fixes my primary issue.

    opened by RamblinWreck77 9
  • Cache with RealmSwift

    Cache with RealmSwift

    when i'm use cache and RealmSwift,for example:

    import Foundation import RealmSwift import ObjectMapper import Cache

    class Session: Object, Mappable { dynamic var token = "" dynamic var ttl: Double = 0

    required convenience init?(_ map: Map) {
        self.init()
    }
    
    override static func primaryKey() -> String? {
        return "token"
    }
    
    func mapping(map: Map) {
        token               <- map["token"]
        ttl                 <- map["ttl"]
    }
    

    }

    extension Session: Cachable {

    typealias CacheType = Session
    
    static func decode(data: NSData) -> CacheType? {
        Log.debug("decode")
        var object: CacheType?
    
        do {
            object = try DefaultCacheConverter<CacheType>().decode(data)
            Log.debug("object: \(object)")
        } catch {}
    
        return object
    }
    
    func encode() -> NSData? {
        var data: NSData?
        Log.debug("encode")
        do {
            data = try DefaultCacheConverter<CacheType>().encode(self)
        } catch {}
    
        return data
    }
    

    }

        let session = Session()
        session.token = "aaa"
        session.ttl = 20
        syncCache.add("tujiaoSession", object: session, expiry: .Date(NSDate().dateByAddingTimeInterval(100000)))
    
        let s:Session? = syncCache.object("tujiaoSession")
        Log.debug("ssssss: \(s)")
    

    there have some errors for decode method.

    opened by arden 9
  • Not clear expired content on CacheManager init

    Not clear expired content on CacheManager init

    I'm using Cache to store JSON coming from the server. This content is updated everyday, so I'm setting it's expiration date to be the day it was downloaded at 11:59 PM. this works just fine and the content is expired and re downloaded the next day.

    The problem I'm facing is that, due to business rules, I need to return expired content (so I can have something to show user), identify that this content has expired and download the new version. And then update view with this new content and cache it.

    Is there any way to flag Cache to not clear expired data on initialization, and when receiving some notifications?

    enhancement 
    opened by jlsandrin 8
  • Use SwiftHash

    Use SwiftHash

    • Use the homegrown SwiftHash, it contains just 1 file
    • I've tested it a lot, even with https://github.com/minimaxir/big-list-of-naughty-strings
    • This is not about file size, there's no problem with CryptoSwift, it's just about taste 😈
    opened by onmyway133 8
  • Feature request: observation

    Feature request: observation

    Hey there!

    First, congrats on delivering Cache, it seems to be a very well built and appreciated library. I'm working for SoundCloud, and we are considering using your library in our iOS app. Although, before we get to this point, I would have a couple of questions if you don't mind!

    There's a feature we probably would like to see in our caching layer: the ability to notify external observers that something changed. By changed, I mean:

    • a key was modified (i.e. edition or removal)
    • same goes on a store level: a key is added, edited or removed

    Do you think that's something you would like to feature in your library? If so, would you build this yourself? Or would you expect a pull request?

    Thanks in advance for you reply!

    Cheers, Romain.

    enhancement 
    opened by RomainBoulay 7
  • Disk cache failed to save object with specific cache string

    Disk cache failed to save object with specific cache string

    When use directory as cache key, disk cache just fail

    This key dose not work

    /private/var/mobile/Containers/Data/Application/BBFA132393-27C1-4A50-903420-B5742173983BF8A/Documents/14231/2022-11-03_11-30-28.zip

    And this works

    2022-11-03_11-30-28.zip

    This may be a bug?

    opened by DikeyKing 2
  • Improve dealing with cache expiration

    Improve dealing with cache expiration

    First, I'm saying "thank you!" for great caching library! It eliminates lots of needs related to caching, especially for expirable entries

    Next, I'd like to ask to add convenient extension to the library

    • requesting object with checking if it is still alive
    • scheduling cache cleanup
    extension StorageAware {
        public func aliveObject(forKey key: Key) -> Value? {
            try? !isExpiredObject(forKey: key) ? object(forKey: key) : nil
        }
        
        public func scheduleCleanup(_ interval: TimeInterval) where Self: AnyObject {
            DispatchQueue.global().asyncAfter(deadline: .now() + interval) { [weak self] in
                try? self?.removeExpiredObjects()
                self?.scheduleCleanup(interval)
            }
        }
    }
    
    opened by Alkenso 1
  • When retrieving object from Cache it throws error

    When retrieving object from Cache it throws error "keyNotFound".

    I have the following case:

    I'm fetching from API, decoding the response to concrete model. After that the model gets store in Cache (HybridStorage). Everything is fine until I'm trying to retrieve the model from Cache. Because the model is Inherited from other model and I think that is the problem. The ModelCache is declared because I have a list of FootballCountryModel.

     public class RemapableObject: NSObject, Codable {
    
     }
    
     public class FootballCountryModel: RemapableObject {
        var id: String;
        var alias: String?;
        var countryCode: String?;
        var assets: CountryAssets?;
        var name: String;
    
        enum CodingKeys: String, CodingKey {
            case id, alias, assets, name;
            case countryCode = "country_code";
        }
        
        /*
         For a subclass of a class already adopting Codable (e.g. RemapableObject) we need to
         override our superclass's implementation by offering our own.
         There is no way for the compiler to synthesize an implementation here for us.
         */
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self);
    
            self.id = try container.decode(String.self, forKey: .id);
            self.alias = try container.decode(String?.self, forKey: .alias);
            self.countryCode = try container.decode(String?.self, forKey: .countryCode);
            self.assets = try container.decode(CountryAssets?.self, forKey: .assets);
            self.name = try container.decode(String.self, forKey: .name);
            
            try super.init(from: container.superDecoder());
        }
    }
    
    class ModelCache<T: Codable>: NSObject, Codable {
        let data: [T];
        
        init (data: [T]) {
            self.data = data;
        }
    }
    
    

    Here is when Im trying to retrieve from Cache:

    let diskConfig = DiskConfig(name: "FansUnitedSDK")
    let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
    let storage = try! Storage<String, ModelCache<FootballCountryModel>>(
              diskConfig: diskConfig,
              memoryConfig: memoryConfig,
              transformer: TransformerFactory.forCodable(ofType: ModelCache<FootballCountryModel>.self))
    let cacheKey = "football_countries";
    do {
            let footballCountriesCache = try storage.object(forKey: cacheKey) // Here it returns keyNotFound Error
    } catch {
            print(error)
    }
    

    The keyNotFound Error is the following one:

    keyNotFound(CodingKeys(stringValue: "id", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "object", intValue: nil), CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").", underlyingError: nil))

    Any suggestions will be helpful. I'm trying for days to implement some caching in my project (with Core Data and other dependencies) but there always some issues coming up.

    opened by viktoryoo 1
  • removeResourceObjects sortedFiles should return time1 < time2

    removeResourceObjects sortedFiles should return time1 < time2

    let sortedFiles = objects.sorted { if let time1 = $0.resourceValues.contentModificationDate?.timeIntervalSinceReferenceDate, let time2 = $1.resourceValues.contentModificationDate?.timeIntervalSinceReferenceDate { return time1 > time2 } else { return false } } this will remove recent file, generally speaking should remove the file which older not the recent

    opened by ywz364kf 2
  • removeObject Doesn't remove object from memory

    removeObject Doesn't remove object from memory

    Try to update cached object:

    step 1: delete object using **removeObject** 
    step 2: save updated object using **setObject**
    

    Every things goes well but the problem appears when try to get data using async.object it returns old value of object. I have checked value saved on disk it is new data.

    I have trace the code and figure out to get data this first check memory : public func entry(forKey key: Key) throws -> Entry<Value> { do { return try memoryStorage.entry(forKey: key) } catch { let entry = try diskStorage.entry(forKey: key) // set back to memoryStorage memoryStorage.setObject(entry.object, forKey: key, expiry: entry.expiry) return entry } }

    how can remove object in first step to make sure it is removed from disk & memory?

    opened by Masoomeh-Heidari 0
Releases(6.0.0)
  • 6.0.0(Oct 4, 2020)

    Breaking changes

    • Bumped minimum deployment target iOS 11.0, macOS 10.12 and tvOS 11

    • Changed API so they key can be any Hashable not just string, in order to make the old code work we need to add String to our Storage declaration. (Added by @nerdsupremacist, thanks!)

    // Old
    Storage<Image>
    
    // New
    Storage<String, Image>
    
    Source code(tar.gz)
    Source code(zip)
    Cache.framework.zip(5.96 MB)
  • 5.3.0(Jun 5, 2020)

  • 5.2.0(Sep 24, 2018)

    🚀 Merged pull requests

    • Made the code compatible with Swift 4.2. https://github.com/hyperoslo/Cache/pull/214, by rizwan95
    • update to support swift4+ https://github.com/hyperoslo/Cache/pull/215, by Pircate
    • Swift 4.2 and Xcode 10 improvements https://github.com/hyperoslo/Cache/pull/216, by fassko

    🤘 Closed issues

    • Can't play the video that load from the cached filePath https://github.com/hyperoslo/Cache/issues/204
    Source code(tar.gz)
    Source code(zip)
  • 5.1.0(Aug 13, 2018)

    • Storage and key observations https://github.com/hyperoslo/Cache/pull/202
    • Add filePath to Entry https://github.com/hyperoslo/Cache/pull/205
    • Handle file extensions https://github.com/hyperoslo/Cache/pull/206
    Source code(tar.gz)
    Source code(zip)
  • 5.0.0(Jun 13, 2018)

    Merged pull requests

    • Fix typo https://github.com/hyperoslo/Cache/pull/182, by emreozdil
    • Improved sample code of storage's async functions in README https://github.com/hyperoslo/Cache/pull/183, by guoyingtao
    • Removed totalCostLimit from MemoryConfig https://github.com/hyperoslo/Cache/pull/184, by guoyingtao
    • Fix Bug - Passing the meta to Entry https://github.com/hyperoslo/Cache/pull/187, by Gruppio
    • Refactor to generic Storage https://github.com/hyperoslo/Cache/pull/189, by onmyway133
    • Update README https://github.com/hyperoslo/Cache/pull/190, by onmyway133

    Closed issues

    • setObject Thread Problem https://github.com/hyperoslo/Cache/issues/181
    • Store and Fetch an array https://github.com/hyperoslo/Cache/issues/188
    • Shouldn't the default cache folder be /library or /documents not /library/caches? https://github.com/hyperoslo/Cache/issues/179
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0(Apr 4, 2018)

  • 4.1.2(Oct 27, 2017)

  • 4.1.1(Oct 27, 2017)

    🚀 Merged pull requests

    • Inline SwiftHash https://github.com/hyperoslo/Cache/pull/159, by onmyway133

    🤘 Closed issues

    • Does this library support migration in case you change Object structure with the update? https://github.com/hyperoslo/Cache/issues/153
    Source code(tar.gz)
    Source code(zip)
  • 4.0.4(Oct 13, 2017)

  • 4.0.3(Oct 9, 2017)

  • 4.0.2(Oct 3, 2017)

  • 4.0.1(Sep 26, 2017)

  • 4.0.0(Sep 20, 2017)

    • Update to Swift 4 with Codable support 🎉
    • Work with Swift 4 Codable. Anything conforming to Codable will be saved and loaded easily by Storage.
    • Disk storage by default. Optionally using memory storage to enable hybrid.
    • Many options via DiskConfig and MemoryConfig.
    • Support expiry and clean up of expired objects.
    • Thread safe. Operations can be accessed from any queue.
    • Sync by default. Also support Async APIs.
    • Store images via ImageWrapper.
    • Extensive unit test coverage and great documentation.
    • iOS, tvOS and macOS support.

    ⚠️ Changes from v3 -> v4 ⚠️

    • You now work with Storage most of the time.
    • HybridCache was removed. Storage can be configured with DiskConfig and MemoryConfig. If you don't specify MemoryConfig, then only disk cache is used.
    • SpecializedCache was removed. You can just use Storage
    • Cacheable and Coding was removed. You need to conform to Swift 4 Codable to take advantage of the auto serializing/deserializing
    • CacheArray can be replaced with [SomeClass].self
    Source code(tar.gz)
    Source code(zip)
  • 3.3.0(Jun 12, 2017)

  • 3.2.0(Jun 9, 2017)

  • 3.1.0(Jun 9, 2017)

  • 3.0.0(Jun 8, 2017)

    ⚠️ Breaking change!!!

    Cache 3 features multiple public API changes and brand new features implemented as a feedback to the input we got. We also did some internal framework refactoring that will make it easier to read the code, test and contribute 🎉

    • Cache is renamed to SpecializedCache in order to resolve conflicts with the name of the framework.
    • SyncCache and SyncHybridCache wrappers were removed because SpecializedCache and HybridCache are now sync by default. But it's also possible to perform async cache operation using the new Async API: https://github.com/hyperoslo/Cache#hybrid-cache
    • As a part of the new Sync API you can use subscript to easily set/get/remove objects: https://github.com/hyperoslo/Cache#specialized-cache
    • Now it's possible to update cache directory attributes with setDiskCacheDirectoryAttributes method and use setFileProtection method to enable data protection: https://github.com/hyperoslo/Cache#enabling-data-protection
    • There is new Coding protocol which works in the same way as NSCoding, but can be used for Swift structs and enums: https://github.com/hyperoslo/Cache#coding
    • totalDiskSize() method is added to get total cache size on the disk https://github.com/hyperoslo/Cache/pull/94
    • CryptoSwift is replaced with more lightweight SwiftHash framework: https://github.com/hyperoslo/Cache/pull/88
    Source code(tar.gz)
    Source code(zip)
  • 2.3.0(May 31, 2017)

    • Fix generic Cache class https://github.com/hyperoslo/Cache/pull/80
    • Allow for the disk cache to be stored in a different location https://github.com/hyperoslo/Cache/pull/82 by @letko-dmitry
    • Update front storage with back storage when cache only found in back storage https://github.com/hyperoslo/Cache/pull/84 by @kirmanie
    Source code(tar.gz)
    Source code(zip)
  • 2.2.2(Mar 28, 2017)

  • 2.2.0(Jan 11, 2017)

  • 2.1.1(Oct 23, 2016)

  • 2.1.0(Oct 23, 2016)

  • 2.0.0(Oct 13, 2016)

  • 1.5.1(Aug 8, 2016)

  • 1.5.0(Jul 7, 2016)

  • 1.4.0(May 24, 2016)

  • 1.3.0(Apr 27, 2016)

  • 1.2.0(Apr 27, 2016)

    • DiskStorage uses MD5 digest as file name instead of the Base64 key. It fixes the issue with long URL keys. https://github.com/hyperoslo/Cache/pull/34

    Thanks to @attila-at-hyper who discovered and fixed this bug 🎉

    Source code(tar.gz)
    Source code(zip)
  • 1.1.0(Apr 24, 2016)

Owner
HyperRedink
Connected creativity
HyperRedink
A simple but flexible cache

Carlos A simple but flexible cache, written in Swift for iOS 13+ and WatchOS 6 apps. Breaking Changes Carlos 1.0.0 has been migrated from PiedPiper de

National Media & Tech 628 Dec 3, 2022
Awesome Cache Delightful on-disk cache (written in Swift). Backed by NSCache for maximum performance

Awesome Cache Delightful on-disk cache (written in Swift). Backed by NSCache for maximum performance and support for expiry of single objects. Usage d

Alexander Schuch 1.3k Dec 29, 2022
Apple Asset Cache (Content Cache) Tools

AssetCacheTool A library and tool for interacting with both the local and remote asset caches. This is based on research I did a few years ago on the

Kenneth Endfinger 21 Jan 5, 2023
CachyKit - A Caching Library is written in Swift that can cache JSON, Image, Zip or AnyObject with expiry date/TTYL and force refresh.

Nice threadsafe expirable cache management that can cache any object. Supports fetching from server, single object expire date, UIImageView loading etc.

Sadman Samee 122 Dec 28, 2022
Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift.

Cachyr A typesafe key-value data cache for iOS, macOS, tvOS and watchOS written in Swift. There already exists plenty of cache solutions, so why creat

Norsk rikskringkasting (NRK) 124 Nov 24, 2022
MemoryCache - type-safe, thread-safe memory cache class in Swift

MemoryCache is a memory cache class in swift. The MemoryCache class incorporates LRU policies, which ensure that a cache doesn’t

Yusuke Morishita 74 Nov 24, 2022
A lightweight generic cache for iOS written in Swift with extra love for images.

Haneke is a lightweight generic cache for iOS and tvOS written in Swift 4. It's designed to be super-simple to use. Here's how you would initalize a J

Haneke 5.2k Dec 29, 2022
High performance cache framework for iOS.

YYCache High performance cache framework for iOS. (It's a component of YYKit) Performance You may download and compile the latest version of sqlite an

null 2.3k Dec 16, 2022
Everyone tries to implement a cache at some point in their iOS app’s lifecycle, and this is ours.

Everyone tries to implement a cache at some point in their app’s lifecycle, and this is ours. This is a library that allows people to cache NSData wit

Spotify 1.2k Dec 28, 2022
Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU.

Track is a thread safe cache write by Swift. Composed of DiskCache and MemoryCache which support LRU. Features Thread safe: Implement by dispatch_sema

Cheer 268 Nov 21, 2022
UITableView cell cache that cures scroll-lags on cell instantiating

UITableView + Cache https://github.com/Kilograpp/UITableView-Cache UITableView cell cache that cures scroll-lags on a cell instantiating. Introduction

null 73 Aug 6, 2021
Fast, non-deadlocking parallel object cache for iOS, tvOS and OS X

PINCache Fast, non-deadlocking parallel object cache for iOS and OS X. PINCache is a fork of TMCache re-architected to fix issues with deadlocking cau

Pinterest 2.6k Dec 28, 2022
💾 Simple memory & disk cache

Cache ?? Simple memory & disk cache Usage ??‍?? Default let cache = Cache<String>() try memory.save("MyValue", forKey: "MyKey") let cached = try cac

SeongHo Hong 2 Feb 28, 2022
Cache library for videos for React Native

@lowkey/react-native-cache Cache everything Installation npm install @lowkey/react-native-cache Usage import ReactNativeCache from "@lowkey/react-nati

Max Prokopenko 1 Oct 1, 2021
CachedAsyncImage is the simplest way to add cache to your AsyncImage.

CachedAsyncImage ??️ CachedAsyncImage is AsyncImage, but with cache capabilities. Usage CachedAsyncImage has the exact same API and behavior as AsyncI

Lorenzo Fiamingo 278 Jan 5, 2023
🏈 Cache CocoaPods for faster rebuild and indexing Xcode project.

Motivation Working on a project with a huge amount of pods I had some troubles: - Slow and unnecessary indexing of pods targets, which implementation

Vyacheslav Khorkov 487 Jan 5, 2023
XCRemoteCache is a remote cache tool for Xcode projects.

XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artifacts generated on a remote machine, served from a simple REST server. H

Spotify 737 Dec 27, 2022
A simple cache that can hold anything, including Swift items

CacheIsKing CacheIsKing is a simple cache that allows you to store any item, including objects, pure Swift structs, enums (with associated values), et

Christopher Luu 13 Jan 22, 2018
MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

MrCode is a simple GitHub iPhone App that can cache Markdown content (include images in HTML) for read it later.

hao 448 Dec 19, 2022