KVO for Swift - Value Observing and Events

Overview

Value Observing and Events for Swift

Swift lacks the powerful Key Value Observing (KVO) from Objective-C. But thanks to closures, generics and property observers, in some cases it allows for far more elegant observing. You have to be explicit about what can be observed, though.

Overview

Observable-Swift is a Swift library for value observing (via explicit usage of Observable<T>) and subscribable events (also explicit, using Event<T>). While it is not exactly "KVO for Swift" (it is explicit, there are no "Keys", ...) it is a catchy name so you can call it that if you want. The library is still under development, just as Swift is. Any contributions, both in terms of suggestions/ideas or actual code are welcome.

Observable-Swift is brought to you by Leszek Ślażyński (slazyk), you can follow me on twitter and github. Also check out SINQ my other Swift library that makes working with collections a breeze.

Observables

Using Observable<T> and related classes you can implement wide range of patterns using value observing. Some of the features:

  • observable variables and properties
  • chaining of observables (a.k.a. key path observing)
  • short readable syntax using +=, -=, <-/^=, ^
  • alternative syntax for those who dislike custom operators
  • handlers for before or after the change
  • handlers for { oldValue:, newValue: } (oldValue, newValue) or (newValue)
  • adding multiple handlers per observable
  • removing / invalidating handlers
  • handlers tied to observer lifetime
  • observable mutations of value types (structs, tuples, ...)
  • conversions from observables to underlying type (not available since Swift Beta 6)
  • observables combining other observables
  • observables as value types or reference types
  • ...

Events

Sometimes, you don’t want to observe for value change, but other significant events. Under the hood Observable<T> uses beforeChange and afterChange of EventReference<ValueChange<T>>. You can, however, use Event<T> or EventReference<T> directly and implement other events too.

Installation

You can use either CocoaPods or Carthage to install Observable-Swift.

Otherwise, the easiest option to use Observable-Swift in your project is to clone this repo and add Observable-Swift.xcodeproj to your project/workspace and then add Observable.framework to frameworks for your target.

After that you just import Observable.

Examples

Observable<T> is a simple struct allowing you to have observable variables.

// create a Observable<Int> variable
var x = Observable(0)

// add a handler
x.afterChange += { println("Changed x from \($0) to \($1)") }
// without operators: x.afterChange.add { ... }

// change the value, prints "Changed x from 0 to 42"
x <- 42
// alternativelyL x ^= 42, without operators: x.value = 42

You can, of course, have observable properties in a class or a struct:

struct Person {
    let first: String
    var last: Observable<String>
    
    init(first: String, last: String) {
        self.first = first
        self.last = Observable(last)
    }
}
    
var ramsay = Person(first: "Ramsay", last: "Snow")
ramsay.last.afterChange += { println("Ramsay \($0) is now Ramsay \($1)") }        
ramsay.last <- "Bolton"

Up to Swift Beta 5 you could implicitly convert Observable<T> to T, and use it in places where T is expected. Unfortunately Beta 6 forbids defining implicit conversions:

let x = Observable(20)
// You can use the value property ...
let y1 = x.value + 22
// ... or a postfix operator ...
let  y2 = x^ + 22
/// ... which has the advantage of easy chaining
let y3 = obj.property^.whatever^.sthElse^
/// ... you can also use ^= instead of <- for consistency with the postfix ^

For value types (such as structs or tuples) you can also observe their mutations:
Since Observable is a struct, ramsay in example above gets mutated too. This means, you could observe ramsay as well.

struct Person {
    let first: String
    var last: String
    var full: String { get { return "\(first) \(last)" } }
}

var ramsay = Observable(Person(first: "Ramsay", last: "Snow"))
// x += { ... } is the same as x.afterChange += { ... }
ramsay += { println("\($0.full) is now \($1.full)") }
ramsay.value.last = "Bolton"

You can remove observers by keeping the subscription object:

var x = Observable(0)    
let subscr = x.afterChange += { (_,_) in println("changed") }
// ...
x.afterChange -= subscr
// without operators: x.afterChange.remove(subscr)

Invalidating it:

var x = Observable(0)    
let subscr = x.afterChange += { (_,_) in println("changed") }
// ...
subscr.invalidate() // will be removed next time event fires

Or tie the subscription to object lifetime:

var x = Observable(0)        
for _ in 0..1 {
    let o = NSObject() // in real-world this would probably be self
    x.afterChange.add(owner: o) { (oV, nV) in println("\(oV) -> \(nV)") }
    x <- 42 // handler called
} // o deallocated, handler invalidated
x <- -1 // handler not called

You can also chain observables (observe "key paths"):

class Person {
    let firstName: String
    var lastName: Observable<String>
    var friend: Observable<Person?> = Observable(nil)
	// init(...) { ... }
}

let me = Person()
var myFriendsName : String? = nil

// we want to observe my current friend last name
// and get notified with name when the friend or the name changes
chain(me.friend).to{$0?.lastName}.afterChange += { (_, newName) in
	myFriendsName = newName
}

// alternatively, we can do the same with '/' operator
(me.friend / {$0?.lastName}).afterChange += { (_, newName) in
	myFriendsName = newName
}

Event<T> is a simple struct allowing you to define subscribable events. Observable<T> uses EventReference<ValueChange<T>> for afterChange and beforeChange.

class SomeClass {
 	// defining an event someone might be interested in
 	var somethingChanged = Event<String>()
 
 	// ...
 
 	func doSomething() {
 		// ...
 		// fire the event and notify all observers
 		somethingChanged.notify("Hello!")
 		// ...
 	}
}

var obj = SomeClass()

// subscribe to an event
obj.somethingChanged += { println($0) }

obj.doSomething()

More examples can be found in tests in ObservableTests.swift

Advanced

If you require observables as reference types, you can use either ObservableProxy which is a reference type in between your code and the real Observable value type. You can also use ObservableReference which is a ObservableProxy to an Observable that it holds on a property.

Same is true for Event, there is EventReference as well. Actually, Observable uses EventReference instead of Event, otherwise some use cases would be difficult to implement. This means, that if you want to unshare events and subscriptions you need to call observable.unshare(removeSubscriptions:).

Comments
  • Support Xcode 8 and Swift 3 / Swift 2.3

    Support Xcode 8 and Swift 3 / Swift 2.3

    iOS 10 is out!

    When are the plans to support Swift 2.3/3.0 and Xcode 8? Due to the nature of the Swift runtime ALL libraries in a project must support it for the app to update to it - which means this library is blocking us from building against the iOS 10 SDK.

    Thanks =)

    opened by marchy 8
  • Update minimumosversion to 8.0 to fix usage with Carthage

    Update minimumosversion to 8.0 to fix usage with Carthage

    It's currently not possible to submit an app that uses Observable-Swift via Carthage, because when the the framework is built via Carthage minimumosversion is set to 7.0, however it must be 8.0 or higher. setting to 8.0 is sensible because iOS 8.0 is the first version to support frameworks.

    A similar issue was opened for Alamofire.

    opened by bradleyayers 4
  • Fix warnings when compiling with Xcode 9 / Swift 3.2 & 4

    Fix warnings when compiling with Xcode 9 / Swift 3.2 & 4

    Fixed a couple compilation warnings that occurred under Xcode 9 (default warning settings) & Swift >= 3.2

    I believe those were the issues referenced at https://github.com/slazyk/Observable-Swift/issues/36

    • Redefined public ValueType as to not use internal types
    • Redefined value as to use ValueType
    • Removed conformance of O1 O2 and O3 to AnyObservable as this was redundant with ObservableChainingProxy type requirements

    @slazyk Please take a look and submit your feedback

    opened by FlorianDenis 3
  • Compilation error using cocoapods in Xcode 9.0

    Compilation error using cocoapods in Xcode 9.0

    • "Type alias cannot be declared public because its underlying type uses an internal type" at PairObservable row 14.
    • "Property cannot be declared public because its type uses an internal type" at PairObservable row 22.
    opened by sewil 3
  • Issue setting before/afterChange handlers

    Issue setting before/afterChange handlers

    Hey, I'm trying to get the library up and running, however when I go to add a handler an observable like so:

    var ob = Observable(0)
    ob.beforeChange += { print("foo") }
    

    I get the following error: Left side of mutating operator isn't mutable: 'beforeChange' setter is inaccessible

    I am using version 0.7.0 through cocoapods, with Xcode 8.3.

    opened by marcelvanworkum 2
  • Code signing error when installing via Carthage

    Code signing error when installing via Carthage

    I am having a problem when installing via Carthage.

    The error is

    CodeSign error: code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 9.1'

    I think ideally the unit tests wouldn't even be built when installing via Carthage, but failing that perhaps the unit test target needs to have code signing enabled?

    opened by bradleyayers 2
  • Observing Array<T> crashes in Swift 1.2 (Xcode 6.3 Beta)

    Observing Array crashes in Swift 1.2 (Xcode 6.3 Beta)

    In a Playground, the following code will result in a runtime error EXC_BAD_ACCESS. In a compiled Swift app, it crashes both the app and Xcode (if debugging), or just the app (if running a release configuration). This makes it tricky to pinpoint the source of this issue.

    Since observing arrays used to work previous to this update to Swift, we are probably dealing with a bug in the (reportedly newly improved) compiler. I would file a bug report with Apple, but I have no idea which language feature is actually misbehaving. Perhaps it will be a little more obvious to you, slazyk?

    import Observable
    
    class Test {
        var vals = Observable<[Int]>([])
    
        func put(val: Int) {
            vals.value.append(val)
        }
    }
    
    
    let t = Test()
    
    t.vals.afterChange.add {
        println($0.newValue)
    }
    
    t.put(2)
    
    opened by harenbrs 2
  • Observable-iOS Build Errors - Simulator

    Observable-iOS Build Errors - Simulator

    Seeing the following errors when building Observable-iOS and targeting the simulator:

    Undefined symbols for architecture x86_64: "_swift_allocateGenericClassMetadata", referenced from: _create_generic_metadata in ObservableChainingProxy.o _create_generic_metadata in EventReference.o _create_generic_metadata in EventSubscription.o _create_generic_metadata in TupleObservable.o _create_generic_metadata in ObservableReference.o _create_generic_metadata in OwningEventReference.o _create_generic_metadata in ObservableProxy.o ... "_swift_allocateGenericValueMetadata", referenced from: _create_generic_metadata in Observable.o _create_generic_metadata1 in Observable.o _create_generic_metadata in Event.o _create_generic_metadata25 in ObservableChainingProxy.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)

    opened by timward60 2
  • __conversion won't be available in Swift 1.0

    __conversion won't be available in Swift 1.0

    Hi,

    You've probably already noticed that, but __conversion won't be available in the final release of Swift. Do you have some ideas on how to adjust Observable-Swift's API after this change happens?

    opened by fastred 2
  • Make EventSubscription handling public

    Make EventSubscription handling public

    Recently I found myself in a need to use a custom EventReference and wanted to call subscription.handler(value) manually from the inside. To my surprise, this was marked as internal.

    This pull request fixes this by introducing a public func handle(value: T), which makes it possible to customize the behavior of EventReferences.

    opened by akashivskyy 1
  • Added fix for version bug that disabled iTunes validation

    Added fix for version bug that disabled iTunes validation

    This cause an app to not be validated when sent to AppStore. Don't really know if this is the case but the target framework was 7.0 and after changing it to 8.0 as the error suggested it worked.

    opened by okipol88 1
  • Add an ability to copy the value from Observable without having to access it first

    Add an ability to copy the value from Observable without having to access it first

    Currently when copying values (let's say between models), we always need to access them.

    Example:

    model.name.value = diffModel.name.value
    model.name <- diffModel.name.value
    
    // or shorthand
    model.name <- diffModel.name^
    

    The improvement allow use to do this:

    model.name <- diffModel.name
    

    There is no need to access the value of the Observable and since we use <- operator this action seems to be also pretty clear (unlike =).

    opened by Mattijah 0
  • Name clash with Observable module

    Name clash with Observable module

    Hi there, I ran into unsurmountable issues getting the framework running where a name clash happens with any class within the module when importing as a framework (ie: Cocoapods). For example, we have an Event class (since our domain models everyday events) and Swift gets tripped up on being able to specify that an event is an Observable.Event as opposed to your own Event.

    The reason for this is that both the module is called Observable as well as a framework class exists called Observable. This is apparently an anti-practice in Swift to have the module use the same name as any of its existing classes. Event typealiasing gets tripped up by this (ie: ""typealias ObservableEvent = Observables.Event"")

    PROPOSAL: Rename the framework to "Observables" (or something similar). Unfortunately I think this is the only way out of this (without renaming the Observable class itself)

    I have made all the changes required on the fork here: https://github.com/slazyk/Observable-Swift/pull/21. Would appreciate you accepting this and renaming the project (Cocoapod will likely require a new pod, GitHub has a rename option at the top of the project Settings page). I know it's not the nicest thing to rename the project but you really want people to be able to integrate your project seamlessly using Cocoapods etc and not just copying the files in, and it'll solve any problems like this going forward. So hopefully someone else doesn't waste a full day chasing this down as I did. :)

    Cheers!

    opened by marchy 0
  • Fixed naming clash between Observable module and Observable class surfaced when any class name in the including project clashes with a class name in the framework

    Fixed naming clash between Observable module and Observable class surfaced when any class name in the including project clashes with a class name in the framework

    Renamed module to "Observables" to get around unsurmountable issues with making the module work when there is a name clash with any class within the module when importing it as a framework (ie: Cocoapods). For example, if you have an Event class in your own project (ie: your domain models everyday events) Swift gets tripped up on being able to specify that an event is an Observable.Event as opposed to your own Event.

    The reason for this is that both the framework module is called Observable as well contains a class called Observable. It's apparently an anti-practice in Swift to have the module use the same name as any of its existing classes. Event type aliasing gets tripped up by this (ie: "typealias ObservableEvent = Observables.Event" --> compiler can't distinguish between Observable the class and Observable the module, despite the "import Observable" declaration)

    Thus, the solution was to rename the framework to "Observables" (or something similar). Unfortunately this is the only reliable way out of this (without renaming the Observable class itself). Alternatives would have been to rename the Observable class (not worth it) or giving up framework integration and copying files directly (not using Cocoapods).

    opened by marchy 1
  • Enhancement: remove subscriptions by owner

    Enhancement: remove subscriptions by owner

    What do you think about adding a method like Event.remove(owner owner:AnyObject)? It would eliminate the need to store subscriptions in an instance var in the common case. I can't implement it in an extension, because the list of subscriptions is marked internal.

    opened by zsau 3
Owner
Leszek Ślażyński
Leszek Ślażyński
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
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
Lightweight observations and bindings in Swift

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 o

Blendle 526 Oct 18, 2022
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
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
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 + 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
RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

Written in Swift 5 RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with

Dalton Hinterscher 9 Aug 7, 2020
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
CMPSC475 Final Project, ArboretumID Application allows users to explore the Penn State Arboretum, identify plants and learn about the exhibits!

ArboretumID: CMPSC475 Final Project Taylan Unal (@taylanu) About ArboretumID ArboretumIdentifier (ArboretumID) is an app that enhances the Penn State

Taylan 1 Nov 27, 2021
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
Binding - Data binding framework (view model binding on MVVM) written using propertyWrapper and resultBuilder

Binding Data binding framework (view model binding on MVVM) written using @prope

Sugeng Wibowo 4 Mar 23, 2022
UIKitPreviews - A simple way to see your UIKit ViewController changes live and on-demand

SwiftUI preview provider for UIKit A simple way to see your UIKit ViewController

Emad Beyrami 8 Jan 8, 2023
Elm-parcel-capacitor - A sample setup to build an app with Elm, Capacitor, Parcel and Tailwind CSS

Elm, Capacitor, Parcel and Tailwindcss This project is a sample setup to build a

Anthonny Quérouil 10 May 9, 2022