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.
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.
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
- ...
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.
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
.
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
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:)
.