{"id":1010,"url":"https://github.com/metasmile/PropertyKit","last_synced_at":"2025-07-30T20:30:40.649Z","repository":{"id":62451193,"uuid":"146090642","full_name":"metasmile/PropertyKit","owner":"metasmile","description":"A protocol-centric, type and queue safe key-value workflow.","archived":false,"fork":false,"pushed_at":"2019-04-06T15:33:53.000Z","size":84,"stargazers_count":12,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-14T10:03:16.342Z","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/metasmile.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":"2018-08-25T11:42:25.000Z","updated_at":"2021-02-26T08:09:25.000Z","dependencies_parsed_at":"2022-11-01T23:33:03.527Z","dependency_job_id":null,"html_url":"https://github.com/metasmile/PropertyKit","commit_stats":null,"previous_names":["metasmile/propertydefaults"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/metasmile/PropertyKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metasmile%2FPropertyKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metasmile%2FPropertyKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metasmile%2FPropertyKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metasmile%2FPropertyKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metasmile","download_url":"https://codeload.github.com/metasmile/PropertyKit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metasmile%2FPropertyKit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267935130,"owners_count":24168267,"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","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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.961Z","updated_at":"2025-07-30T20:30:40.363Z","avatar_url":"https://github.com/metasmile.png","language":"Swift","funding_links":[],"categories":["Database"],"sub_categories":["Getting Started","Linter"],"readme":"\u003cimg width=\"447\" alt=\"PropertyKit\" src=\"https://github.com/metasmile/PropertyKit/raw/master/title.png?raw=true\"\u003e\n\n[![swift](https://img.shields.io/badge/Awesome-iOS-red.svg)](https://github.com/vsouza/awesome-ios#database)\n[![cocoapods compatible](https://img.shields.io/badge/cocoapods-compatible-brightgreen.svg)](https://cocoapods.org/pods/PropertyKit)\n[![carthage compatible](https://img.shields.io/badge/carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage)\n[![language](https://img.shields.io/badge/spm-compatible-brightgreen.svg)](https://swift.org)\n[![platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS-lightgrey.svg)](https://developer.apple.com/develop/)\n[![swift](https://img.shields.io/badge/swift-4.2,5.x-orange.svg)](https://github.com/metasmile/PropertyKit/releases)\n\nLight-weight, strict protocol-first styled PropertyKit helps you to easily and safely handle guaranteed values, keys or types on various situations of the large-scale Swift project on iOS, macOS and tvOS.\n\n## Installation\n\n[Detail Guide](https://github.com/metasmile/PropertyKit/blob/master/INSTALL.md)\n\n\nCocoaPods\n```ruby\npod 'PropertyKit'\n```\n\nCarthage\n```ogdl\ngithub \"metasmile/PropertyKit\"\n```\n\nSwift Package Manager\n```swift\n.Package(url: \"https://github.com/metasmile/PropertyKit.git\")\n```\n\n\n## Modules\n\n- [PropertyWatchable](https://github.com/metasmile/PropertyKit/blob/master/README.md#propertywatchable) *for NSKeyValueObservation*\n- [PropertyDefaults](https://github.com/metasmile/PropertyKit/blob/master/README.md#propertydefaults) *for UserDefaults*\n- ~\n\n\n\n## PropertyDefaults\n\nThe simplest, but reliable way to manage UserDefaults. PropertyDefaults automatically binds value and type from Swift property to UserDefaults keys and values. It forces only protocol extension pattern that is focusing on syntax-driven value handling, so it helps to avoid unsafe String key use. Therefore, natually, Swift compiler will protect all of missing or duplicating states with its own. \n\nPropertyDefaults is new hard fork of [DefaultsKit](https://github.com/nmdias/DefaultsKit) by [nmdias](https://github.com/nmdias) with entirely different approaches.\n\n\n### Features\n\n- [x] Swift 4 Codable Support\n- [x] Compile-time UserDefaults guaranteeing. \n- [x] Key-Type-Value relationship safety, no String literal use.\n- [x] Structural extension-protocol-driven, instead of an intension.\n- [x] Permission control\n- [ ] Automatic private scope - In File, Class or Struct, Function\n\n### Usage\n\nAn example to define automatic UserDefaults keys with basic Codable types:\n```swift\nextension Defaults: PropertyDefaults {\n    public var autoStringProperty: String? {\n        set{ set(newValue) } get{ return get() }\n    }\n    public var autoDateProperty: Date? {\n        set{ set(newValue) } get{ return get() }\n    }\n}\n```\n\n```swift\nvar sharedDefaults = Defaults()\nsharedDefaults.autoStringProperty = \"the new value will persist in shared scope\"\n// sharedDefaults.autoStringProperty == Defaults.shared.autoStringProperty\n\nDefaults.shared.autoStringProperty = \"another new value will persist in shared scope\"\n// Defaults.shared.autoStringProperty == sharedDefaults.autoStringProperty\n\nvar localDefaults = Defaults(suiteName:\"local\")\nlocalDefaults.autoStringProperty = \"the new value will persist in local scope\"\n// localDefaults.autoStringProperty != Defaults.shared.autoStringProperty\n```\n\nDirectly save/load as Codable type\n```swift\npublic struct CustomValueType: Codable{\n    var key:String = \"value\"\n    var date:Date?\n    var data:Data?\n}\nextension Defaults: PropertyDefaults {\n    // non-optional - must define the default value with the keyword 'or'\n    public var autoCustomNonOptionalProperty: CustomValueType {\n        set{ set(newValue) } get{ return get(or: CustomValueType()) }\n    }\n    // optional with/without setter default value\n    public var autoCustomOptionalProperty: CustomValueType? {\n        set{ set(newValue) } get{ return get() }\n    }\n    public var autoCustomOptionalPropertySetterDefaultValue: CustomValueType? {\n        set{ set(newValue, or: CustomValueType()) } get{ return get() }\n    }\n}\n```\n\n\nStrongly guaranteeing unique key with Swift compiler.\n```swift\n//CodeFile1_ofLargeProject.swift\nprotocol MyDefaultsKeysUsingInA : PropertyDefaults{\n    var noThisIsMyKeyNotYours:Int?{ get }\n}\nextension Defaults : MyDefaultsKeysUsingInA{\n    var noThisIsMyKeyNotYours:Int?{ set{ set(newValue) } get{ return get() } }\n}\n\n//CodeFile2_ofLargeProject.swift\nprotocol MyDefaultsKeysUsingInB : PropertyDefaults{\n    var noThisIsMyKeyNotYours:Int?{ get }\n}\nextension Defaults : MyDefaultsKeysUsingInB{\n    var noThisIsMyKeyNotYours:Int?{ set{ set(newValue) } get{ return get() } }\n}\n```\n\n```bash\n❗️Swift Compiler Error\n~.swift:30:9: Invalid redeclaration of 'noThisIsMyKeyNotYours'\n~.swift:21:9: 'noThisIsMyKeyNotYours' previously declared here\n```\n\nWith this pattern, as you know, you also can control access permission with the protocol. It means you can use 'private' or 'file-private' defaults access.\n\n```swift\n// MyFile.swift\nfileprivate protocol PrivateDefaultKeysInThisSwiftFile: PropertyDefaults{\n    var filePrivateValue: String? {set get}\n}\n\nextension Defaults: PrivateDefaultKeysInThisSwiftFile {\n    public var filePrivateValue: String? {\n        set{ set(newValue) } get{ return get() }\n    }\n}\n\n// Can access - 👌\nDefaults.shared.filePrivateValue\n```\n\n```swift\n// MyOtherFile.swift\n\n// Not able to access - ❌\nDefaults.shared.filePrivateValue\n```\n\nAnd, Yes, It's a hack way to crack our design intention.  \n\n```swift\nvar p1:Int{\n    Defaults.shared.set(2)  \n    return Defaults.shared.get(or:0)  \n}\nvar p2: Int{\n    return Defaults.shared.get(or:0)  \n}\n\np1 // == 2\np2 // == 0\n//It means that are function/property-scoped capsulated defaults values.\n```\n\n## PropertyWatchable\n\nA protocol extension based on NSKeyValueObservation. It simply enables to let a class object become a type-safe keypath observable object. And unique observer identifier will be assigned to all observers automatically. That prevents especially duplicated callback calls and so it can let you atomically manage a bunch of key-value flows between its joined queues.\n\n### Features\n\n- [x] Making an observable object with only protocol use.\n- [x] Swift property literal based keypath observation.\n- [x] Strictful type-guaranteed callback parameter support.\n- [x] Automatic unique identifier support.\n- [x] File-scoped observer removing support.\n- [ ] Queue-private atomic operation support.\n\n### Usage\n\nThe simplest example to use.\n```swift\nclass WatchableObject:NSObject, PropertyWatchable{\n    @objc dynamic\n    var testingProperty:String?\n}\n\nlet object = WatchableObject()\nobject.watch(\\.testingProperty) {\n    object.testingProperty == \"some value\"\n    //Do Something.\n}\n\nobject.testingProperty = \"some value\"\n```\n\nAll options and strongly typed-parameters are same with NSKeyValueObservation. \n```swift\n// Default option is the default of NSKeyValueObservation (.new)\n// (WatchableObject, NSKeyValueObservedChange\u003cValue\u003e)\nobject.watch(\\.testingProperty, options: [.initial, .new, .old]) { (target, changes) in     \n    target.testingProperty == \"some value\"\n    //Dd Something.\n}\n```\n```swift\nlet object = WatchableObject()\nobject.testingProperty = \"initial value\"\nconfig.watch(\\.testingProperty, options: [.initial]) { (o, _) in\n    o.testingProperty == \"initial value\"\n}\n```\n\nAutomatic line by line identifier support.\n```swift\nobject.watch(\\.testingProperty) {\n    // Listening as a unique observer 1\n}\n\nobject.watch(\\.testingProperty) {\n    // Listening as a unique observer 2\n}\n\nobject.watch(\\.testingProperty, id:\"myid\", options:[.old]) {\n    // Listening as an observer which has identifier \"myid\"\n}\n\n// total 3 separated each observers are listening each callbacks.\n```\n\nRemove observations with various options.\n\n```swift\n// Remove only an observer which has \"myid\" only\nobject.unwatch(\\.testingProperty, forIds:[\"myid\"])\n\n// Remove all observers that are watching \".testingProperty\"\nobject.unwatch(\\.testingProperty)\n\n//Automatically remove all observers in current file.\nobject.unwatchAllFilePrivate()\n\n//Automatically remove entire observers in application-wide.\nobject.unwatchAll()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetasmile%2FPropertyKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetasmile%2FPropertyKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetasmile%2FPropertyKit/lists"}