{"id":984,"url":"https://github.com/tasanobu-zz/TypedDefaults","last_synced_at":"2025-07-30T19:33:13.335Z","repository":{"id":56924649,"uuid":"53901885","full_name":"tasanobu-zz/TypedDefaults","owner":"tasanobu-zz","description":"TypedDefaults is a utility library to type-safely use NSUserDefaults.","archived":false,"fork":false,"pushed_at":"2017-10-30T09:44:02.000Z","size":30,"stargazers_count":110,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-29T19:52:51.348Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tasanobu-zz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-15T00:26:38.000Z","updated_at":"2022-02-06T10:06:13.000Z","dependencies_parsed_at":"2022-08-20T22:10:05.741Z","dependency_job_id":null,"html_url":"https://github.com/tasanobu-zz/TypedDefaults","commit_stats":null,"previous_names":["tasanobu/typeddefaults"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tasanobu-zz%2FTypedDefaults","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tasanobu-zz%2FTypedDefaults/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tasanobu-zz%2FTypedDefaults/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tasanobu-zz%2FTypedDefaults/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tasanobu-zz","download_url":"https://codeload.github.com/tasanobu-zz/TypedDefaults/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227895642,"owners_count":17836443,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:36.267Z","updated_at":"2024-12-04T19:32:28.057Z","avatar_url":"https://github.com/tasanobu-zz.png","language":"Swift","funding_links":[],"categories":["Database"],"sub_categories":["Getting Started"],"readme":"TypedDefaults\n===\n\n[![Language](http://img.shields.io/badge/language-swift-brightgreen.svg?style=flat\n)](https://developer.apple.com/swift)\n[![CocoaPods](https://img.shields.io/cocoapods/v/TypedDefaults.svg)]()\n[![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat\n)](http://mit-license.org)\n[![Issues](https://img.shields.io/github/issues/tasanobu/TypedDefaults.svg?style=flat\n)](https://github.com/tasanobu/TypedDefaults/issues?state=open)\n\n`TypedDefaults` is a utility library to type-safely use NSUserDefaults.\n\n#### Motivation\nThe talk *[Keep Calm and Type Erase On](https://realm.io/news/tryswift-gwendolyn-weston-type-erasure/)* by [Gwendolyn Weston](https://github.com/gwengrid) at [try! Swift 2016](http://www.tryswiftconf.com) is great and it inspired me to apply the technique *Type Erasure* for actual cases in app development.\n\n#### Installation\n- Install with CocoaPods\n\n  ```ruby\n  use_frameworks!\n\n  platform :ios, '8.0'\n\n  pod 'TypedDefaults'\n  ```\n\n#### Requirements\n- iOS 8.0+\n- Swift 4.0\n- Xcode 9\n\n## Features\n- Custom types can be type-safely stored in NSUserDefaults\n- Dependency Injection support\n\n## Usage\n\n### Custom types\nCustom types can be type-safely stored in NSUserDefaults.\u003cbr/\u003e\nCustom types only need to adopt `DefaultsConvertible` protocol as described later. No need to inherit NSObject.\u003cbr/\u003e\nTherefore, `Swift native Class` `Struct` and `Enum` are available for custom types.(Of course, subclasses of NSObject are available.)  \n\n#### `DefaultsConvertible` protocol\n```swift\npublic protocol DefaultConvertible {\n\n    static var key: String { get }\n\n    init?(_ object: Any)\n\n    func serialize() -\u003e Any\n}\n```\n\nCustom types are stored in NSUserDefaults as AnyObject.\u003cbr/\u003e\n`serialize()` is called when saving and `init?(_ object:)` is called when getting from NSUserDefaults.\u003cbr/\u003e\n\u003cbr/\u003e\nIt's assuemd each custom type and one configuration used in app is one-to-one relation.\u003cbr/\u003e\nTherefore, `key` is prepared as type property in order to assign `key` to one `custom type`.\n\n##### Example of custom type\nThis is an example of the custom type with `flag for saving photo to CameraRoll` and `Photo Size` as camera configuration.\n\n```swift\nstruct CameraConfig: DefaultConvertible {\n    enum Size: Int {\n        case Large, Medium, Small\n    }\n\n    var saveToCameraRoll: Bool\n    var size: Size\n\n    // MARK: DefaultConvertible\n\n    static let key = \"CameraConfig\"\n\n    init?(_ object: Any) {\n        guard let dict = object as? [String: Any] else {\n          return nil\n        }\n\n        self.saveToCameraRoll = dict[\"cameraRoll\"] as? Bool ?? true\n        if let rawSize = dict[\"size\"] as? Int,\n         let size = Size(rawValue: rawSize) {\n            self.size = size\n         } else {\n            self.size = .Medium\n        }\n    }\n\n    func serialize() -\u003e Any {\n        return [\"cameraRoll\": saveToCameraRoll, \"size\": size.rawValue]\n    }\n}\n```\n\n#### Saving custom type to NSUserDefaults\n`PersistentStore` is the class to save custom types to NSUserDefaults.\u003cbr/\u003e\nBelow is the sample of how to use it.\n\n```swift\n/// Specify a custom type when initializing PersistentStore\nlet userDefaults = PersistentStore\u003cCameraConfig\u003e()\n\n// Make an instance of CameraConfig\nvar cs = CameraConfig([:])!\n\n// Set\nuserDefaults.set(cs)\n// Get\nuserDefaults.get()?.size // Medium\n\n/// Change the size\ncs.size = .Large\n\n// Set\nuserDefaults.set(cs)\n// Get\nuserDefaults.get()?.size // Large\n```\n\n### Dependency Injection support\nNSuserDefaults is not Unit Test friendly because it persistently stores data on file system.\u003cbr/\u003e\n`TypedDefaults` has the types `InMemoryStore` `AnyStore` for Dependency Injection in order to test types which behave differently depending on custom types stored in NSuserDefaults.\u003cbr/\u003e\n\u003cbr/\u003e\n`InMemoryStore` adopts `DefaultStoreType` protocol as well as `PersistentStore`. \u003cbr/\u003e\nHowever, `InMemoryStore` retains custom types only on memory, which is different from `PersistentStore`.\u003cbr/\u003e\nAs for `AnyStore`, it is the type to abstract `PersistentStore` and `InMemoryStore`.\n\n#### Example\nThis is the example to use `InMemoryStore` and `AnyStore` instead of `PersistentStore` at Unit Test.\u003cbr/\u003e\n\u003cbr/\u003e\nThere is a class called `CameraViewController` which inherits UIViewController.\u003cbr/\u003e\nIt has a property `config` to retain a custom type saved in NSuserDefaults. To support Dependency Injection, set `AnyStore` as the type of `config`.\n\n```\nclass CameraViewController: UIViewController {\n    lazy var config: AnyStore\u003cCameraConfig\u003e = {\n        let ds = PersistentStore\u003cCameraConfig\u003e()\n        return AnyStore(ds)\n    }()\n\n    ...\n}\n```\n\nBecause the type of `config` is not `PersistentStore` but `AnyStore`, it can be replaced with `InMemoryStore` at Unit Test as below.\n\n```\nclass CameraViewControllerTests: XCTestCase {\n    var viewController: CameraViewController!\n\n    override func setUp() {\n        viewController = CameraViewController()\n\n        let defaultConfig = CameraConfig([:])!\n        let ds = InMemoryStore\u003cCameraConfig\u003e()\n        ds.set(defaultConfig) //\n        viewController.config = AnyStore(ds)\n    }\n}\n```\n\n## Release Notes\nSee https://github.com/tasanobu/TypedDefaults/releases\n\n## License\n`TypedDefaults` is released under the MIT license. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftasanobu-zz%2FTypedDefaults","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftasanobu-zz%2FTypedDefaults","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftasanobu-zz%2FTypedDefaults/lists"}