Observable is the easiest way to observe values in Swift.

Overview

Observable

Observable is the easiest way to observe values in Swift.

How to

Create an Observable and MutableObservable

Using MutableObservable you can create and observe event. Using Observable you can observe event, in order to avoid side-effects on our internal API.

class SomeViewModel {
    /// Public property, that can be read / observed by external classes (e.g. view controller), but not changed.
    var position: Observable = {
        return positionSubject
    }
    // Or use the helper method Observable.asObservable()
    // lazy var position = positionSubject.asObservable()

    /// Private property, that can be changed / observed inside this view model.
    private let positionSubject = MutableObservable(CGPoint.zero)
}

Create Observer with custom onDispose functionality

In some cases Observables require resources while they're active that must be cleaned up when they're disposed of. To handle such cases you can pass an optional block to the Observable initializer to be executed when the Observable is disposed of.

url.startAccessingSecurityScopedResource()
let observable = Observable([URL]()) {
    url.stopAccessingSecurityScopedResource()
}

Model Properties as @MutableObservable

Now mark your binded/mapped properties as observable and export public observable

{ return _text } ">
//Private Observer
@MutableObservable var text: String = "Test"

//add observer

_text.observe { (newValue, oldValue) in
    print(newValue)
}.add(to: &disposable)
        
//Public Observer

var textObserve: ImmutableObservable<String> {
    return _text
}

Add an observer

position.observe { p in
    // handle new position
}

Add an observer and specify the DispatchQueue

position.observe(DispatchQueue.main) { p in
// handle new position
}

Change the value

position.wrappedValue = p

Stop observing new values

position.observe {
    // This will stop all observers added to `disposal`
    self.disposal.dispose()
}.add(to: &disposal)

Memory management

For a single observer you can store the returned Disposable to a variable

disposable = position.observe { p in

For multiple observers you can add the disposable to a Disposal variable

position.observe { }.add(to: &disposal)

And always weakify self when referencing self inside your observer

position.observe { [weak self] position in

Installation

CocoaPods

Observable is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Observable'

Swift Package Manager

Observable is available through Swift Package Manager. Swift Package Manager (SwiftPM) is a tool for automating the distribution of Swift code. It is integrated into the swift compiler and from Xcode 11, SwiftPM got natively integrated with Xcode.

dependencies: [
    .package(url: "https://github.com/roberthein/Observable", from: "VERSION")
]

Migrations

1.x.y to 2.0.0

  • Observable is now MutableObservable
  • ImmutableObservable is now Observable
  • Observable.asImmutableObservable() is now Observable.asObservable()
  • Observable.value is now Observable.wrappedValue

Suggestions or feedback?

Feel free to create a pull request, open an issue or find me on Twitter.

Comments
  • Migrate to Observable and MutableObservable

    Migrate to Observable and MutableObservable

    As discussed in https://github.com/roberthein/Observable/issues/24, this PR tries to fix the issue. @roberthein What do you think about this? I would like to know your opinion on this. Thanks

    opened by 4brunu 8
  • Added base subject class

    Added base subject class

    I restore back mutex because it less expensive for usage than thread. Deadlock problem developer can solve with add observer dispatch queue.

    Subject sometimes can be usefull. E.g. when we call something from net, and waiting result. For this moment you don't have value.

    opened by AlexandrGraschenkov 8
  • Firing first event using thread

    Firing first event using thread

    This PR fixes the issue #32 where the first time we call the observer, right after subscribing, we where not using the given queue from the parameter.

    opened by Edudjr 7
  • First observer value not being fired using the queue

    First observer value not being fired using the queue

    I have a scenario where I'm using an observer in a singleton, so it's shared between some classes. The problem arises when this observer triggers the instantiation of another class which also uses the same observable (It looks like this is related to #19 ). In this scenario, the app freezes because since the first event is not using the queue, the second time I subscribe it reaches lock for the second time, before the first has a chance to unlock.

    I noticed that in line 45, the queue is not really used at all. https://github.com/roberthein/Observable/blob/b8e87082d65f3f322f35973384e23590031f078c/Observable/Classes/Observable.swift#L45

    Is there any reason for that? Or do you think we could use the queue on that line, like this:

       if let queue = queue {
           queue.async {
               observer(self.value, nil)
           }
       } else {
           observer(value, nil)
       }
    

    Here are some test scenarios comparing the old/new implementation:

        // Hangs with current implementation
        // Succeeds with new implementation
        func test_whenChangingValue_shouldSucceed() {
            let exp = expectation(description: "")
            let observable = Observable(0)
    
            observable.observe(DispatchQueue.main) { _, _ in
                observable.removeAllObservers()
                observable.value = 1
                exp.fulfill()
            }.add(to: &disposal)
    
            wait(for: [exp], timeout: 1.0)
            XCTAssert(true)
        }
    
        // Succeeds with current implementation
        // Succeeds with new implementation
        func test_whenUsingDispatchMain_shouldSucceed() {
            let exp = expectation(description: "")
            let singleton = Singleton.shared
            let callback: (() -> Void) = {
                exp.fulfill()
            }
            var aClass: AClass?
    
            singleton.observable.observe { _, _ in
                singleton.observable.removeAllObservers()
                DispatchQueue.main.async {
                    aClass = AClass(callback: callback)
                }
            }.add(to: &disposal)
    
            print(aClass.debugDescription)
            wait(for: [exp], timeout: 1.0)
            XCTAssert(true)
        }
    
        // Hangs with current implementation
        // Succeeds with new implementation
        func test_whenPassingDispatchMain_shouldSucceed() {
            let exp = expectation(description: "")
            let singleton = Singleton.shared
            let callback: (() -> Void) = {
                exp.fulfill()
            }
            var aClass: AClass?
    
            singleton.observable.observe(DispatchQueue.main) { _, _ in
                singleton.observable.removeAllObservers()
                aClass = AClass(callback: callback)
            }.add(to: &disposal)
    
            print(aClass.debugDescription)
            wait(for: [exp], timeout: 2.0)
            XCTAssert(true)
        }
    
        // Hangs with current implementation
        // Succeeds with new implementation
        func test_whenUsingDispatchGlobal_shouldSucceed() {
            let exp = expectation(description: "")
            let singleton = Singleton.shared
            let callback: (() -> Void) = {
                exp.fulfill()
            }
            var aClass: AClass?
    
            singleton.observable.observe(DispatchQueue.global()) { _, _ in
                singleton.observable.removeAllObservers()
                aClass = AClass(callback: callback)
            }.add(to: &disposal)
    
            print(aClass.debugDescription)
            wait(for: [exp], timeout: 1.0)
            XCTAssert(true)
        }
    
        // Hangs with current implementation
        // Hangs with new implementation
        func test_whenNotUsingDispatch_shouldHang() {
            let exp = expectation(description: "")
            let singleton = Singleton.shared
            let callback: (() -> Void) = {
                exp.fulfill()
            }
            var aClass: AClass?
    
            singleton.observable.observe { _, _ in
                singleton.observable.removeAllObservers()
                aClass = AClass(callback: callback)
            }.add(to: &disposal)
    
            print(aClass.debugDescription)
            wait(for: [exp], timeout: 1.0)
            XCTAssert(true)
        }
    
        class AClass {
            var disposal = Disposal()
    
            init(callback: @escaping () -> Void) {
                let singleton = Singleton.shared
                singleton.observable.observe { _, _ in
                    callback()
                }.add(to: &disposal)
            }
        }
    
        class Singleton {
            static let shared = Singleton()
            var observable = Observable(0)
            private init() {}
        }
    
    opened by Edudjr 5
  • Replaces Lock with DispatchQueues to avoid deadlock

    Replaces Lock with DispatchQueues to avoid deadlock

    Replaces Lock with DispatchQueues to avoid deadlock when updating an Observable value from within its Observation.

    Added a read/write queue for set/get the value and added another queue to decoupled the changes broadcasting to observers from the read/write.

    opened by lm2s 5
  • Add test suite

    Add test suite

    I opened this issue, to continue the conversation started in https://github.com/roberthein/Observable/pull/12 about tests.

    Do you want create the Xcode project to add the test target, or should I create it? 🙂

    opened by 4brunu 5
  • Make the Disposable initializer public

    Make the Disposable initializer public

    Our project has an extension to wrap some existing Observable-like APIs in the Realm framework so that they match the Observable API. In order to do this we need to create a Disposable object, but the initializer for Disposable isn't marked as public so it defaults to internal.

    Can the initializer for Disposable be made public?

    opened by farktronix 4
  • Rename `ImmutableObservable` to `Observable`

    Rename `ImmutableObservable` to `Observable`

    The ImmutableObservable class is great, since it allows you to have a public API in your classes that is safe from external side effects, but the naming semantics are backwards.

    The current name would imply that ImmutableObservable is a special kind of Observable that adds immutability, but that's not the case. Instead Observable is a special kind of ImmutableObservable that adds mutability, but that's not apparent from reading the names.

    I addition, it litters your public API with extra information. The client shouldn't know or care that the Observable is immutable, that's an implementation detail that makes usages more verbose and less readable.

    I suggest flipping the names, so the standard implementation is Observable and the subclass is MutableObservable. That would make the relationship clear and clean up public usages.

    class SomeViewModel {
        /// Public property
        var position: Observable<CGPoint> = {
            return positionSubject
        }
        
        /// Private property
        private let positionSubject = MutableObservable(CGPoint.zero)
    }
    
    opened by rharter 4
  • Deadlock when updating observable value from within observation

    Deadlock when updating observable value from within observation

    I've just noticed that updating the value of an observable from within an observation results in deadlock because a mutex is being used to ensure thread safety. The mutex could be replaced by DispatchQueues and avoid this issue. Is there a specific reason as to why mutexes are being used instead of DispatchQueues?

    I can do a PR for this.

    opened by lm2s 4
  • [Feature] Adding helper methods .bind()

    [Feature] Adding helper methods .bind()

    Hello,

    Firstly a big thanks for this library ! I'm using Observable in a lot of projects and I love it. It fit really well with mvvm. But if you have a lot of properties, the viewController get fat. 1 observable is about 3 lines of codes, so 10 observables is 30 lines. Readability and maintainability become less clear.

    The idea is to reduce the number of line of code to juste one, for all simple cases.

    What do you think about adding something like that :

    func bind<O: NSObject>(to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, T>, disposal: inout Disposal)
    or
    func bind<O: NSObject, R>(to object: O, _ objectKeyPath: ReferenceWritableKeyPath<O, R>, disposal: inout Disposal, transform: @escaping (T) -> R)
    
    

    usage:

    class ViewModel {
       @observable var firstname: String 
    }
    
    class ViewController {
    
        @IBOutlet private weak var firstnameTextField: UITextField!
    
        func viewDidLoad() {
            viewModel.firstname.bind(to: self.firstnameTextField, \.text, disposal: &self.disposal)
            // or 
            viewModel.firstname.bind(to: self.firstnameTextField, \.text, disposal: &self.disposal) {
                  v?.uppercased()
            }
    
        }
    }
    

    I have make some tests with simple cases and it's seems to works, but I'm sure that more work is needed to fit complexes cases and generic makes me sick.

    if this proposal fits with the spirit of Observable, I can make a PR. (if not I keep that in an extension 😉 )

    opened by bfrolicher 3
  • Avoid deadlock by using NSRecursiveLock

    Avoid deadlock by using NSRecursiveLock

    Hey, Currently Observable creates a deadlock when updating an Observable value from within its Observation as described https://github.com/roberthein/Observable/issues/19 and https://github.com/roberthein/Observable/pull/20.

    This PR migrates the lock from pthread_mutex_t to NSRecursiveLock which avoids the deadlock problem. This is also the solution used in RxSwift 1 2.

    Regarding performance, it has a small overhead, which is expected, but I think it's not something noticeable, and it will avoid the dead lock situation.

    Here are some performance tests on pthread_mutex_t and NSRecursiveLock.

    https://github.com/nyisztor/LockTest#the-results

    https://gist.github.com/azinman/5410263c62157086943a#file-appdelegate-m-L30-L41

     Pthread mutex avg: 133 ns
     Pthread reentrant mutex avg: 194 ns
     Pthread reentrant mutex + exceptions avg: 191 ns
     Pthread 3x reentrant mutex + exceptions avg: 262 ns
     Pthread reentrant mutex contended on main avg: 742 ns
     Pthread reentrant mutex contended on high priority avg: 915 ns
    
     NSRecursiveLock avg: 188 ns
     NSRecursiveLock + exception handling avg: 179 ns
     NSRecursiveLock + exception handling 3x avg: 348 ns
     NSRecursiveLock under contention on main + exception handling avg: 758 ns
     NSRecursiveLock under contention on high priority + exception handling avg: 928 ns
    

    This PR also has a unit tests that tests the deadlock situation.

    @roberthein Please let me know if there is any additional changes to merge this 🙂 Thanks

    opened by 4brunu 3
  • [Feature] Merge/Combine multiple observable

    [Feature] Merge/Combine multiple observable

    What do you thinks about adding something like combineLatest function ? This could be really useful when handling 2 or more observable values.

    Like that

    Observable.combine([observables]) { (values, oldValues) in 
    
    }.add()
    
    

    What would be your approche to do that ?

    Ben

    opened by bfrolicher 0
  • [Feature] observable.skip(count: Int) method

    [Feature] observable.skip(count: Int) method

    Hi,

    Is a skip method possible ?

    This could simplify unit test and some edge case where first or more value must be ignore.

    First I can simply change

    public func observe(_ queue: DispatchQueue? = nil, _ observer: @escaping Observer) -> Disposable 
    

    to

    public func observe(_ queue: DispatchQueue? = nil, skipFirst: Bool = false,  _ observer: @escaping Observer) -> Disposable 
    

    This could solve the first emitted null value when using Observable(String?) = Observable(nil)

    But i can't add skip(count: Int) method without a lot of changes, maybe due to a lack of knowledge about the library.

    ✌️ Ben

    opened by bfrolicher 0
  • [Issue] Tests missing

    [Issue] Tests missing

    Hello,

    Test bundle seems to be broken. I can't find it in _Pod.project or Observable_Example.xcodeproj Screenshot 2020-07-20 at 19 29 52

    A good reason to keep the auto generate pod format? We could just have a simple framework project with unit tests, examples, .podspec, and Package.swift files. I find the format proposed by cocoapods is complex.

    opened by bfrolicher 2
Releases(2.2.0)
Owner
Robert-Hein Hooijmans
iOS Developer
Robert-Hein Hooijmans
📬 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
Rx is a generic abstraction of computation expressed through Observable Element interface,

Rx is a generic abstraction of computation expressed through Observable<Element> interface, which lets you broadcast and subscribe to values and other events from an Observable stream.

ReactiveX 23.1k Dec 31, 2022
Very simple Observable and Publisher implementation for iOS apps.

Very simple Observable and Publisher implementation for iOS apps.

Igor Kulman 7 Jun 11, 2022
Open source implementation of Apple's Combine framework for processing values over time.

OpenCombine Open-source implementation of Apple's Combine framework for processing values over time. The main goal of this project is to provide a com

OpenCombine 2.4k Dec 26, 2022
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
TopicEventBus is Easy to use, type safe way of implementing Publish–subscribe design pattern.

TopicEventBus Publish–subscribe design pattern implementation framework, with ability to publish events by topic. (NotificationCenter extended alterna

Matan Abravanel 55 Nov 29, 2021
Bond is a Swift binding framework that takes binding concepts to a whole new level.

Bond, Swift Bond Update: Bond 7 has been released! Check out the migration guide to learn more about the update. Bond is a Swift binding framework tha

Declarative Hub 4.2k Jan 5, 2023
Write great asynchronous code in Swift using futures and promises

BrightFutures How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. BrightFutures implements proven fu

Thomas Visser 1.9k Dec 20, 2022
Easy Swift Futures & Promises.

❗️ Archived now ❗️ Since Apple released Combine framework, I decide to archive this repo. You still can use this repo as an example of Future/Promise

Dmytro Mishchenko 40 Sep 23, 2022
Type-safe event handling for Swift

emitter-kit v5.2.2 A replacement for NSNotificationCenter#addObserver and NSObject#addObserver that is type-safe and not verbose. import EmitterKit /

Alec Larson 570 Nov 25, 2022
A Swift based Future/Promises Library for IOS and OS X.

FutureKit for Swift A Swift based Future/Promises Library for IOS and OS X. Note - The latest FutureKit is works 3.0 For Swift 2.x compatibility use v

null 759 Dec 2, 2022
Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.

Katana is a modern Swift framework for writing iOS applications' business logic that are testable and easy to reason about. Katana is strongly inspire

Bending Spoons 2.2k Jan 1, 2023
📡 Helping you own NotificationCenter in Swift!

Notificationz ?? Helping you own NotificationCenter Highlights Keep Your Naming Conventions: This library gives you convenient access to NotificationC

Kitz 77 Feb 18, 2022
Modern thread-safe and type-safe key-value observing for Swift and Objective-C

Now Archived and Forked PMKVObserver will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork o

Postmates Inc. 708 Jun 29, 2022
A library for reactive and unidirectional Swift applications

ReactorKit is a framework for a reactive and unidirectional Swift application architecture. This repository introduces the basic concept of ReactorKit

ReactorKit 2.5k Dec 28, 2022
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app's components.

null 7.3k Jan 9, 2023
🐌 snail - An observables framework for Swift

?? snail A lightweight observables framework, also available in Kotlin Installation Carthage You can install Carthage with Homebrew using the followin

Compass 179 Nov 21, 2022
Lightweight Promises for Swift & Obj-C

Tomorrowland Tomorrowland is an implementation of Promises for Swift and Objective-C. A Promise is a wrapper around an asynchronous task that provides

Lily Ballard 115 Nov 23, 2022
VueFlux is the architecture to manage state with unidirectional data flow 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