{"id":13818413,"url":"https://github.com/ZkHaider/DiffValue","last_synced_at":"2025-05-15T23:31:59.269Z","repository":{"id":83713814,"uuid":"220948050","full_name":"ZkHaider/DiffValue","owner":"ZkHaider","description":"Diff Observation PropertyWrapper for Equatable types","archived":false,"fork":false,"pushed_at":"2019-12-21T23:15:33.000Z","size":1678,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-05T07:01:48.360Z","etag":null,"topics":["combine","continuous-integration","diff","diffing","ios","macos","macosx","property","property-observers","property-wrapper","property-wrappers","redux","swift","swift5","tvos","watchos"],"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/ZkHaider.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,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-11-11T09:43:04.000Z","updated_at":"2022-12-17T00:44:06.000Z","dependencies_parsed_at":"2023-03-12T19:26:11.636Z","dependency_job_id":null,"html_url":"https://github.com/ZkHaider/DiffValue","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZkHaider%2FDiffValue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZkHaider%2FDiffValue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZkHaider%2FDiffValue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZkHaider%2FDiffValue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZkHaider","download_url":"https://codeload.github.com/ZkHaider/DiffValue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225387197,"owners_count":17466377,"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":["combine","continuous-integration","diff","diffing","ios","macos","macosx","property","property-observers","property-wrapper","property-wrappers","redux","swift","swift5","tvos","watchos"],"created_at":"2024-08-04T07:00:45.389Z","updated_at":"2024-11-19T16:30:59.630Z","avatar_url":"https://github.com/ZkHaider.png","language":"Swift","funding_links":[],"categories":["Awesome Repositories"],"sub_categories":[],"readme":"\u003cp align=\"left\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/ZkHaider/DiffValue/master/.github/Assets/diffvalue_title.png\" alt=\"DiffValue by ZkHaider\" width=\"400\" /\u003e\n\u003c/p\u003e\n\n[![Build Status](https://travis-ci.com/ZkHaider/DiffValue.svg?branch=master)](https://travis-ci.com/ZkHaider/DiffValue) ![Platforms](https://img.shields.io/badge/platform-iOS%20%7C%20MacOS%20%7C%20tvOS%20%7C%20watchOS-brightgreen) ![Swift Version](https://img.shields.io/badge/swift-5.1-blue)\n\n#\n\nDiffValue is a property observation tool that utilizes automatic diffing on properties through Combine and Property Wrappers. \n\n\n\n## Installation\n\nDiffValue is available via Carthage, just add to your Cartfile like so:\n\n`Cartfile`\n```\n# Property obsersation\ngithub \"ZkHaider/DiffValue\" \"master\"\n```\n\n`Terminal`\n```shell \n$ carthage update DiffValue\n```\n\n## Usage \n\nIt's easy to observe only properties you are interested in, you can do so like this:\n\n```swift \nstruct UserState {\n    let userName: String \n    let email: String\n    let password: String\n}\n\nextension UserState: EquatableWithIdentity {\n    /// Default value\n    static var identity: UserState {\n        UserState(\n            userName: \"\",\n            email: String,\n            password: String\n        )\n    }\n}\n\nfinal class ViewController: UIViewController {\n\n    // MARK: - State\n    \n    @Diff(\\.userName, \\.email)\n    var userState; UserState \n    \n    // MARK: - Lifecycle \n    \n    override func viewDidLoad() {\n        super.viewDidLoad() \n        \n        // Start listening to state changes \n        // This is only called when your specified key paths are updated\n        let subscription = $userState.sink { state in \n            print(\"Listening to state changes: \\(state)\")\n        }\n    }\n\n}\n```\n\nThat's it! Use `KeyPath` to specify which properties you are interested in. You can optionally choose to conform to `EquatableWithIdentity`, however any value that you want to be utilized by `@Diff` needs to be `Equatable`. If you opt out of `EquatableWithIdentity` you will have to pass a default value in the property wrapper:\n\n```swift \nstruct UserState {\n    let userName: String \n    let email: String\n    let password: String\n}\n\nfinal class ViewController: UIViewController {\n\n    // MARK: - State\n    \n    @Diff(\n        value: UserState(userName: \"\", email: \"\", password: \"\"),\n        \\.userName, \n        \\.email\n    )\n    var userState; UserState \n    \n    // MARK: - Lifecycle \n    \n    override func viewDidLoad() {\n        super.viewDidLoad() \n        \n        // Start listening to state changes \n        // This is only called when your specified key paths are updated\n        let subscription = $userState.sink { state in \n            print(\"Listening to state changes: \\(state)\")\n        }\n    }\n\n}\n```\n\nIf no `KeyPath\u003cRoot, Value\u003e` are passed into the `@Diff` wrapper it will just do an equality check like normal based on `Equatable`:\n\n```swift \nstruct UserState {\n    let userName: String \n    let email: String\n    let password: String\n}\n\nfinal class ViewController: UIViewController {\n\n    // MARK: - State\n    \n    @Diff()\n    var userState; UserState \n    \n    // MARK: - Lifecycle \n    \n    override func viewDidLoad() {\n        super.viewDidLoad() \n        \n        // Start listening to state changes \n        // This is only called when your specified key paths are updated\n        // Invoked via equatable if we detect any changes because \n        // we did not pass in any keypaths\n        let subscription = $userState.sink { state in \n            print(\"Listening to state changes: \\(state)\")\n        }\n    }\n\n}\n```\n\n`DiffValue` supports passing up to `10 KeyPath\u003cRoot, Value\u003e` parameters in the initializer, if you require more you will have to pass an array of `DiffableKeyPath\u003cRoot\u003e` types like:\n\n```swift\nstruct MyVeryLargeState {\n    let property1: String \n    let property2: String\n    let property3: String\n    let property4: String\n    let property5: String\n    let property6: String\n    let property7: String\n    let property8: String\n    let property9: String\n    let property10: String\n    let property11: String\n    let property12: String\n}\n\nextension MyVeryLargeState: EquatableWithIdentity {\n    /// Default value\n    static var identity: MyVeryLargeState {\n        MyVeryLargeState(\n            property1: \"\", \n            property2: \"\",\n            property3: \"\",\n            property4: \"\",\n            property5: \"\",\n            property6: \"\",\n            property7: \"\",\n            property8: \"\",\n            property9: \"\",\n            property10: \"\",\n            property11: \"\",\n            property12: \"\"\n        )\n    }\n}\n\nfinal class ViewController: UIViewController {\n\n    // MARK: - State\n    \n    @Diff(keyPaths: \n        (\\MyVeryLargeState.property1).diffable, \n        (\\MyVeryLargeState.property2).diffable,\n        (\\MyVeryLargeState.property3).diffable,\n        (\\MyVeryLargeState.property4).diffable,\n        (\\MyVeryLargeState.property5).diffable,\n        (\\MyVeryLargeState.property6).diffable,\n        (\\MyVeryLargeState.property7).diffable,\n        (\\MyVeryLargeState.property8).diffable,\n        (\\MyVeryLargeState.property9).diffable,\n        (\\MyVeryLargeState.property10).diffable,\n        (\\MyVeryLargeState.property11).diffable,\n        (\\MyVeryLargeState.property12).diffable,\n    )\n    var largeState; MyVeryLargeState \n    \n    // MARK: - Lifecycle \n    \n    override func viewDidLoad() {\n        super.viewDidLoad() \n    }\n\n}\n```\n\nChances are if the  `State` encapsulates a large set of properties it probably needs to be divided up -- always keep your `State` lightweight! However this library does support as many `KeyPath\u003cRoot, Value\u003e`s as you wish to diff on!\n\nA `@Diff` property wrapper exposes a `CurrentValueRelay\u003cRoot, Never\u003e`. This is a `Publisher` with a private `CurrentValueSubject\u003cRoot, Never\u003e` field. This is hidden so you cannot pass a `completion` event to the `Relay`. Use the `Relay` to subscribe your `State` to other `Subscribers`! \n\n## Property Observation \n\nYou can also observe single properties directly without having to observe entire value changes:\n\n```swift\nstruct State {\n    let stringProperty: String\n    let intProperty: Int\n}\n\nfinal class ExampleClass {\n\n    @Diff(\\.stringProperty)\n    var state: State\n\n}\n\nfinal class TestClass {\n\n    let exampleClass: ExampleClass = ExampleClass()\n\n    init() {\n        exampleClass.$state.add( \n            \\.stringProperty,\n            target: self,\n            hook: .method(TestClass.observeString)\n        )\n    }\n\n    private func observeString(_ stringValue: String) {\n        print(\"📝 Property Changed: \\(stringValue)\")\n    }\n\n}\n```\n\n#\n\nHere is fully fledged example:\n\n```swift \nstruct State {\n    let stringProperty: String\n    let intProperty: Int\n}\n\nfinal class ExampleClass {\n    \n    @Diff(\\.stringProperty, \\.intProperty)\n    var state1: State\n    \n    @Diff(\\.intProperty)\n    var state2: State\n    \n    @Diff(\\.stringProperty)\n    var state3: State\n}\n\nvar modifiedState = State(stringProperty: \"\", intProperty: 0)\n\n/// Setup subscriptions\n\n// State 1\n\nlet relay1 = exampleClass.$state1\nlet replay1 = relay1\n    .print(\"DiffStateReplay1\")\n    .singleReplay()\n\nreplay1.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\nreplay1.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\n// State 2\n\nlet relay2 = exampleClass.$state2\nlet replay2 = relay2\n    .print(\"DiffStateReplay2\")\n    .singleReplay()\n\nreplay2.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\nreplay2.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\n// State 3\n\nlet relay3 = exampleClass.$state3\nlet replay3 = relay3\n    .print(\"DiffStateReplay3\")\n    .singleReplay()\n\nreplay3.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\nreplay3.sink { (state) in\n    \n}.store(in: \u0026subscriptions)\n\nprint(\"\"\"\n\n-------------------------------------------------\nAll Initial Sink Values should be established ✅\n-------------------------------------------------\n\n\"\"\")\n\nprint(\"\"\"\n\n-------------------------------------------------\nBeginning State 1 Modifications\n-------------------------------------------------\n\n\"\"\")\n\n/**\n   State 1 Modifications\n*/\n\n// No modification nothing should print\nexampleClass.state1 = modifiedState\n\nprint(\"Nothing should have printed 👀\")\n\n// Modify state only change string property and set to state 1\nmodifiedState = State(stringProperty: \"Hello world\", intProperty: 0)\nexampleClass.state1 = modifiedState\n\n// Should print a change now for state 1\n\n// Modify state only change int property and set to state 1\nmodifiedState = State(stringProperty: \"Hello world\", intProperty: 10)\nexampleClass.state1 = modifiedState\n\n// Should print a change now for state 1\n\n/**\n    State 2 Modifications\n */\n\nprint(\"\"\"\n\n-------------------------------------------------\nBeginning State 2 Modifications\n-------------------------------------------------\n\n\"\"\")\n\n// Modify state only change string property and set to state 2\nmodifiedState = State(stringProperty: \"Hello world\", intProperty: 0)\nexampleClass.state2 = modifiedState\n\n// Nothing should print because we are not diffing on string property\n\nprint(\"Nothing should have printed 👀\")\n\n// Modify state and this time change int property and set to state 2\nmodifiedState = State(stringProperty: \"Hello world\", intProperty: 10)\nexampleClass.state2 = modifiedState\n\n// Should print a change now for state 2\n\n/**\n  State 3 Modifications\n*/\n\nprint(\"\"\"\n\n-------------------------------------------------\nBeginning State 3 Modifications\n-------------------------------------------------\n\n\"\"\")\n\n// Modify state only change int property and set to state 3\nmodifiedState = State(stringProperty: \"\", intProperty: 10)\nexampleClass.state3 = modifiedState\n\n// Nothing should print because we are not diffing on int property\n\nprint(\"Nothing should have printed 👀\")\n\n// Modify state only change int property and set to state 3\nmodifiedState = State(stringProperty: \"Hello world\", intProperty: 10)\nexampleClass.state3 = modifiedState\n\n// Should print a change now for state 3\n\n/***\n\nPrint Output:\n \n DiffStateReplay1: receive subscription: (CurrentValueSubject)\n DiffStateReplay1: request unlimited\n DiffStateReplay1: receive value: (State(stringProperty: \"\", intProperty: 0))\n DiffStateReplay2: receive subscription: (CurrentValueSubject)\n DiffStateReplay2: request unlimited\n DiffStateReplay2: receive value: (State(stringProperty: \"\", intProperty: 0))\n DiffStateReplay3: receive subscription: (CurrentValueSubject)\n DiffStateReplay3: request unlimited\n DiffStateReplay3: receive value: (State(stringProperty: \"\", intProperty: 0))\n\n -------------------------------------------------\n All Initial Sink Values should be established ✅\n -------------------------------------------------\n\n\n -------------------------------------------------\n Beginning State 1 Modifications\n -------------------------------------------------\n\n Nothing should have printed 👀\n DiffStateReplay1: receive value: (State(stringProperty: \"Hello world\", intProperty: 0))\n DiffStateReplay1: receive value: (State(stringProperty: \"Hello world\", intProperty: 10))\n\n -------------------------------------------------\n Beginning State 2 Modifications\n -------------------------------------------------\n\n Nothing should have printed 👀\n DiffStateReplay2: receive value: (State(stringProperty: \"Hello world\", intProperty: 10))\n\n -------------------------------------------------\n Beginning State 3 Modifications\n -------------------------------------------------\n\n Nothing should have printed 👀\n DiffStateReplay3: receive value: (State(stringProperty: \"Hello world\", intProperty: 10))\n \n */\n``` \n\nIf you like the library please star it 🙂\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZkHaider%2FDiffValue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FZkHaider%2FDiffValue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZkHaider%2FDiffValue/lists"}