Lightweight observations and bindings in Swift

Overview

Language: Swift Platform: macOS | iOS | watchOS | tvOS Carthage License

What is Hanson?

Hanson is a simple, lightweight library to observe and bind values in Swift. It's been developed to support the MVVM architecture in our Blendle iOS app. Hanson provides several advantages to using KVO in Swift, such as a Swiftier syntax, no boilerplate code, and the ability to use it in pure Swift types.

Example Usage

The most basic use case is to simply observe an Observable for changes:

let observable = Observable("Hello World")
observe(observable) { event in
    // Invoked whenever observable.value is set.
    print("Value changed from \(event.oldValue) to \(event.newValue)")
}

Hanson also provides a wrapper around KVO, so you can do the following to observe a UITextField's text property for changes:

let textField = UITextField()
let textFieldObservable = textField.dynamicObservable(keyPath: #keyPath(UITextField.text), type: String.self)
observe(textFieldObservable) { event in
    print("Text field value changed from \(event.oldValue) to \(event.newValue)")
}

Furthermore, you can also use Hanson to bind an observable to another observable. Let's say we have a view model that's responsible for loading data, and we want the view to show an activity indicator while the view model is loading data:

class ViewModel {

    let isLoadingData = Observable(false)

}

class View {

    let showsActivityIndicator = Observable(false)

}

let viewModel = ViewModel()
let view = View()
bind(viewModel.isLoadingData, to: view.showsActivityIndicator)

Now, whenever the view model's isLoadingData property is set to a different value, it will automatically be set to the view's showsActivityIndicator property.

Binding is also supported from and to KVO-backed observables. To bind a text field's content to a label:

let textField = UITextField()
let textFieldObservable = textField.dynamicObservable(keyPath: #keyPath(UITextField.text), type: String.self)

let label = UILabel()
let labelObservable = label.dynamicObservable(keyPath: #keyPath(UILabel.text), type: String.self)

bind(textFieldObservable, to: labelObservable)

If you want to handle the binding yourself, you can also provide a closure that will be invoked when a new value should be set. In the following example, we'll bind an isLoadingData observable to a UIActivityIndicatorView:

let isLoadingData = Observable(false)
let activityIndicatorView = UIActivityIndicatorView()
bind(isLoadingData, to: activityIndicatorView) { activityIndicatorView, isLoadingData in
    if isLoadingData {
        activityIndicatorView.startAnimating()
    } else {
        activityIndicatorView.stopAnimating()
    }
}

Hanson also supports observering notifications sent through a NotificationCenter. For example, to observe when an application is entering the background:

let observable = NotificationCenter.default.observable(for: Notification.Name.UIApplicationDidEnterBackground)
observe(observable) { notification in
    print("Application did enter background")
}

Schedulers

Schedulers can be used to schedule the events of your observation. By default, Hanson uses the CurrentThreadScheduler, which immediatly sends events on whatever thread it currently is. Hanson also offers the MainThreadScheduler, which ensures events are sent on the main thread. This is useful when observing a value that can change from a background thread and you want to do UI changes based on that value. For example:

let observable = Observable("Hello World")
observe(observable, with: MainThreadScheduler()) { event in
    // It's safe to do UI work here without calling DispatchQueue.main.async here
}

performOnBackground {
    observable.value = "Hello from a background"
}

Schedulers are also supported when binding observables:

let isLoadingData = Observable(true)
let activityIndicatorView = UIActivityIndicatorView()
bind(isLoadingData, with: MainThreadScheduler(), to: activityIndicatorView) { activityIndicatorView, isLoadingData in
    // It's safe to do UI work here without calling DispatchQueue.main.async here
}

performOnBackground {
    isLoadingData.value = false
}

You can create your own scheduler by conforming to the EventScheduler protocol.

Requirements

  • iOS 8.0+ / macOS 10.9+ / tvOS 9.0+
  • Xcode 8

Installation

Hanson is available through either CocoaPods or Carthage.

Cocoapods

  1. Add pod 'Hanson' to your Podfile.
  2. Run pod install.

Carthage

  1. Add github 'blendle/Hanson' to your Cartfile.
  2. Run carthage update.
  3. Link the framework with your target as described in Carthage Readme.

Swift Package Manager

  1. In Xcode, select your project and scroll to Frameworks, Libraries, and Embedded Content.
  2. Click the +.
  3. At the bottom of the frameworks and libraries window that opens, select Add other... and then Add package dependency....
  4. Paste https://github.com/blendle/Hanson.git in the search textfield and follow through with the assistant.

Building

The project obviously builds fine through Xcode, just load up Hanson.xcodeproj and run it.

For convenience, we've included a few scripts and a Makefile that allow you to build Hanson from the command line and through continuous integration. They are inspired by GitHub's Scripts to Rule Them All boilerplate:

|-- script/
  |-- etc/
    |-- config.sh   # Contains basic configuration parameters
  |-- bootstrap     # Prepares the project
  |-- setup         # Sets up the local building process
  |-- test          # Runs tests locally
  |-- cisetup       # Sets up the CI building process
  |-- citest        # Runs tests in a CI environment

To get started:

$ make

To skip setup and immediately start testing:

$ make test

Make sure all tests pass before opening a Pull Request.

Release Notes

See CHANGELOG.md for a list of changes.

License

Hanson is released under the ISC license. See LICENSE for details.

Comments
  • Bind error

    Bind error

    Hi. Thank you for your work. I've been looking for a library just like yours. It's great for implementing MVVM without having to add RxSwift and so.

    I'm getting an error when I try to use the bind function. For instance, if I write:

    bind(wsSuccess, to: model.wsFBSuccess) //both properties are Observable(false)
    

    I get a Xcode error saying "Missing argument for parameter #3 in call". I don't know if you have changed the function signature from the one appearing in the readme but autocomplete is only offering me: bind(Int32, UnsafePointer!, socklen_t) which seems unrelated to your library.

    What am I doing wrong?

    opened by rmvz3 7
  • observe one property of complex object?

    observe one property of complex object?

    I am not quite sure how to approach this and think my understanding of Hanson is not yet up-to-speed. I have a complex object from another repo. It is very deep and async-oriented. I want to track at least one property from it, and prefer not to tap into their repo to make changes. Without those changes, is something like this possible?

    I want to track the duration property of the youtube API return values from player within youtubeKit, if that helps. And i understand if this is too complex to ask! thx...

    opened by wdcurry 5
  • Will you consider  integrating reactive property?

    Will you consider integrating reactive property?

    For example:

    observe(observable) { (e) in
        cb();
    }
    
    cb();
    

    But with reactive property I can code like this:

    observable.subscribe { (e) in
        cb();
    }
    
    opened by singno 3
  • Use of unresolved identifier 'observe' for non-ui class

    Use of unresolved identifier 'observe' for non-ui class

    import UIKit
    import Hanson
    
    final class RootCoordinator {
        static func presentTodoLists(list:[TodoModel]) -> UINavigationController {
            // Adding observer listening to model change
            observe(todosObservable) { event in
    
            }
        }
    }
    
    

    Hello @utemissov , Is there anyway that I can observe a value inside a kind of non-ui class ?

    opened by gloryluu 2
  • Is there a way to initialize observable properties to nil?

    Is there a way to initialize observable properties to nil?

    Let's say I'd like to observe a property of type UIImage. Is there a way of make it nil by default?

    Something like:

    let observableImage : UIImage? = Observable(nil)
    
    opened by rmvz3 2
  • Controller cannot release after observing a notification.

    Controller cannot release after observing a notification.

    Hello, I got an issue that the view controller cannot release when observing a notification like this.

    let observable = NotificationCenter.default.observable(for: Notification.Name.UIApplicationDidEnterBackground)
    observe(observable) { notification in
        print("Application did enter background")
    }
    

    How to fix it ? Thanks.

    opened by gloryluu 1
  • How to listen to changing the text of UITextFiled in a xib?

    How to listen to changing the text of UITextFiled in a xib?

    Hello, Thank you very much for your library !! I have a problem: when I input text to a UITextFiled (from xib), how do I listen for changes in text ?

    opened by okFancy 1
  • Replacing Unowned with Weak

    Replacing Unowned with Weak

    The CustomBindable object used unowned reference to target which may lead to crashes when used with multiple threads. So its reference type changed to weak. The same thing happening in ObservationManager's bind function. It captures reference of observable as unowned, which may lead to crash.

    opened by utemissov 0
  • Add EventScheduler support

    Add EventScheduler support

    This PR adds support for event schedulers.

    Schedulers can be used to schedule the events of your observation. By default, Hanson uses the CurrentThreadScheduler, which immediatly sends events on whatever thread it currently is. Hanson also offers the MainThreadScheduler, which ensures events are sent on the main thread. This is useful when observing a value that can change from a background thread and you want to do UI changes based on that value.

    This introduces a breaking change to the EventPublisher protocol. The eventHandlers type has been changed to add support for event schedulers.

    opened by bramschulting 0
  • Make ObservationManager init function public

    Make ObservationManager init function public

    This makes the init function of the ObservationManager public so that conformance to the Observer protocol is possible outside of the framework.

    Related: https://github.com/blendle/Hanson/issues/12

    opened by smeis 0
Releases(2.0.0)
  • 2.0.0(Nov 6, 2020)

    2.0.0 (2020-11-06)

    Breaking change:

    • Added support for EventScheduler. This changes the EventPublisher protocol. Apps that use their own implementation of this protocol need to update their implementation.
    Source code(tar.gz)
    Source code(zip)
Owner
Blendle
Discover the world's best journalism. No paywalls, no ads, no clickbait.
Blendle
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift. ⚠️ Looking for the Objective-C API? ?? Migrating from RAC 4.x? ?? Release Road

null 20k Jan 8, 2023
RxSwift bindings for Permissions API in iOS.

RxPermission RxSwift bindings for Permission API that helps you with Permissions in iOS. Installation RxPermission is available through CocoaPods. I c

Luke 230 Dec 27, 2022
Animated RxCocoa bindings

RxAnimated - animated bindings RxAnimated provides animation interface to RxCocoa's bindings. It comes with few predefined animation bindings, and pro

RxSwift Community 687 Oct 26, 2022
Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest Observable<T> implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Jens Ravens 1.1k Jan 3, 2023
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

About Architecture concerns RxReduce Installation The key principles How to use RxReduce Tools and dependencies Travis CI Frameworks Platform Licence

RxSwift Community 125 Jan 29, 2022
📬 A lightweight implementation of an observable sequence that you can subscribe to.

Features Lightweight Observable is a simple implementation of an observable sequence that you can subscribe to. The framework is designed to be minima

Felix M. 133 Aug 17, 2022
A lightweight, event-driven architectural framework

Trellis Trellis features a declarative DSL that simplifies service bootstrapping: let cluster = try await Bootstrap { Group { Store(model:

Valentin Radu 25 Aug 16, 2022
A lightweight Elm-like Store for SwiftUI

ObservableStore A simple Elm-like Store for SwiftUI, based on ObservableObject. ObservableStore helps you craft more reliable apps by centralizing all

Subconscious 28 Nov 8, 2022
Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features.

swift-unidirectional-flow Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features. struct SearchState: Equatabl

Majid Jabrayilov 104 Dec 26, 2022
Dynamic and type-safe framework for building linear and non-linear flows.

FlowKit FlowKit is a dynamic flow framework capable of building a flow, based on conditions and ordered according to a logic of next steps. By using F

N26 55 Dec 20, 2022
RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Thane Gill 8 Jun 28, 2020
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Ryo Aoyama 324 Dec 17, 2022
KVO for Swift - Value Observing and Events

Value Observing and Events for Swift Swift lacks the powerful Key Value Observing (KVO) from Objective-C. But thanks to closures, generics and propert

Leszek Ślażyński 1.2k Dec 9, 2022
A sample modulated project to show my knowledge about Swift and Software Development process

A sample modulated project to show my knowledge about Swift and Software Development process

Kamyar Sehati 4 Apr 8, 2022
A Swift event bus for UIWebView/WKWebView and JS.

An event bus for sending messages between UIWebView/WKWebView and embedded JS. Made with pure Swift. Features Easy, fast and reliable event bus system

Coshx 149 Oct 9, 2022
Redux for Swift - a predictable state container for Swift apps

Merge / deprecation announcement: ReduxKit and Swift-Flow have joined forces! The result is ReSwift. The nitty gritty: We decided to deprecate ReduxKi

null 613 Jan 3, 2023
🤖 RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Yasuhiro Inami 719 Nov 19, 2022
A configurable api client based on Alamofire4 and RxSwift4 for iOS

SimpleApiClient A configurable api client based on Alamofire4 and RxSwift4 for iOS Requirements iOS 8.0+ Swift 4 Table of Contents Basic Usage Unwrap

Jay 67 Dec 7, 2020
STDevRxExt contains some extension functions for RxSwift and RxCocoa which makes our live easy.

STDevRxExt Example To run the Example.playground, clone the repo, and run pod install from the Example directory first. Requirements iOS 9.0+ tvOS 9.0

STDev 6 Mar 26, 2021