Git Product home page Git Product logo

reactiveswift-composable-architecture's Introduction

ReactiveCocoa

Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift.

Join the ReactiveSwift Slack community.


Carthage compatible CocoaPods compatible SwiftPM compatible GitHub release Swift 5.1 platforms

⚠️ Looking for the Objective-C API?

🎉 Migrating from RAC 4.x?

🚄 Release Roadmap

What is ReactiveSwift?

ReactiveSwift offers composable, declarative and flexible primitives that are built around the grand concept of streams of values over time. These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are fundamentally an act of observation.

For more information about the core primitives, see ReactiveSwift.

What is ReactiveCocoa?

ReactiveCocoa wraps various aspects of Cocoa frameworks with the declarative ReactiveSwift primitives.

  1. UI Bindings

    UI components expose BindingTargets, which accept bindings from any kind of streams of values via the <~ operator.

    // Bind the `name` property of `person` to the text value of an `UILabel`.
    nameLabel.reactive.text <~ person.name

    Note: You'll need to import ReactiveSwift as well to make use of the <~ operator.

  2. Controls and User Interactions

    Interactive UI components expose Signals for control events and updates in the control value upon user interactions.

    A selected set of controls provide a convenience, expressive binding API for Actions.

    // Update `allowsCookies` whenever the toggle is flipped.
    preferences.allowsCookies <~ toggle.reactive.isOnValues
    
    // Compute live character counts from the continuous stream of user initiated
    // changes in the text.
    textField.reactive.continuousTextValues.map { $0.characters.count }
    
    // Trigger `commit` whenever the button is pressed.
    button.reactive.pressed = CocoaAction(viewModel.commit)
  3. Declarative Objective-C Dynamism

    Create signals that are sourced by intercepting Objective-C objects, e.g. method call interception and object deinitialization.

    // Notify after every time `viewWillAppear(_:)` is called.
    let appearing = viewController.reactive.trigger(for: #selector(UIViewController.viewWillAppear(_:)))
    
    // Observe the lifetime of `object`.
    object.reactive.lifetime.ended.observeCompleted(doCleanup)
  4. Expressive, Safe Key Path Observation

    Establish key-value observations in the form of SignalProducers and DynamicPropertys, and enjoy the inherited composability.

    // A producer that sends the current value of `keyPath`, followed by
    // subsequent changes.
    //
    // Terminate the KVO observation if the lifetime of `self` ends.
    let producer = object.reactive.producer(forKeyPath: #keyPath(key))
    	.take(during: self.reactive.lifetime)
    
    // A parameterized property that represents the supplied key path of the
    // wrapped object. It holds a weak reference to the wrapped object.
    let property = DynamicProperty<String>(object: person,
                                           keyPath: #keyPath(person.name))

But there are still more to be discovered and introduced. Read our in-code documentations and release notes to find out more.

Getting started

ReactiveCocoa supports macOS 10.9+, iOS 8.0+, watchOS 2.0+, and tvOS 9.0+.

Carthage

If you use Carthage to manage your dependencies, simply add ReactiveCocoa to your Cartfile:

github "ReactiveCocoa/ReactiveCocoa" ~> 10.1

If you use Carthage to build your dependencies, make sure you have added ReactiveCocoa.framework and ReactiveSwift.framework to the "Linked Frameworks and Libraries" section of your target, and have included them in your Carthage framework copying build phase.

CocoaPods

If you use CocoaPods to manage your dependencies, simply add ReactiveCocoa to your Podfile:

pod 'ReactiveCocoa', '~> 10.1'

Swift Package Manager

If you use Swift Package Manager, simply add ReactiveCocoa as a dependency of your package in Package.swift:

.package(url: "https://github.com/ReactiveCocoa/ReactiveCocoa.git", branch: "master")

Git submodule

  1. Add the ReactiveCocoa repository as a submodule of your application’s repository.
  2. Run git submodule update --init --recursive from within the ReactiveCocoa folder.
  3. Drag and drop ReactiveCocoa.xcodeproj and Carthage/Checkouts/ReactiveSwift/ReactiveSwift.xcodeproj into your application’s Xcode project or workspace.
  4. On the “General” tab of your application target’s settings, add ReactiveCocoa.framework and ReactiveSwift.framework to the “Embedded Binaries” section.
  5. If your application target does not contain Swift code at all, you should also set the EMBEDDED_CONTENT_CONTAINS_SWIFT build setting to “Yes”.

Have a question?

If you need any help, please visit our GitHub issues or Stack Overflow. Feel free to file an issue if you do not manage to find any solution from the archives.

Release Roadmap

Current Stable Release:
GitHub release

In Development

Plan of Record

ABI stability release

ReactiveCocoa is expected to declare library ABI stability when Swift rolls out resilience support in Swift 5. Until then, ReactiveCocoa will incrementally adopt new language features.

reactiveswift-composable-architecture's People

Contributors

alexito4 avatar andreyz avatar filblue avatar finestructure avatar iampatbrown avatar jager-yoo avatar jasdev avatar jeffersonsetiawan avatar kaandedeoglu avatar kalupas226 avatar kgrigsby59 avatar konomae avatar mackoj avatar maximkrouk avatar mbrandonw avatar mluisbrown avatar nmccann avatar ollitapa avatar omaralbeik avatar onevcat avatar p4checo avatar peterkovacs avatar pitt500 avatar rono23 avatar seviocorrea avatar stephencelis avatar tgrapperon avatar ts avatar wendyliga avatar yimajo 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

reactiveswift-composable-architecture's Issues

Object get deallocated on Signal.Observer

This error happen on device not simulator, it happens before pushing a new controller on IfLet.
Could anyone please help me get out of this ?
iOS version : 14.5
Xcode 12.5

Screenshot 2021-05-11 at 13 22 19

Change TestStore so that it isn't only compiled when running in DEBUG

On one of the projects I work on, we are forced to pull in CA manually. This is because we have a build configuration for testing that isn't DEBUG. However, this means that we can not use TestStore since that is guarded by DEBUG. It would be nice to be able to use TestStore regardless of project configuration.

Would it be possible to remove the #if DEBUG here?

ViewStore version without Combine/SwiftUI

Hi,

First of all, thank you for doing an amazing job in creating this version of the library. It's been of a lot of help.

At my workplace we're still required to support iOS 12, which means that we can't use Combine/SwiftUI yet.
I wanted to integrate this version, as it's based on ReactiveSwift which can run on older version, but to my surprise, parts of it are still using Combine.

Is there any option to remove dependency from ViewStore to Combine.
It's conforming to ObservableObject(least of all) which makes it impossible to use in older version.

Include TCA Swift Concurrency changes from release 0.39.0

TCA just released version 0.39.0 which includes all the Swift Concurrency changes, and fundamentally changes many internals of the library. For example, Effect no longer conforms to Publisher but instead holds on to a Publisher internally.

The changes encourage moving away from Combine based effects to effects that use Swift Concurrency using Effect.task.

I've been able to maintain this fork in sync with TCA up to the last commit prior to the merge of the Swift Concurrency PR, however I feel that trying to update the fork to include the Swift Concurrency changes would be a huge task, and I don't have the necessary time to dedicate to it.

There's also the question of how useful this fork will continue to be. The objective for @mbrandonw and @stephencelis with TCA is clearly (and rightly) to be able to remove Combine completely from TCA sooner or later, at which point the need for a Reactive Swift fork really ceases to exist. Even now, this fork has a minimum deployment target of iOS 13 so the only use cases for it are:

  • existing projects using it which haven't yet migrated to Combine TCA
  • projects that don't want to use Combine as it's not open source
  • projects using non-Apple OSes like Linux and Android

If anyone is interested in trying to migrate this fork to the new changes, I'd be happy to help out where I can.

Nested properties keyPath support for Produced values

I'd like to add support for the nested properties in Produced values, current implementation use
@dynamicMemberLookup, which does not support nested properties yet, and I have 2.5 solutions:

Zero (not a real one, I mean 😅)
Use

viewStore.property.map(\.nestedProperty).skipRepeats()

which is okay, but not the most convenient stuff.

First
Add another method to the ViewStore type to support default KeyPath stuff:

viewStore.producer(for: \.KeyPath<State, Value>) -> Produced<Value>

That extremely easy and fits ReactiveSwift, but I'm not sure if it fits ComposableArchitecture.
Maybe it worth mentioning in readme or smth if approved.
I use it locally now btw.

Second
Use Produced type as a dynamicMemberLookup container and do not return a producer implicitly, but return mapped itself.

/// A producer of store state.
@dynamicMemberLookup
public struct Produced<Value> { // <- have to drop `SignalProducerConvertible` protocol
    private let _producer: Effect<Value, Never> // <- private stuff
    
    init(by upstream: Effect<Value, Never>) {
        self._producer = upstream
    }
    
    /// Returns the resulting producer of a given key path.
    public subscript<LocalValue>(
        dynamicMember keyPath: KeyPath<Value, LocalValue>
    ) -> Produced<LocalValue> {
        Produced<LocalValue>(by: _producer.map(keyPath)) // <- here is a change
    }
}

extension Produced {
    public var producer: Effect<Value, Never> { _producer } // <- Logic inconsistency {li}
}

extension Produced where Value: Equatable {
    public var producer: Effect<Value, Never> { _producer.skipRepeats() } // <- Logic inconsistency {li}
}

Usage:

viewStore.property.nestedProperty.producer

That will lead to more consistent API, but the implementation will be major so that users won't be able to subscribe to any property without explicitly calling the producer anymore. Also, there is a little logic inconsistency at {li} marks due to Equatable conformance, that should be tested and anyways it may suffer from type erasure stuff.

Multiple calls to store.ifLet

Thanks for the hard work in making this fork! I'm currently noticing an issue with calling ifLet on a Store. I have a test project that I've been messing around with to get a good feel for using this fork. I can share the test project if needed.

The issue is that I have two modals that are presented from the root view controller. Both have optional states on the root State. When presenting/dismissing the modals one after the other, I'm noticing that the ifiLet call on the store is called multiple times. This doesn't happen the first time, only after dismissing a modal once or twice, and then it happens every time after that.

Here's what my root state looks like:

struct State {
    var items: ItemsState?
    var favorites: FavoritesState?
    ...
}

I'm presenting the view controllers with the following:

let store = self.store.scope(state: \.favorites, action: Root.Action.favorites)
store.ifLet { [weak self] store in
    guard let self = self else { return }
    let vc = FavoritesViewController(store: store)
    let nc = UINavigationController(rootViewController: vc)
    nc.presentationController?.delegate = self
    self.present(nc, animated: true, completion: nil)
}

I was able to fix my issue by creating another variant of ifLet. The "fix" is to use skipRepeats() instead of .skipRepeats { ($0 != nil) == ($1 != nil) }.

@discardableResult
public func ifLet2<Wrapped: Equatable>(
    then unwrap: @escaping (Store<Wrapped, Action>) -> Void,
    else: @escaping () -> Void
) -> Disposable where State == Wrapped? {
    let elseDisposable =
        self
        .producerScope(
            state: { state -> Effect<Wrapped?, Never> in
                state
                    .skipRepeats()
            }
        )
        .startWithValues { store in
            if ViewStore(store).state == nil { `else`() }
        }

    let unwrapDisposable =
        self
        .producerScope(
            state: { state -> Effect<Wrapped, Never> in
                state
                    .skipRepeats()
                    .compactMap { $0 }
            }
        )
        .startWithValues(unwrap)

    return CompositeDisposable([elseDisposable, unwrapDisposable])
}

I don't want to break something else by doing this though, so I'm curious what I might be missing? I'll try digging in to this a bit more if I get the time.

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.