Git Product home page Git Product logo

observable-swift's Introduction

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

observable-swift's People

Contributors

armadsen avatar bradleyayers avatar dcharbonnier avatar harenbrs avatar slazyk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

observable-swift's Issues

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.

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 =)

Sending initial value when handler is added

It would be useful to somehow getting the first value of an Observable immediately after adding a handler. Say I have a View that updates a textLabel based on the corresponding ViewModel's name property.

class MyViewModel {
  var name = Observable("test")
}

class MyView: UIView {
  @IBOutlet var nameLabel: UILabel!

  func configureWithViewModel(viewModel: MyViewModel) {
    nameLabel.text = viewModel.name.value

    viewModel.name.afterChange += { [unowned self] in self.nameLabel.text = $1 }
  }
}

It's a bit cumbersome having to repeat myself about how the value of a viewModel affects changes in the view. Is there another way to do this or is it something that could be implemented?

How do you install this?

In your documentation to install the framework it says:

"and then add Observable.framework to frameworks for your target."

What is that supposed to mean? Do I just drag the .framework file into the frameworks folder? Or do I need to link it through the configuration UI? Can you be more clear about how someone is to go about doing that? I can't figure it out.

XCode Beta 6 Build Issues

The latest update breaks the framework:

  • '__conversion' functions are no longer allowed
  • cannot reference a local function from another local function

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)

New Observable value syntax (especially with Optionals)

As originally described in the answer to #2, now that there is no possibility to define implicit conversions from Observable<T> to T, preferred way of getting the value out of the optional is via a postfix operator ^:

let y = doSomething(x^)

The main reason for it to be postfix is to allow easy chaining with similar syntax to optional chaining:

// .value is just too long in some cases
let y1 = obj.property.value.whatever.value.sthElse.value
// prefix requires parentheses
let y2 = ^(^(^obj.property).whatever).sthElse
// postfix allows nice chaining
let y3 = obj.property^.whatever^.sthElse^

However, for ^ to really be useful there is probably also need for ^! and ^?, because with optionals you now have to use parentheses, which kind of defeats the whole purpose:

let z = (me.friend^)!.lastName^

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.

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?

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: #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!

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.

Swift compiler crashes on ObservableReference.swift in new Xcode 6.1 GM

So I just updated to Xcode 6.1 GM from Xcode 6.1 beta 2 and my project fails to build.

CompileSwift normal armv7 /Project/Observable-Swift/ObservableReference.swift

0  swift                    0x0000000110481f68 llvm::sys::PrintStackTrace(__sFILE*) + 40
1  swift                    0x0000000110482454 SignalHandler(int) + 452
2  libsystem_platform.dylib 0x00007fff8580bf1a _sigtramp + 26
3  swift                    0x000000010f9434c8 getThunkResult(swift::Lowering::SILGenFunction&, swift::SILLocation, TranslationKind, swift::CanTypeWrapper<swift::SILFunctionType>, swift::Lowering::AbstractionPattern, swift::CanType, swift::SILType, swift::SILValue, swift::SILValue, swift::SILValue) + 2344
4  swift                    0x000000010f8dbeeb swift::Lowering::TypeConverter::makeConstantType(swift::SILDeclRef, bool) + 699
5  swift                    0x000000010f8a20dd swift::Lowering::TypeConverter::getConstantInfo(swift::SILDeclRef) + 189
6  swift                    0x000000010f907b0f swift::Lowering::SILGenModule::emitProtocolWitness(swift::ProtocolConformance*, swift::SILLinkage, swift::SILDeclRef, swift::SILDeclRef, swift::Lowering::IsFreeFunctionWitness_t, llvm::ArrayRef<swift::Substitution>) + 79
7  swift                    0x000000010f908e25 (anonymous namespace)::SILGenConformance::emitFuncEntry(swift::FuncDecl*, swift::ValueDecl*, llvm::ArrayRef<swift::Substitution>) + 213
8  swift                    0x000000010f904f80 swift::Lowering::SILGenModule::getWitnessTable(swift::ProtocolConformance*) + 496
9  swift                    0x000000010f909d4b SILGenType::emitType() + 315
10 swift                    0x000000010f904a9e swift::Lowering::SILGenModule::visitNominalTypeDecl(swift::NominalTypeDecl*) + 30
11 swift                    0x000000010f8f1c0b swift::Lowering::SILGenModule::emitSourceFile(swift::SourceFile*, unsigned int) + 395
12 swift                    0x000000010f8f1eca swift::SILModule::constructSIL(swift::Module*, swift::SourceFile*, swift::Optional<unsigned int>) + 314
13 swift                    0x000000010f8f2018 swift::performSILGeneration(swift::SourceFile&, swift::Optional<unsigned int>) + 72
14 swift                    0x000000010f7c8518 frontend_main(llvm::ArrayRef<char const*>, char const*, void*) + 3432
15 swift                    0x000000010f7c606d main + 1677
16 libdyld.dylib            0x00007fff8f2605c9 start + 1

Also SourceKit crashes on parsing ObservableReference.swift and Observable.swift
I'm trying to find workaround right now, but afraid I won't be able to do that myself.

Observing Array<T> 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)

Immutable value of type 'Event<String>' only has mutating members named 'add'

Seems the Event doesn't work well with Generic function, or did I do something wrong?

protocol B {
  var event: Event<Bool> { get set }
}

func test<A: B>(a: A) {

  //Got Cannot invoke '+=' with an argument list of type '(Event<Bool>, (($T5) -> ($T5) -> $T4) -> (($T5) -> $T4) -> $T4)'

  a.event += { println($0)  }

 // Got an error: Immutable value of type 'Event<String>' only has mutating members named 'add'
  a.event.add {
    println($0)
  }
}

didSet called each time value updates

I have a class with an optional Observable var. I would like to add a handler to this when I set the observable.

    class Field: UIView {
        var observe:Observable<String>? {
            didSet {
                observe!.afterChange += { self.update($1) }
            }
        }
    }

However, I find that the didSet callback gets called not only when the observable gets set initially, but also each time its value updates after that, which I would not expect to happen. Is this expected? How can I achieve this otherwise?

Edit: I just found out Observables are passed by value, which renders my whole approach moot. You are making it hard to love you, Swift.

Edit2: ObservableReference to the rescue! Working beautifully now.

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.