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<CGPoint> = {
        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

//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
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 Jan 2, 2023
Two-way data binding framework for iOS. Only one API to learn.

BindKit A simple to use two-way data binding framework for iOS. Only one API to learn. Supports Objective-C, Swift 5, Xcode 10.2, iOS 8 and above. Shi

Electric Bolt 13 May 25, 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
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
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
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
Reactive Programming in Swift

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

ReactiveX 23.1k Jan 5, 2023
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 Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Dec 25, 2022
A Swift Reactive Programming Kit

ReactiveKit is a lightweight Swift framework for reactive and functional reactive programming that enables you to get into the reactive world today. T

Declarative Hub 1.2k Dec 29, 2022
RxSwift wrapper around the elegant HTTP networking in Swift Alamofire

RxAlamofire RxAlamofire is a RxSwift wrapper around the elegant HTTP networking in Swift Alamofire. Getting Started Wrapping RxSwift around Alamofire

RxSwift Community 1.6k Jan 3, 2023
An array class implemented in Swift that can be observed using ReactiveCocoa's Signals

ReactiveArray An array class implemented in Swift that can be observed using ReactiveCocoa's Signals. Installation Carthage Add the following to your

Wolox 53 Jan 29, 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
Predictable state container for Swift too

ReduxSwift ReduxSwift is a minimal Swift port of Redux, a popular JavaScript library for application state management. Functionality Centralized State

Lucas Sunsi Abreu 38 Oct 6, 2020
Aftermath is a stateless message-driven micro-framework in Swift

Aftermath is a stateless message-driven micro-framework in Swift, which is based on the concept of the unidirectional data flow architecture.

HyperRedink 70 Dec 24, 2021
🔄 Unidirectional data flow in Swift.

Reactor Reactor is a framework for making more reactive applications inspired by Elm, Redux, and recent work on ReSwift. It's small and simple (just o

Reactor 175 Jul 9, 2022
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 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