GithubHelp home page GithubHelp logo

roberthein / observable Goto Github PK

View Code? Open in Web Editor NEW
370.0 10.0 38.0 4.83 MB

The easiest way to observe values in Swift.

License: MIT License

Swift 100.00%
functional reactive programming observable observer swift easy observe value values

observable's Introduction

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.

observable's People

Contributors

4brunu avatar edudjr avatar farktronix avatar fxm90 avatar lm2s avatar matsue avatar rharter avatar roberthein avatar thomas-sivilay avatar zahidappsgenii 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

observable's Issues

Library life

Hi,

Two months have passed since my last comments and still no answer.
I love this library but we (developers) need to be sure that the tools we use are maintained and that there is a semblance of life.
This is not a criticism, I know it's not always easy to spend time on it, but I need an answer.
What is the future of this library ?
Should I make a fork, and move on my own or move on to something else?

Sincerely
Benjamin

[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 ๐Ÿ˜‰ )

Logic

I went thru the code, IT feels like its keeping a dict for all the values and when there is a change it loops thru everything and update the data.

In this case if you use this for a project it will slow down every thing

Observe on a given DispatchQueue

Hi, first thanks for this awesome library!

It would be great if it would be possible to add an observer on a given DispatchQueue.

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

To make this change without breaking the API, the DispatchQueue could be an optional parameter.

What do you think of this?
If you agreed with this change, I can try to make a PR if you want ๐Ÿ™‚

Observable.value thread safe

One thing that worries me, is that Observable.value is not thread safe.

What do you think about adding some thread safe mechanism, like atomic, to ensure that Observable.value doesn't get changed by two thread at same time?

Observable with readonly value

Hey, I would like to have the ability of having an Observable with readonly value.

This way we can make sure that some classes that are should only observe changes, don't emit values.

What do you think?

Make Observable and ImmutableObservable 'open' classes

Hi! Thanks for the nice library.

Could you change Observable and ImmutableObservable classes to be 'open'
It will allow creating some project specific Observables classes.
E.g. I want to implement ProgressObservable which should extend from ImmutableObservable

[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

Add test suite

I opened this issue, to continue the conversation started in #12 about tests.

Do you want create the Xcode project to add the test target, or should I create it? ๐Ÿ™‚

Carthage installation instructions from readme don't work

I added Observable to my Cartfile as per the readme
github "roberthein/Observable" "master"

But it didn't work:

*** Cloning Observable
*** Checking out Observable at "513781328be8ed6b66ab71c4ea2a59b736140f41"
*** xcodebuild output can be found in /var/folders/l0/hwn7fkf57_b1sxlp360mg3lr0000gn/T/carthage-xcodebuild.NPMhHh.log

*** Skipped building Observable due to the error:
Dependency "Observable" has no shared framework schemes

If you believe this to be an error, please file an issue with the maintainers at https://github.com/roberthein/Observable/issues/new

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

[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.

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.

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() {}
    }

[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

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.

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.