Git Product home page Git Product logo

diffvalue's Introduction

DiffValue by ZkHaider

Build Status Platforms Swift Version

DiffValue is a property observation tool that utilizes automatic diffing on properties through Combine and Property Wrappers.

Installation

DiffValue is available via Carthage, just add to your Cartfile like so:

Cartfile

# Property obsersation
github "ZkHaider/DiffValue" "master"

Terminal

$ carthage update DiffValue

Usage

It's easy to observe only properties you are interested in, you can do so like this:

struct UserState {
    let userName: String 
    let email: String
    let password: String
}

extension UserState: EquatableWithIdentity {
    /// Default value
    static var identity: UserState {
        UserState(
            userName: "",
            email: String,
            password: String
        )
    }
}

final class ViewController: UIViewController {

    // MARK: - State
    
    @Diff(\.userName, \.email)
    var userState; UserState 
    
    // MARK: - Lifecycle 
    
    override func viewDidLoad() {
        super.viewDidLoad() 
        
        // Start listening to state changes 
        // This is only called when your specified key paths are updated
        let subscription = $userState.sink { state in 
            print("Listening to state changes: \(state)")
        }
    }

}

That'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:

struct UserState {
    let userName: String 
    let email: String
    let password: String
}

final class ViewController: UIViewController {

    // MARK: - State
    
    @Diff(
        value: UserState(userName: "", email: "", password: ""),
        \.userName, 
        \.email
    )
    var userState; UserState 
    
    // MARK: - Lifecycle 
    
    override func viewDidLoad() {
        super.viewDidLoad() 
        
        // Start listening to state changes 
        // This is only called when your specified key paths are updated
        let subscription = $userState.sink { state in 
            print("Listening to state changes: \(state)")
        }
    }

}

If no KeyPath<Root, Value> are passed into the @Diff wrapper it will just do an equality check like normal based on Equatable:

struct UserState {
    let userName: String 
    let email: String
    let password: String
}

final class ViewController: UIViewController {

    // MARK: - State
    
    @Diff()
    var userState; UserState 
    
    // MARK: - Lifecycle 
    
    override func viewDidLoad() {
        super.viewDidLoad() 
        
        // Start listening to state changes 
        // This is only called when your specified key paths are updated
        // Invoked via equatable if we detect any changes because 
        // we did not pass in any keypaths
        let subscription = $userState.sink { state in 
            print("Listening to state changes: \(state)")
        }
    }

}

DiffValue supports passing up to 10 KeyPath<Root, Value> parameters in the initializer, if you require more you will have to pass an array of DiffableKeyPath<Root> types like:

struct MyVeryLargeState {
    let property1: String 
    let property2: String
    let property3: String
    let property4: String
    let property5: String
    let property6: String
    let property7: String
    let property8: String
    let property9: String
    let property10: String
    let property11: String
    let property12: String
}

extension MyVeryLargeState: EquatableWithIdentity {
    /// Default value
    static var identity: MyVeryLargeState {
        MyVeryLargeState(
            property1: "", 
            property2: "",
            property3: "",
            property4: "",
            property5: "",
            property6: "",
            property7: "",
            property8: "",
            property9: "",
            property10: "",
            property11: "",
            property12: ""
        )
    }
}

final class ViewController: UIViewController {

    // MARK: - State
    
    @Diff(keyPaths: 
        (\MyVeryLargeState.property1).diffable, 
        (\MyVeryLargeState.property2).diffable,
        (\MyVeryLargeState.property3).diffable,
        (\MyVeryLargeState.property4).diffable,
        (\MyVeryLargeState.property5).diffable,
        (\MyVeryLargeState.property6).diffable,
        (\MyVeryLargeState.property7).diffable,
        (\MyVeryLargeState.property8).diffable,
        (\MyVeryLargeState.property9).diffable,
        (\MyVeryLargeState.property10).diffable,
        (\MyVeryLargeState.property11).diffable,
        (\MyVeryLargeState.property12).diffable,
    )
    var largeState; MyVeryLargeState 
    
    // MARK: - Lifecycle 
    
    override func viewDidLoad() {
        super.viewDidLoad() 
    }

}

Chances 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<Root, Value>s as you wish to diff on!

A @Diff property wrapper exposes a CurrentValueRelay<Root, Never>. This is a Publisher with a private CurrentValueSubject<Root, Never> field. This is hidden so you cannot pass a completion event to the Relay. Use the Relay to subscribe your State to other Subscribers!

Property Observation

You can also observe single properties directly without having to observe entire value changes:

struct State {
    let stringProperty: String
    let intProperty: Int
}

final class ExampleClass {

    @Diff(\.stringProperty)
    var state: State

}

final class TestClass {

    let exampleClass: ExampleClass = ExampleClass()

    init() {
        exampleClass.$state.add( 
            \.stringProperty,
            target: self,
            hook: .method(TestClass.observeString)
        )
    }

    private func observeString(_ stringValue: String) {
        print("๐Ÿ“ Property Changed: \(stringValue)")
    }

}

Here is fully fledged example:

struct State {
    let stringProperty: String
    let intProperty: Int
}

final class ExampleClass {
    
    @Diff(\.stringProperty, \.intProperty)
    var state1: State
    
    @Diff(\.intProperty)
    var state2: State
    
    @Diff(\.stringProperty)
    var state3: State
}

var modifiedState = State(stringProperty: "", intProperty: 0)

/// Setup subscriptions

// State 1

let relay1 = exampleClass.$state1
let replay1 = relay1
    .print("DiffStateReplay1")
    .singleReplay()

replay1.sink { (state) in
    
}.store(in: &subscriptions)

replay1.sink { (state) in
    
}.store(in: &subscriptions)

// State 2

let relay2 = exampleClass.$state2
let replay2 = relay2
    .print("DiffStateReplay2")
    .singleReplay()

replay2.sink { (state) in
    
}.store(in: &subscriptions)

replay2.sink { (state) in
    
}.store(in: &subscriptions)

// State 3

let relay3 = exampleClass.$state3
let replay3 = relay3
    .print("DiffStateReplay3")
    .singleReplay()

replay3.sink { (state) in
    
}.store(in: &subscriptions)

replay3.sink { (state) in
    
}.store(in: &subscriptions)

print("""

-------------------------------------------------
All Initial Sink Values should be established โœ…
-------------------------------------------------

""")

print("""

-------------------------------------------------
Beginning State 1 Modifications
-------------------------------------------------

""")

/**
   State 1 Modifications
*/

// No modification nothing should print
exampleClass.state1 = modifiedState

print("Nothing should have printed ๐Ÿ‘€")

// Modify state only change string property and set to state 1
modifiedState = State(stringProperty: "Hello world", intProperty: 0)
exampleClass.state1 = modifiedState

// Should print a change now for state 1

// Modify state only change int property and set to state 1
modifiedState = State(stringProperty: "Hello world", intProperty: 10)
exampleClass.state1 = modifiedState

// Should print a change now for state 1

/**
    State 2 Modifications
 */

print("""

-------------------------------------------------
Beginning State 2 Modifications
-------------------------------------------------

""")

// Modify state only change string property and set to state 2
modifiedState = State(stringProperty: "Hello world", intProperty: 0)
exampleClass.state2 = modifiedState

// Nothing should print because we are not diffing on string property

print("Nothing should have printed ๐Ÿ‘€")

// Modify state and this time change int property and set to state 2
modifiedState = State(stringProperty: "Hello world", intProperty: 10)
exampleClass.state2 = modifiedState

// Should print a change now for state 2

/**
  State 3 Modifications
*/

print("""

-------------------------------------------------
Beginning State 3 Modifications
-------------------------------------------------

""")

// Modify state only change int property and set to state 3
modifiedState = State(stringProperty: "", intProperty: 10)
exampleClass.state3 = modifiedState

// Nothing should print because we are not diffing on int property

print("Nothing should have printed ๐Ÿ‘€")

// Modify state only change int property and set to state 3
modifiedState = State(stringProperty: "Hello world", intProperty: 10)
exampleClass.state3 = modifiedState

// Should print a change now for state 3

/***

Print Output:
 
 DiffStateReplay1: receive subscription: (CurrentValueSubject)
 DiffStateReplay1: request unlimited
 DiffStateReplay1: receive value: (State(stringProperty: "", intProperty: 0))
 DiffStateReplay2: receive subscription: (CurrentValueSubject)
 DiffStateReplay2: request unlimited
 DiffStateReplay2: receive value: (State(stringProperty: "", intProperty: 0))
 DiffStateReplay3: receive subscription: (CurrentValueSubject)
 DiffStateReplay3: request unlimited
 DiffStateReplay3: receive value: (State(stringProperty: "", intProperty: 0))

 -------------------------------------------------
 All Initial Sink Values should be established โœ…
 -------------------------------------------------


 -------------------------------------------------
 Beginning State 1 Modifications
 -------------------------------------------------

 Nothing should have printed ๐Ÿ‘€
 DiffStateReplay1: receive value: (State(stringProperty: "Hello world", intProperty: 0))
 DiffStateReplay1: receive value: (State(stringProperty: "Hello world", intProperty: 10))

 -------------------------------------------------
 Beginning State 2 Modifications
 -------------------------------------------------

 Nothing should have printed ๐Ÿ‘€
 DiffStateReplay2: receive value: (State(stringProperty: "Hello world", intProperty: 10))

 -------------------------------------------------
 Beginning State 3 Modifications
 -------------------------------------------------

 Nothing should have printed ๐Ÿ‘€
 DiffStateReplay3: receive value: (State(stringProperty: "Hello world", intProperty: 10))
 
 */

If you like the library please star it ๐Ÿ™‚

diffvalue's People

Contributors

zkhaider avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.