The missing light persistence layer for Swift

Related tags

Utility Pantry
Overview

If you're planning on using Swift 4 in the near future, please consider using the new Codable protocols which provide the same functionality as Pantry in the core Swift library. Pantry will not be supported going forward.

Storage

Platform: iOS 8+ Language: Swift 3 Carthage compatible CocoaPods compatible Docs License: MIT

InstallationIssuesLicense

Please join us in issues if you'd like to help us get to 1.0. And read about more use cases for Pantry.

Pantry is a lightweight way to persist structs containing user data, cached content or other relevant objects for later retrieval.

let someCustomStruct = SomeCustomStruct(...)
Pantry.pack(someCustomStruct, "user_data")

... later ...

if let unpackedCustomStruct: SomeCustomStruct = Pantry.unpack("user_data") {
  print("got my data out",unpackedCustomStruct)
} else {
  print("there was no struct data to get")
}

You can store:

  • Structs
  • Strings, Ints and Floats (our default types)
  • Arrays of structs and default types
  • Nested structs
  • Nested Arrays
  • Classes
  • Arrays of classes and default types
  • Nested classes
  • Enums with raw types

Check out the tests for a detailed look at the varied types you can easily store.

Compatibility

Pantry requires iOS 8+ and is compatible with Swift 3 projects. Please use release 0.2.2 for the final Swift 2.x supported version, or the swift2 branch. Objective-C support is unlikely.

Installation

Installation for Carthage is simple enough:

github "nickoneill/Pantry" ~> 0.3

As for CocoaPods, use this to get the latest release:

use_frameworks!

pod 'Pantry'

And import Pantry in the files you'd like to use it.

Usage

Basic types

Pantry provides serialization of some basic types (String, Int, Float, Bool) with no setup. You can use it as a simple expiring cache like this:

if let available: Bool = Pantry.unpack("promptAvailable") {
    completion(available: available)
} else {
    anExpensiveOperationToDetermineAvailability({ (available) -> () in
      Pantry.pack(available, key: "promptAvailable", expires: .Seconds(60 * 10))
      completion(available: available)
    })
}

Automagic Persistent Variables

Use Swift's get/set to automatically persist the value of a variable on write and get the latest value on read.

var autopersist: String? {
    set {
        if let newValue = newValue {
            Pantry.pack(newValue, key: "autopersist")
        }
    }
    get {
        return Pantry.unpack("autopersist")
    }
}

...later...

autopersist = "Hello!"
// restart app, reboot phone, etc
print(autopersist) // Hello!

Structs

Add the Storable protocol to any struct you want stored and then ensure they comply by implementing an init method that gets each property from the warehouse, and a toDictionary method that converts the other way:

struct Basic: Storable {
    let name: String
    let age: Float
    let number: Int

    init(warehouse: Warehouseable) {
        self.name = warehouse.get("name") ?? "default"
        self.age = warehouse.get("age") ?? 20.5
        self.number = warehouse.get("number") ?? 10
    }

    func toDictionary() -> [String : Any] {
        return [ "name": self.name, "age": self.age, "number": self.number ]
    }
}

Getters always provide an optional value, leaving you the opportunity to fill in a default if a value isn't available. This makes for hassle-free property additions to your structs.

Classes

Classes are also supported and can be setup the same way Structs are however the init method must be marked required in this case. Class inheritance and nested Storable properties are also possible:

class ModelBase: Storable {
    let id: String

    required init(warehouse: Warehouseable) {
        self.id = warehouse.get("id") ?? "default_id"
    }

    func toDictionary() -> [String : Any] {
        return [ "id": self.id ]
    }
}

class BasicClassModel: ModelBase {
    let name: String
    let age: Float
    let number: Int

    required init(warehouse: Warehouseable) {
        self.name = warehouse.get("name") ?? "default"
        self.age = warehouse.get("age") ?? 20.5
        self.number = warehouse.get("number") ?? 10

        super.init(warehouse: warehouse)
    }

    func toDictionary() -> [String : Any] {
        var dictionary = super.toDictionary()
        dictionary["name"] = self.name
        dictionary["age"] = self.age
        dictionary["number"] = self.number
        return dictionary
    }
}

Also

Pantry works great with network data when paired with a JSON struct decoder such as Unbox. Download JSON, decode it with Unbox, save it with Pantry and have it available for as long as you need. The architecture of Pantry is heavily influenced by Unbox, it's worth a look in any case.

License

Pantry uses the MIT license. Please file an issue if you have any questions or if you'd like to share how you're using this tool.

ack

Pantry "can icon" by CDH from the Noun Project

Comments
  • Example code references 'Warehouseable'

    Example code references 'Warehouseable'

    Example code in the docs for Classes references Warehouseable which doesn't appear to be in the code anymore?

    required init(warehouse: Warehouseable) {
            self.id = warehouse.get("id") ?? "default_id"
    }
    

    image

    https://github.com/nickoneill/Pantry/blame/master/README.md#L133

    An update to this section would be great.

    Cheers,

    opened by barfoon 9
  • Initializing JSONWarehouse

    Initializing JSONWarehouse

    I'm having some trouble packing structs. It seems like JSONWarehouse doesn't have any public initializers. I'm the following error from Xcode: "'JSONWarehouse' cannot be constructed because it has no accessible initializers".

    I'm probably just missing something, but creating a JSONWarehouse object doesn't seem to be working.

    Thanks!

    opened by m-levi 9
  • Add support for Swift Package Manager

    Add support for Swift Package Manager

    Sample usage package via my fork : https://github.com/aciidb0mb3r/Pantrie

    This will start working on this repo by creating tag from master after this PR is merged

    opened by aciidb0mb3r 6
  • Make storable init failable

    Make storable init failable

    Having the protocol use a failable init introduces possibility of not requiring default values in your struct while still allowing for nonfailable intitializers.

    This also allows for the possibility of calling a types other failable initializers from the storable initializer.

    opened by litso 6
  • Storable extension `toDictionary()` not visible from outside its module

    Storable extension `toDictionary()` not visible from outside its module

    Hi,

    Thank you for the work on this project, it's really helpful! However, I have an issue with the visibility of toDictionary() default implementation in the module. As the default implementation is not public, I end up with compilation error: Type 'MyType' does not confirm to protocol 'Storable': Protocol requires function 'toDictionary()' with type '() -> [String: AnyObject]'. Changing the visibility of the default implementation solves the issue.

    opened by delannoyk 5
  • Added support for class based models with inheritance hierarchies

    Added support for class based models with inheritance hierarchies

    -Moved the Mirror based serialisation to a Mirror extension to make it easier to traverse the inheritance chain for classes -Duplicated the existing struct test types into class based representations with some inheritance sprinkled in -Duplicated the relevant struct tests to also test the class models

    opened by IanKeen 5
  • Swift 3

    Swift 3

    Migrated to Swift 3

    • to be able to handle nil value in Swift types, switched to JSONSerialization
    • explicitly create NSNull in toDictionary for nil optional values
    • use Any instead of AnyObject
    opened by andreyz 4
  • Nested Dictionary Rejected by Pantry

    Nested Dictionary Rejected by Pantry

    I have a multiple-nested dictionary of type:

    Dictionary<String, Dictionary<String, Dictionary<String, Int>>>

    and when I call Pantry.pack on it this is the error message I get this error message

    Cannot invoke 'pack' with an argument list of type (Dictionary<String, Dictionary<String, Dictionary<String, Int>>>, key: String)

    opened by SikandAlex 4
  • Pantry crashes when trying to expire a non-existent key

    Pantry crashes when trying to expire a non-existent key

    try this...

    Pantry.expire("Test")
    

    assuming the key "Test" does not exist, and the framework will crash

    I was looking at your code and I see that you did a force unwrapping within the delete cache function, I know try methods are cumbersome because this framework is based of speed of code but maybe do a safe unwrap under the layer and if the unwrapping is unsafe then just return nil perhaps?

    screen shot 2016-06-04 at 12 48 58 pm

    opened by DrewKiino 4
  • Add support for objects that conform to NSCoding

    Add support for objects that conform to NSCoding

    There are already a lot of objects that can be persisted using NSCoding. I think being able to persist them out of the box makes a lot of sense.

    I had to remove StorableDefaultType from NSDate because it conforms to NSCoding and the compiler wouldn't know which pack/unpack to use.

    opened by fpg1503 3
  • JSONWarehouse methods visibility

    JSONWarehouse methods visibility

    Hi, this looks like a nice project and I thought I give it a try. I integrated via CocoaPods and I'm getting a compile error. The get is not visible

    extension TMDBMovie: Storable {
        init(warehouse: JSONWarehouse) {
            // Value of type 'JSONWarehouse' has no member 'get'
            originalTitle = warehouse.get("originalTitle")
        }
        func toDictionary() -> [String: AnyObject] {
            return ["originalTitle" : originalTitle ?? ""]
        }
    }
    

    Shouldn't they be declared as public ?

    cheers

    opened by JanC 3
  • Reading the legacy storage format

    Reading the legacy storage format

    This PR enables Pantry to read its legacy storage format (plist). This is necessary when upgrading from a pre-swift-3 version of the framework a current version, otherwise the storage contents will always be tried to read as JSON (which will fail) and the unpack method will ultimately return nil although all the data was still there.

    What this PR does is basically first trying to read the old format for downwards compatibility and only if that fails, read the storage contents as JSON.

    You don't NEED to merge the PR if you don't want to as I can see this being a rare edge case but I wanted to offer it anyways. And I can totally see that 'reading the legacy format first' is a overhead that might be too big for the edge case it solves.

    Also, I'm open for better approaches to fix this scenario if you have any. This is just a quick fix and honestly the first idea I had. But it works.

    opened by floriankrueger 0
  • Int32 / Int64 should be a default type

    Int32 / Int64 should be a default type

    I needed to explicitly store a 64-bit Int on 32-bit devices, but Pantry doesn't support Int64 by default. Including this line seems to work:

    extension Int64: StorableDefaultType {}

    (Hopefully there's no reason not to do that, it seems to work without error.)

    Presumably Int32 also needs to be added, but I didn't explicitly test for that.

    opened by davidcorry 0
  • crash in JSONWarehouse during JSON Write

    crash in JSONWarehouse during JSON Write

    I have the following nested model:

        let id: String
        let agencyId: String
        let name: String
        var checklistItems: [ChecklistItem]
    
        init (id: String, agencyId: String, name: String, checklistItems: [ChecklistItem]) {
            self.id = id
            self.agencyId = agencyId
            self.name = name
            self.checklistItems = checklistItems
        }
    
        init(unboxer: Unboxer) throws {
            id = unboxer.unbox(key: "id") ?? ""
            agencyId = unboxer.unbox(key: "agencyId") ?? ""
            name = unboxer.unbox(key: "name") ?? ""
            checklistItems = unboxer.unbox(key: "checklistItems") ?? []
        }
    }
    
    extension Checklist: Storable {
        init(warehouse: Warehouseable) {
            self.id = warehouse.get("id") ?? ""
            self.agencyId = warehouse.get("agencyId") ?? ""
            self.name = warehouse.get("name") ?? ""
            self.checklistItems = warehouse.get("checklistItems") ?? [ChecklistItem]()
        }
    
        func toDictionary() -> [String : Any] {
            return [ "id": self.id, "agencyId": self.agencyId, "name": self.name, "checklistItems": self.checklistItems ]
        }
    }
    
    struct ChecklistItem: Unboxable {
        let id: String
        let checklistId: String
        let title: String
        var checked: Bool = false
    
        init(id: String, checklistId: String, title: String, checked: Bool) {
            self.id = id
            self.checklistId = checklistId
            self.title = title
            self.checked = checked
        }
    
        init(unboxer: Unboxer) throws {
            id = unboxer.unbox(key: "id") ?? ""
            checklistId = unboxer.unbox(key: "checklistId") ?? ""
            title = unboxer.unbox(key: "title") ?? ""
        }
    }
    
    extension ChecklistItem: Storable {
        init(warehouse: Warehouseable) {
            self.id = warehouse.get("id") ?? ""
            self.checklistId = warehouse.get("checklistId") ?? ""
            self.title = warehouse.get("title") ?? ""
            checked = warehouse.get("checked") ?? false
        }
    
        func toDictionary() -> [String : Any] {
            return [ "id": self.id, "checklistId": self.checklistId, "title": self.title, "checked": self.checked ]
        }
    }
    

    checklist.toDictionary() produces the following:

    ["agencyId": "00000000-0000-0000-0000-000000000001", "id": "00000000-0000-0000-0000-000000000001", "checklistItems": [vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000006", checklistId: "00000000-0000-0000-0000-000000000001", title: "Opaľovací krém", checked: false), vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000005", checklistId: "00000000-0000-0000-0000-000000000001", title: "Slnečné okuliare", checked: false), vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000004", checklistId: "00000000-0000-0000-0000-000000000001", title: "Plavky", checked: false), vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000003", checklistId: "00000000-0000-0000-0000-000000000001", title: "Teplé oblečenie na večer", checked: false), vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000002", checklistId: "00000000-0000-0000-0000-000000000001", title: "Lieky", checked: false), vacatuner.ChecklistItem(id: "00000000-0000-0000-0000-000000000001", checklistId: "00000000-0000-0000-0000-000000000001", title: "Pas", checked: true)], "name": "Europa"]
    

    when I call Pantry.pack() on the checklist instance, I stumble upon a crash in JSONWarehouse.swift:

    let data = try JSONSerialization.data(withJSONObject: storableDictionary, options: .prettyPrinted)
    
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (_SwiftValue)'
    
    opened by alexwald 6
  • Crash after Swift 3 update

    Crash after Swift 3 update

    Hello guys, I'm having a serious problem with Pantry. I didn't change anything from what I had before the update (it's a project that could wait). After this update, Pantry crashes at every pack call. I pack arrays (of every type, even custom structs that conforms to the Storable protocol) and other kind of stuff. Before the update everything was working good, not now.

    One of the call is simple as :

    Pantry.pack(self, key: pantryKeyCurrentUser)

    Here crashes pointing me to this line (number 118 of file JSONWarehouse):

    let data = try JSONSerialization.data(withJSONObject: storableDictionary, options: .prettyPrinted)

    The error is EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP, subcode = 0x0)

    opened by andrealufino 40
  • Default Implementation

    Default Implementation

    Hey! Good job on Pantry! I've built a new extension that inherits from Storable. Added extensions for default implementations (It uses Reflection to generate property-value pairs that's then typecasted to a dictionary. Now, if you want to add caching behaviour to a Struct, simply add Pantryable in an extension. Here's a Gist

    //Extensions for Pantry using reflection. Default implementation
    
    
    public protocol Pantryable: Storable {
        func allProperties() throws -> [String: Any]
        func toDictionary() -> [String : AnyObject]
    }
    
    
    extension Pantryable {
     public func allProperties() throws -> [String: Any] {
            var result: [String: Any] = [:]
    
            let mirror = Mirror(reflecting: self)
    
            guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
                //throw some error
                throw NSError(domain: "com.kayako.kayako", code: 666, userInfo: nil)
            }
    
            for (labelMaybe, valueMaybe) in mirror.children {
                guard let label = labelMaybe else {
                    continue
                }
                result[label] = valueMaybe
            }
            return result
        }
    
        public func toDictionary() -> [String : AnyObject] {
            do {
                let properties = try self.allProperties()
                var result:[String: AnyObject] = [:]
                for (key,value) in properties {
                    if let v = value as? AnyObject {
                        result[key] = v
                    }
                }
                return result
    
            } catch {
                fatalError("properties can't be retrieved")
            }
    
        }
    
    }
    
    opened by codeOfRobin 0
Owner
Nick O'Neill
software generalist, engineering manager by day, technical director at @5calls by night
Nick O'Neill
💡 A light Swift wrapper around Objective-C Runtime

A light wrapper around Objective-C Runtime. What exactly is lumos? lumos as mentioned is a light wrapper around objective-c runtime functions to allow

Suyash Shekhar 139 Dec 19, 2022
Automatically set your keyboard's backlight based on your Mac's ambient light sensor.

QMK Ambient Backlight Automatically set your keyboard's backlight based on your Mac's ambient light sensor. Compatibility macOS Big Sur or later, a Ma

Karl Shea 29 Aug 6, 2022
BCSwiftTor - Opinionated pure Swift controller for Tor, including full support for Swift 5.5 and Swift Concurrency

BCSwiftTor Opinionated pure Swift controller for Tor, including full support for

Blockchain Commons, LLC — A “not-for-profit” benefit corporation 4 Oct 6, 2022
Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Apple 2k Dec 28, 2022
Swift-DocC is a documentation compiler for Swift frameworks and packages aimed at making it easy to write and publish great developer documentation.

Swift-DocC is a documentation compiler for Swift frameworks and packages aimed at making it easy to write and publish great developer docum

Apple 833 Jan 3, 2023
Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. (Pure Swift, Supports Linux)

SwiftFoundation Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. Goals Provide a cross-platform in

null 620 Oct 11, 2022
Swift - ✏️Swift 공부 저장소✏️

Swift 스위프트의 기초 1. Swift의 기본 2. 변수와 상수 [3. 데이터 타입 기본] [4. 데이터 타입 고급] 5. 연산자 6. 흐름 제어 7. 함수 8. 옵셔널 객체지향 프로그래밍과 스위프트 9. 구조체와 클래스 10. 프로퍼티와 메서드 11. 인스턴스 생

Jiwon 0 Mar 9, 2022
Swift-ndi - Swift wrapper around NewTek's NDI SDK

swift-ndi Swift wrapper around NewTek's NDI SDK. Make sure you extracted latest

Alessio Nossa 12 Dec 29, 2022
__.swift is a port of Underscore.js to Swift.

__.swift Now, __.swift is version 0.2.0! With the chain of methods, __.swift became more flexible and extensible. Documentation: http://lotz84.github.

Tatsuya Hirose 86 Jun 29, 2022
SNTabBarDemo-Swift - Cool TabBar With Swift

SNTabBarDemo-Swift Cool TabBar How To Use // MARK: - setup private func setu

iAnchor 3 Sep 29, 2022
Swift-when - Expression switch support in Swift

Swift When - supporting switch expressions in Swift! What is it? Basically, it a

Gordan Glavaš 7 Nov 24, 2022
Swift-compute-runtime - Swift runtime for Fastly Compute@Edge

swift-compute-runtime Swift runtime for Fastly Compute@Edge Getting Started Crea

Andrew Barba 57 Dec 24, 2022
Swift-HorizontalPickerView - Customizable horizontal picker view component written in Swift for UIKit/iOS

Horizontal Picker View Customizable horizontal picker view component written in

Afraz Siddiqui 8 Aug 1, 2022
swift-highlight a pure-Swift data structure library designed for server applications that need to store a lot of styled text

swift-highlight is a pure-Swift data structure library designed for server applications that need to store a lot of styled text. The Highlight module is memory-efficient and uses slab allocations and small-string optimizations to pack large amounts of styled text into a small amount of memory, while still supporting efficient traversal through the Sequence protocol.

kelvin 4 Aug 14, 2022
Sovran-Swift: Small, efficient, easy. State Management for Swift

Sovran-Swift: Small, efficient, easy. State Management for Swift

Segment 5 Jan 3, 2023
Approximate is a Swift package that provides implementations of floating point comparisons for the Swift ecosystem

Approximate Approximate floating point equality comparisons for the Swift Programming Language. Introduction Approximate is a Swift package that provi

Christopher Blanchard 1 Jun 1, 2022
A Swift app, named 'iPose', for iPhone's pose measurement based on Swift.

iPhone's pose measurement based on Swift. This is a Swift app, named 'iPose', for iPhone's pose measurement based on Swift. This is a side project to

Ghasem Abdi 3 Jul 26, 2022
Swift Package Manager plugin which runs ActionBuilder to create a Github Actions workflow for a swift package.

ActionBuilderPlugin A Swift Package Manager command which builds a Github Actions workflow for the current package. By default the workflow file will

Elegant Chaos 4 Jul 20, 2022
Swift Server Implementation - RESTful APIs, AWS Lambda Serverless For Swift Runtime amazonlinux: AWS Lambda + API Gateway

Swift Server Implementation - RESTful APIs, AWS Lambda Serverless For Swift Runtime amazonlinux: AWS Lambda + API Gateway deployed on Graviton arm64 build swift:5.6.2-amazonlinux2-docker image

Furqan 2 Aug 16, 2022