Coder Social home page Coder Social logo

combineproperty's Introduction

CombineProperty

When using Combine, a challenge sometimes is: How can a Publisher's current value be retrieved?

Combine.CurrentValueSubject would be an option since it stores a current value, but this is a dead end - you cannot map() a CurrentValueSubject and retrieve the mapped current value. It would also sometimes be desirable to make a subject immutable.

Properties (inspired by ReactiveSwift) fill these gaps. They take either a CurrentValueSubject or an initial value and a Publisher, and provide:

  • The current value
  • A Publisher that returns all future values, including the current one
  • A Publisher that returns all future values, excluding the current one
  • Operators to transform to other Properties (map, flatMap, boolean and/or/negate,...)

Why?

There are at least two scenarios where Properties shine:

  1. Slicing state (e.g. AppState) into sub-states (e.g. AuthState) for easier testing. Maybe a ViewModel only needs the AuthState to operate - if it gets passed the AppState, then the whole state has to be mocked for every test. To solve this, a Property<AppState> can hold the current (and observable) root app state, and Property<AuthState> can be derived by calling map on the former. This same principle can be be applied per feature, or for reusable components, too โ€” a UICollectionView cell could be passed a property, and subscribe to that property's changes.
  2. Storing dynamic state in a value type (struct / enum). Property is thread-safe and well-tested (its core functionality relies on CurrentValueSubject), so it's an excellent fit for internal storage. That way, even ViewModels can become value types (see example below), because mutability is pushed down to the properties.

Usage

Here's a not-so-academical example of how Properties can be used:

struct ViewModel {
    let viewState: Property<ViewState>
    let cellContents: Property<[CellContent]>
    let isActivityIndicatorVisible: Property<Bool>
    let emptyListPlaceholder: Property<String?>

    init(
        loadImpulses: PassthroughSubject<APIParams, Never>,
        fetchFromAPI: @escaping (APIParams) -> AnyPublisher<APIResult, APIError>
    ) {
        let loadingStates = loadImpulses
            .flatMap { params -> AnyPublisher<ViewState, Never> in
                let loadingState = Just(ViewState.loading(earlierResult: nil))
                let fetchStates = fetchFromAPI(params)
                    .map(ViewState.loaded)
                    .catch { Just(ViewState.error($0)) }
                return loadingState.append(fetchStates).eraseToAnyPublisher()
            }
        viewState = Property(initial: .initial, then: loadingStates)
        isActivityIndicatorVisible = viewState.map {
            switch $0 {
            case .initial, .loading(.none):
                return true
            case .loading, .loaded, .error:
                return false
            }
        }
        cellContents = viewState.map {
            switch $0 {
            case let .loaded(result), let .loading(earlierResult: .some(result)):
                return result.lines
            case .initial, .error, .loading:
                return []
            }
        }
        emptyListPlaceholder = viewState.map {
            switch $0 {
            case let .loaded(result), let .loading(earlierResult: .some(result)):
                return result.lines.isEmpty ? "No items found" : nil
            case .initial, .error, .loading:
                return nil
            }
        }
    }
}

enum ViewState {
    case initial
    case loading(earlierResult: APIResult?)
    case loaded(APIResult)
    case error(APIError)
}

// Simple placeholder types so the example compiles:
struct APIResult {
    let lines: [String]
}
typealias CellContent = String
typealias APIParams = Int
enum APIError: Error {
    case wrapped(Error)
}

State of this Lib

This is an early beta. Crucial tests exist now, e.g. for lifetime, and the central flatMap operator. All public components and methods are documented.

Collaborate

I'm happy to accept pull requests, and my DMs are open @manuelmaly.

Changelog

0.2.0 (2020/10/25):

  • Fixed lifetime problems for when a Property is not retained, but its Publishers are still being observed.
  • Added crucial tests for Property lifetime and flatMap
  • Added documentation to all public-facing components and methods
  • Added operators:
    • map with 1, 2, or 3 keypaths
    • map with a fixed value
    • filter
    • compactMap
    • zip with one Property

0.1.0 (2020/10/17):

  • Initial implementation. Early alpha.

combineproperty's People

Contributors

manmal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

combineproperty's Issues

currentValue must not be weak

currentValue should not be held weakly by the unsafePublisher's sink, because if subsequentValues is subscribed to while the Property is deallocated, that could in some instances lead to currentValue being deallocated early.

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.