Code Monkey home page Code Monkey logo

opencombine's Introduction

OpenCombine

codecov Language Cocoapods

Open-source implementation of Apple's Combine framework for processing values over time.

The main goal of this project is to provide a compatible, reliable and efficient implementation which can be used on Apple's operating systems before macOS 10.15 and iOS 13, as well as Linux, Windows and WebAssembly.

CI Status
Compatibility tests
macOS
Ubuntu
Windows
Wasm

Installation

OpenCombine contains three public targets: OpenCombine, OpenCombineFoundation and OpenCombineDispatch (the fourth one, COpenCombineHelpers, is considered private. Don't import it in your projects).

OpenCombine itself does not have any dependencies. Not even Foundation or Dispatch. If you want to use OpenCombine with Dispatch (for example for using DispatchQueue as Scheduler for operators like debounce, receive(on:) etc.), you will need to import both OpenCombine and OpenCombineDispatch. The same applies to Foundation: if you want to use, for instance, NotificationCenter or URLSession publishers, you'll need to also import OpenCombineFoundation.

If you develop code for multiple platforms, you may find it more convenient to import the OpenCombineShim module instead. It conditionally re-exports Combine on Apple platforms (if available), and all OpenCombine modules on other platforms. You can import OpenCombineShim only when using SwiftPM. It is not currently available for CocoaPods.

Swift Package Manager
Swift Package

To add OpenCombine to your SwiftPM package, add the OpenCombine package to the list of package and target dependencies in your Package.swift file. OpenCombineDispatch and OpenCombineFoundation products are currently not supported on WebAssembly. If your project targets WebAssembly exclusively, you should omit them from the list of your dependencies. If it targets multiple platforms including WebAssembly, depend on them only on non-WebAssembly platforms with conditional target dependencies.

dependencies: [
    .package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.14.0")
],
targets: [
    .target(
        name: "MyAwesomePackage",
        dependencies: [
            "OpenCombine",
            .product(name: "OpenCombineFoundation", package: "OpenCombine"),
            .product(name: "OpenCombineDispatch", package: "OpenCombine")
        ]
    ),
]
Xcode

OpenCombine can also be added as a SwiftPM dependency directly in your Xcode project (requires Xcode 11 upwards).

To do so, open Xcode, use FileSwift PackagesAdd Package Dependency…, enter the repository URL, choose the latest available version, and activate the checkboxes:

Select the OpenCombine and OpenCombineDispatch targets

CocoaPods

To add OpenCombine to a project using CocoaPods, add OpenCombine and OpenCombineDispatch to the list of target dependencies in your Podfile.

pod 'OpenCombine', '~> 0.14.0'
pod 'OpenCombineDispatch', '~> 0.14.0'
pod 'OpenCombineFoundation', '~> 0.14.0'

Debugger Support

The file opencombine_lldb.py defines some lldb type summaries for easier debugging. These type summaries improve the way lldb and Xcode display some OpenCombine values.

To use opencombine_lldb.py, figure out its full path. Let's say the full path is ~/projects/OpenCombine/opencombine_lldb.py. Then the following statement to your ~/.lldbinit file:

command script import ~/projects/OpenCombine/opencombine_lldb.py

Currently, opencombine_lldb.py defines type summaries for these types:

  • Subscribers.Demand
  • That's all for now.

Contributing

See CONTRIBUTING.md.

opencombine's People

Contributors

5sw avatar adamleonard avatar arthurchi avatar beastgrim avatar broadwaylamb avatar compnerd avatar dependabot[bot] avatar devmaximilian avatar epatey avatar franzbusch avatar grigorye avatar kateinoigakukun avatar m0rtymerr avatar magauran avatar marcusficner avatar marcussc avatar maxdesiatov avatar mayoff avatar nomo-nomad avatar ohitsdaniel avatar spadafiva avatar stuaustin avatar vladiulianbogdan avatar vukrado 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  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

opencombine's Issues

Getting the error `Instance method 'store(in:)' requires the types 'OpenCombine.AnyCancellable' and 'Combine.AnyCancellable' be equivalent`

Hi folks.

Just started using this library, and got the following error:

Instance method 'store(in:)' requires the types 'OpenCombine.AnyCancellable' and 'Combine.AnyCancellable' be equivalent

This is within the block:

.receive(on: RunLoop.main)
                .sink(receiveCompletion: { (completion) in
                    if case let .failure(error) = completion {
                        switch error {
                        case let urlError as URLError:
                            promise(.failure(.urlError(urlError)))
                        case let decodingError as DecodingError:
                            promise(.failure(.decodingError(decodingError)))
                        case let apiError as APIError:
                            promise(.failure(apiError))
                        default:
                            promise(.failure(.genericError))
                        }
                    }
                }, receiveValue: { promise(.success($0.results)) })
                .store(in: &subscriptions)
            }

Let me know if you need any additional information, code context etc.

Thanks!

CocoaPods Support

Hey guys! super cool initiative and amazing work!

I wanted to ask if you guys plan to add support for CocoaPods any time soon?

Thanks!

Tests don't compile with OPENCOMBINE_COMPATIBILITY_TEST enabled

I picked up a few operators and thought I finished implementing them. I'm having a hard time verifying whether tests are ok since some files don't compile (tried Xcode 11 beta 1 and 4, the latest one).

The issues are mainly with a few Publishers that seem to be gone in the latest version of the framework, like Once and Empty. I guess I could just remove some of these from my test target, but I was wondering whether it's only me running into this issue?

Also, how should this be handled in general, I assume we'll have to revisit the API another few times (hopefully not that many since release is coming close), any plans on how to approach updating the API across the board?

Try sink of CurrentValueSubject makes a crash with XCode 11.2.1.

First of all, thanks for your hard work for this open-source, OpenCombine.

I used OpenCombine for using Combine at below iOS 13 in our project.
We successfully released a new version that has many codes operated by OpenCombine. It released with XCode 11.1 and OpenCombine '0.5.0' ver.

And we're preparing a new version with XCode 11.2.1 this time.

However, 0.5.0 ver has an issue in this XCode version.
I prepared a sample for repeating that crash, here is repo.
https://github.com/meccatol/OpenCombineTest

When I tested it with XCode 11.1, it not make a crash.
But It must make a crash with XCode 11.2.1.
(I also tested with Combine in the same project and same code, it's okay, it is not causing a crash.)

Crash happened at 'Subscribers.Sink.swift, line: 56.
subscription.request(.unlimited), Error: EXC_BAD_ACCESS (code=2, address=0x10d96e889)
But I don't know any idea for fixing this issue. :(

I hope that you would watch this issue soon, and I will be waiting for feedback. Thanks!

Extra call to sink

I wrote a little example the I tested in Combine and Open Combine and got two different outputs.

_ = Publishers.Sequence(sequence: [1,2,3,4,5,6]) .map { print("map:" , $0); return String($0) } .sink(receiveValue: { print("sink: " , $0) })

Expected output based on Combine import:

map: 1
map: 2
map: 3
map: 4
map: 5
map: 6
sink: 1
sink: 2
sink: 3
sink: 4
sink: 5
sink: 6

Actual output base on OpenCombine output 0.3.0:
map: 1
map: 2
map: 3
map: 4
map: 5
map: 6
sink: 2 <- this is the offending print
sink: 1
sink: 2
sink: 3
sink: 4
sink: 5
sink: 6

Can not import @Published directly

I am not able to use directly @Published, instead I had to use @OpenCombine.Published. Is there any way to make it work?
I have pushed a demo to a repository
PD: iOS11 target

Great work with the open source library. Thanks :)

Make OpenCombine compatible with the latest SDKs in Xcode 12

There are some new APIs in the new version of Combine, also the documentation has been improved.

Although it is tempting to start patching OpenCombine right away, I would prefer to wait until later Xcode betas, or even the GM build. My experience shows that a lot can change between betas (behavior changes back and forth, APIs are added, bugs are fixed etc etc), so waiting for things to stabilize is a good strategy.

I've already updated the Combine API surface that we target:

consider adapting Apple's implementations of Foundation and Dispatch publishers

Apple's implementations of the Foundation and Dispatch publishers and schedulers are part of the Swift open-source project. Have you considered adapting those implementations for OpenCombine?

Here are the files of interest:

OpenCombine/Combine discrepancy: Old values received by nested subscribers of @Published

Consider the following code:

import Foundation

import Combine
//import OpenCombine
//typealias Published = OpenCombine.Published

class SomeDependency {
    @Published var value = 0
}

class NestedObject {
    let dependency: SomeDependency
    var subscriptions = Set<AnyCancellable>()

    init(dependency: SomeDependency) {
        self.dependency = dependency

        dependency.$value
            .sink { value in
                print("Nested:", value)
            }
            .store(in: &subscriptions)

    }
}

class SuperObject {
    let dependency: SomeDependency
    var nestedObject: NestedObject?
    var subscriptions = Set<AnyCancellable>()

    init(dependency: SomeDependency) {
        self.dependency = dependency

        dependency.$value
            .sink { [unowned self] value in
                print("Super:", value)
                if value == 1 {
                    nestedObject = NestedObject(dependency: dependency)
                }
            }
            .store(in: &subscriptions)
    }
}

let dependency = SomeDependency()
let superObject = SuperObject(dependency: dependency)
dependency.value = 1

When importing Apple's Combine the output for the nested print is 1. When importing OpenCombine the received value is 0.

Kind regards

Karsten

Create removeDuplicates and removeDuplicates(by:)

Thank you for OpenCombine! 😊

I realised that the work for removeDuplicates and removeDuplicates(by:) is missing, so I am volunteering to work on these two operators.

If that is fine to you @broadwaylamb, can you assign this issue to me? As far as I can see, I can not do it.

OpenCombine Debounce is not interchangeable with Combine Debounce

Given the following example class:

import Foundation
import Combine


class CombineViewController : UIViewController {
    
    private var buttonCancellable : AnyCancellable?
    
    override func viewDidLoad() {
        let button = UIButton(frame: CGRect(x: 100,
                                            y: 100,
                                            width: 200,
                                            height: 60))
        button.backgroundColor = .green
        button.setTitle("Button", for: .normal)
        self.view.backgroundColor = .white
        self.view.addSubview(button)
        
        let subject = PassthroughSubject<Int, Never>()
            
        buttonCancellable = subject
            .debounce(for: .milliseconds(500), scheduler: RunLoop.main)
            .sink { index in
                print ("Received index \(index)")
        }
        
    }
}

The above code will compile if "Combine" is imported, if instead we replace the imports with:

import OpenCombine
import OpenCombineDispatch
import OpenCombineFoundation

We receive the following errors:

CombineViewController.swift:34:14: Instance method 'debounce(for:scheduler:options:)' requires that 'RunLoop' conform to 'Scheduler'
CombineViewController.swift:34:29: Cannot infer contextual base in reference to member 'milliseconds'

Removing the debounce line compiles using either Combine or OpenCombine.

Installed via Cocoapods:

  - OpenCombine (0.10.0)
  - OpenCombineDispatch (0.10.0):
    - OpenCombine (>= 0.9)
  - OpenCombineFoundation (0.10.0):
    - OpenCombine (>= 0.9)
  OpenCombine: 3601defa3d2dbb6db5cd81444f762597168cfe24
  OpenCombineDispatch: ff26af5c01e2fd01b3f6552eb8096c6f1ee09ce6
  OpenCombineFoundation: f967ffc3aec477eb9efc8d6a421cdf1e4ba8197d

How to run compatibility test on macOS 10.14.

Hi, @broadwaylamb !

First of all, great job! I came up with the same idea two days after the release of the Combine: using the power of the community to implement the official spec, making it no longer have the limitations of the os version and platform.

Actually, I've been working there for a few days, 😄:
st

But I'm happy to see you have gone a step further than me, so I decided to stop my side and work with you to accomplish this goal together!

Before I make my first commit, I want to know, how to run a compatibility test on macOS 10.14?

Implement IgnoreOutput

Thanks for the important project. It will make transitioning to Combine realistic.

To get my feet wet, I took a stab at IgnoreOutput since it's so trivial. Happy to pick up some more once this one goes in.

Won't compile for iOS in Xcode 13 Beta 3

I see the following:

Failed to build module 'Combine'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 5.5 (swiftlang-1300.0.24.14 clang-1300.0.25.10)', while this compiler is 'Apple Swift version 5.5 (swiftlang-1300.0.24.13 clang-1300.0.25.10)'). Please select a toolchain which matches the SDK.

Next to:

#if !canImport(Combine)

in Optional.Publisher with a dozen other similar errors.

Linux Build Broken on Swift 5.3+: kCFStringEncodingUTF8 not in scope

Added to Package.swift:

.package(name: "OpenCombine",
    url: "https://github.com/OpenCombine/OpenCombine.git",
    from: "0.10.0")

// Added to target dependencies:
.product(name: "OpenCombine", package: "OpenCombine"),
.product(name: "OpenCombineDispatch", package: "OpenCombine"),
.product(name: "OpenCombineFoundation", package: "OpenCombine"),

On macOS 10.15.6 using Swift for Tensorflow v0.11 I am able to build successfully:

$ which swift
/Library/Developer/Toolchains/swift-tensorflow-RELEASE-0.11.xctoolchain/usr/bin/swift
$ swift --version
Swift version 5.3-dev (LLVM db8896f3f345af2, Swift 61684f62a6132c0)
Target: x86_64-apple-darwin19.6.0

However, on Ubuntu 18.04 using Swift for Tensorflow v0.10 I am unable to build successfully:

$ which swift
/home/xander/swift-tensorflow-RELEASE-0.10-cuda10.2-cudnn7-ubuntu18.04/usr/bin/swift
$ swift --version
Swift version 5.3-dev (LLVM 55d27a5828, Swift 6a5d84ec08)
Target: x86_64-unknown-linux-gnu
$ swift build
.build/checkouts/OpenCombine/Sources/OpenCombineFoundation/Timer+Publisher.swift:343:34: error: cannot find 'kCFStringEncodingUTF8' in scope
                CFStringEncoding(kCFStringEncodingUTF8)
                                 ^~~~~~~~~~~~~~~~~~~~~

Swift for Tensorflow is based on the official dev branch.

I also tried building on the latest official 5.3 release branch on Ubuntu 18.04, still same error:

$ which swift
/home/xander/swift-5.3-DEVELOPMENT-SNAPSHOT-2020-08-31-a-ubuntu18.04/usr/bin/swift
$ swift --version
Swift version 5.3-dev (LLVM 3fa9679add, Swift d24649a4d6)
Target: x86_64-unknown-linux-gnu
$ swift build
.build/checkouts/OpenCombine/Sources/OpenCombineFoundation/Timer+Publisher.swift:343:34: error: cannot find 'kCFStringEncodingUTF8' in scope
                CFStringEncoding(kCFStringEncodingUTF8)
                                 ^~~~~~~~~~~~~~~~~~~~~

However, I can successfully build on Ubuntu 18.04 with Swift 5.2.5 official release toolchain:

$ which swift
/home/xander/swift-5.2.5-RELEASE-ubuntu18.04/usr/bin/swift
$ swift --version
Swift version 5.2.5 (swift-5.2.5-RELEASE)
Target: x86_64-unknown-linux-gnu

It appears specifically on Linux, the build is broken on Swift 5.3 release branch, as well as dev trunk.

Strangely, I do see kCFStringEncodingUTF8 referenced in the swift-corelibs-foundation. Is there an additional #include that's required now?

Combine documentation states that the Subscription protocol is class constrained

Im not sure if this is an issue in Apples documentation or implementation but according to the documentation it says "Subscriptions are class constrained because a Subscription has identity, defined by the moment in time a particular subscriber attached to a publisher." But according to the implementation it is not class constrained. I see two instances of a non class conformance to Subscription in OpenCombine one is SideEffectSubscription in Publishers.Autoconnect and the other is _EmptySubscription. I am not sure if I am missing something here but I was wondering what everyones thoughts are on this

Build failing on iOS14

Hi, I am trying to compile on iOS14 but it is failing.

testAssignToPublished is using assign(to: Publisher) and the test case is wrapped with @available(macOS 10.15, iOS 13.0, *), so when trying to make it compile it returns an error 'assign(to:)' is only available in iOS 14.0 or newer. If we update the test case to use iOS14 it works, but I don't know if you are going to update this or split the tests between iOS13 and iOS14.

Environment: XCode12.1 + iOS14.1

Thank you.

Carthage support

For and my team lack of support for Carthage dependency manager is a real barrier to adoption.

It would be great if OpenCombine supported it .

Ready for production?

Hi, what's the status of this framework, is it stable enough for production usage?

CurrentValueSubject is not sending correct value

Hey,

first thanks for this project, I think this can have a huge community impact if we drive it forward and Apple doesn't open-source Combine. I was playing around with it and found a behaviour that I guess is not matching the Apple Combine behaviour.

Take this code:

let subject = CurrentValueSubject<Int, Never>(0)
subject.send(2)
let c = subject.sink { value in
    print(value)
}

Apple's variant is printing 2 but OpenCombine is printing 0. I tracked down the code and saw that we are not storing the send input in the CurrentValueSubject. I changed this behaviour but then a lot of tests were failing. Should I open a PR and we can see there, what the correct approach is?

Edit: I opened a PR and every test except 1 is working, which is the synchronisation test. Would be good if you can take a look.

Why enforce matching of name of Subscription with Apple's implementation

The common pattern in the unit tests is to assert that the string description of the entity that implements Subscription matches what Apple does. For example,

// Testing .tryMap
 XCTAssertEqual(tracking.history, [.subscription("TryMap"),

or

// Testing .map
 XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),

This seems strange to me. From my perspective, that's testing implementation details rather than behavior.

Notice how those two test cases above assert different subscription names. This is because Apple simply chose to proxy the subscription in the TryMap case, but not in the Map case. Of course, in our code, we do the same.

I would argue that this is an anti-pattern. In the Map/TryMap case, if we didn't need to match names of Subscription implementations, we wouldn't need to implement Subscription at all. It could simple be handled by the upstream publisher.

Are there benefits that I'm not understanding?

Merging efforts

Hi,

Thank you for tackling the combine requirements issue. I believe this will help a lot but will require a lot of contributions.

There is another port being build at https://github.com/cx-org/CombineX.
Maybe you could merge yours efforts ? I'm opening the same issue on the other repo.

URLSession.dataTaskPublisher for OpenCombine?

Hi there

So I am on the home stretch, but I have a few issues. Firstly:
I am getting

error: value of type 'URLSession' has no member 'dataTaskPublisher'
            self.urlSession.dataTaskPublisher(for: request)

Is there an OpenCombine equivalent to use dataTaskPublisher?

Secondly, I am also getting:

error: value of type 'Any' has no member 'tryMap'
                .tryMap { (data, response) -> Data in

Is this also functional in OpenCombine?

Thanks once again 🙏🏻

OpenCombine organization?

I'm currently maintaining a few forks of open-source libraries that make those compatible with OpenCombine: GRDBOpenCombine and its dependency OpenCombineExpectations. I'd appreciate any input and contributions to these forks, and maybe some coordinated effort to push a more sane support for "polyfill" libraries in future versions of Swift. If the latter happens, we probably even could upstream changes from these forks to their origin repositories.

Either way, it would make sense to maintain these forks, proposals, and related helper projects under one umbrella OpenCombine organization with an open list of maintainers. This would make the project more visible and I hope attract more attention and contributors. I'm not sure if existing OpenCombine organization is the one previously created for this purpose, if not maybe something like OpenCombineHQ could work as a name just as well.

Implement operators

Mapping Elements

Filtering Elements

  • filter#22
  • tryFilter#22
  • compactMap#32
  • tryCompactMap#32
  • removeDuplicates#89
  • tryRemoveDuplicates#89
  • replaceEmpty#122
  • replaceError#50

Reducing Elements

  • collect#76
  • collect(_:)#137
  • collect(_:options:)
  • ignoreOutput#44
  • reduce#76
  • tryReduce#76

Applying Mathematical Operations on Elements

  • count#20
  • max#76
  • max(by:)#76
  • tryMax(by:)#76
  • min#76
  • min(by:)#76
  • tryMin(by:)#76

Applying Matching Criteria to Elements

  • contains#76
  • contains(where:)#76
  • tryContains(where:)#76
  • allSatisfy#76
  • tryAllSatisfy#76

Applying Sequence Operations to Elements

  • drop(untilOutputFrom:)#136
  • drop(while:)
  • dropFirst#70
  • tryDrop(while:)
  • append#90
  • prepend#90
  • prefix#91
  • prefix(while:)#89
  • tryPrefix(while:)#89
  • prefix(untilOutputFrom:)#206

Selecting Specific Elements

  • first#29
  • first(where:)#29
  • tryFirst(where:)#29
  • last#76
  • last(where:)#76
  • tryLast(where:)#76
  • output(at:)#91
  • output(in:)#91

Combining Elements from Multiple Publishers

Handling Errors

Adapting Publisher Types

  • switchToLatest#142

Controlling Timing

Creating Reference-type Publishers

  • share#60

Encoding and Decoding

  • decode#8
  • encode#8

Identifying Properties with Key Paths

  • map(_ keyPath: KeyPath)#71

Working with Multiple Subscribers

  • multicast
  • multicast(subject:)

Connecting Automatically

  • autoconnect#60

Buffering Elements:

Performing Type-Erasure

  • eraseToAnyPublisher#59

Specifying Schedulers

  • subscribe(on:options:)#116
  • receive(on:options:)#115

Adding Explicit Connectability:

  • makeConnectable#61

Connecting Simple Subscribers:

  • assign
  • sink

Debugging

  • breakpoint#118
  • breakpointOnError#118
  • handleEvents#118
  • print

@Published property wrapper doesn't work

I see in project's release notes that @Published doesn't work yet, which is confusing as it's available in the list of public module declarations. In addition, the property is not recognized even when import OpenCombine is present in the file. Would be great to know if there are some specific reasons for that, otherwise, just creating an issue to track this. Maybe someone could provide more info or directions on the best way to fix this 🙏

Missing `Future` publisher

I've tried to port GRDBCombine to OpenCombine and it seems like the biggest missing piece there is a lack of the Future publisher. Just adding it as a separate issue here for now to hopefully bump its priority on a possible OpenCombine roadmap 🙂

[Question] Reason behind Publisher.makeConnectable’s Failure constraint

Not an “issue” per se but, a question (also mirrored on the Swift forums). Do you know why Combine fences off Publisher.makeConnectable for when Failure == Never? Seems like the project still compiles without it, so I’m having trouble building an intuition for why it’s needed.

https://github.com/broadwaylamb/OpenCombine/blob/d680f09932fe68942c3d391d6df296d38b9dcd05/Sources/OpenCombine/Publishers/Publishers.MakeConnectable.swift#L8

Did you end up leaning on that Never constraint when writing OpenCombine?

Contributions

I would like to be able to use combine in my projects that are pre-ios 13. What do you need help with and how can I contribute?

Supporting Foundation: why it is not possible to do it seamlessly

So the newest version of Foundation depends on Apple's Combine and extends some types like NotificationCenter with convenience methods thet return Combine's Publishers like this:

extension NotificationCenter {

    public func publisher(
        for name: Notification.Name,
        object: AnyObject? = nil
    ) -> NotificationCenter.Publisher {
        // ...
    }
}

So the problem is that if now OpenCombine declares the same extension, the clients which import both Foundation and OpenCombine won't be able to call such functions because of how the mechanism of extensions work:

let pub = NotificationCenter
    .default
    .publisher(for: NSControl.textDidChangeNotification,
               object: filterField)
 // error: ambiguous use of publisher(for:object:)

This is why OpenCombine cannot be fully compatible with Apple's Combine.

We can provide ways to handle this, but it's not perfect, of course:

  • Function overloading — methods in extensions accept one additional argument that helps distinguish the overloads:

    public struct OpenCombineNotificationCenterPublisher: Publisher {
        // ...
    }
    
    public enum OpenCombineNamespace {
        case openCombine
    }
    
    extension NotificationCenter {
    
        public typealias Publisher = OpenCombineNotificationCenterPublisher
    
        public func publisher(
            for name: Notification.Name,
            object: AnyObject? = nil,
            _ namespace: OpenCombineNamespace
        ) -> OpenCombineNotificationCenterPublisher {
            // ...
        }
    }

    which then would be called like this:

    let pub = NotificationCenter
        .default
        .publisher(for: NSControl.textDidChangeNotification,
                   object: filterField,
                   .openCombine)

    This is what, for example, PromiseKit does, but there's one pitfall: it doesn't work with properties.

  • Newtype exposed via property:

    public struct OpenCombineNotificationCenter {
        public let underlying: NotificationCenter
    
        public func publisher(
            for name: Notification.Name,
            object: AnyObject? = nil
        ) -> Publisher {
           // ...
        }
        
        public struct Publisher: OpenCombine.Publisher {
            // ...
        }
    }
    
    extension NotificationCenter {
        var openCombine: OpenCombineNotificationCenter {
            return OpenCombineNotificationCenter(underlying: self)
        }
    }

    At call site it looks like this:

     let pub = NotificationCenter
        .default
        .openCombine
        .publisher(for: NSControl.textDidChangeNotification,
                   object: filterField,
                   .openCombine)

I'd personally took the second approach, but until we implement the fundamental functionality of Apple's Combine there's time to think on this.

Should change unfair lock to unfair recursive lock in Sink?

when AnyCancellable calls cancel function automatically in DEINIT, Sink's cancel function will be called, then terminateAndConsumeLock will be called. As the result, the receiveValue block and receiveCompletion block will be set to nil. Then objects in receiveValue block or receiveCompletion block may be destroyed. If there is an object that in block, it will call same sink's receive completion to finish in object's DEINIT function. That will make a crash cause call lock's lock function twice without unlock.

Result.Inner crash with multiple threads

This might be an example of holding it wrong, but it's a multi-threading crash.

    func testResultPublisherMultiThreaded() throws
    {
      class ExternallyTriggeredSubscriber: Subscriber
      {
        typealias Input = Int
        typealias Failure = Never

        let combineIdentifier = CombineIdentifier()
        var sub: Subscription?

        func receive(subscription: Subscription) { sub = subscription }
        func receive(_ input: Input) -> Subscribers.Demand { return .none }
        func receive(completion: Subscribers.Completion<Failure>) {}

        func request() { sub?.request(.unlimited) }
        func cancel() { sub?.cancel() }
      }

      for i in 1...1000
      {
        let pub = Result<Int, Never>.OCombine.Publisher(i)
        let sub = ExternallyTriggeredSubscriber()
        pub.subscribe(sub)
        DispatchQueue.global(qos: .utility).async { sub.request() }
        DispatchQueue.global(qos: .utility).async { sub.cancel() }
      }
    }

The downstream variable should be protected on read and write.

`OpenCombine.Publisher` to `Combine.Publisher`

Hey 👋,

Is there any way to turn an OpenCombine.Publisher into a Combine.Publisher "by default"?

I couldn't find it in the library and, although I realize it would not fit in the whole "1-to-1" porting vision for OpenCombine, and a custom implementation is trivial tbh, I feel like it might be a nice addition.

Making testing helpers public

Hello 👋

Due to lack of the testing SDK such in RxSwift with there RxTesting and RxBlocking for Combine or Open Combine. Would be great if you can put your testing stuff for public usage. After my short research, I've realized that TrackingSubscriber and friends could be a great replacement of RxBlocking.

0.9.0 release

It looks like enough good changes have accumulated since 0.8.0, @broadwaylamb would you mind tagging a new release? Or if this process is automated, would you mind sharing it, and I can do it myself if I may?

Combine interoperability

First of all, I'd like to say I'm glad I ran into this repo, as I myself had the exact same idea and started implementing the exact same thing! As you guys seem to have solid groundwork set up already, I think that my effort is better placed in this repo. I'll be picking up a few operators and getting to work on them asap.

One of the ideas I had for my implementation (I named it PreCombine :)), was that native implementation of Combine would be used whenever possible (iOS13+, macOS10.15+). I think that it's safe to say that Combine will outperform any 3rd-party implementation out there by a good margin, especially looking at some of the benchmarks out there, and that projects such as this one are only there to bridge the gap and allow us to use frameworks of the future today.

It'll be ages before many production-level apps can drop backward compatibility and support iOS13+ for an example, but many users will definitely be upgrading their device OS much before that, so having those devices run Combine instead would (almost surely) be a performance gain, and potentially avoid a few bugs sneaking in (hopefully that won't be the case regardless :)).

With that said, what I was thinking about implementation-wise was something along the lines of:

#if combineAvailable

import Combine

typealias Publisher = Combine.Publisher

#else

/// Declares that a type can transmit a sequence of values over time.
///
/// There are four kinds of messages:
///     subscription - A connection between `Publisher` and `Subscriber`.
///     value - An element in the sequence.
///     error - The sequence ended with an error (`.failure(e)`).
///     complete - The sequence ended successfully (`.finished`).
///
/// Both `.failure` and `.finished` are terminal messages.
///
/// You can summarize these possibilities with a regular expression:
///   value*(error|finished)?
///
/// Every `Publisher` must adhere to this contract.
public protocol Publisher {

    /// The kind of values published by this publisher.
    associatedtype Output

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    associatedtype Failure: Error

    /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
    ///
    /// - SeeAlso: `subscribe(_:)`
    /// - Parameters:
    ///     - subscriber: The subscriber to attach to this `Publisher`.
    ///                   once attached it can begin to receive values.
    func receive<S>(subscriber: S) where S: Subscriber, Self.Failure == S.Failure, Self.Output == S.Input

}

#endif

where combineAvailable would simply be a flag set like so (DEBUG flag is debatable):

#if canImport(Combine) && !DEBUG
let combineAvailable = true
#else
let combineAvailable = false
#endif

This could obviously be more elaborate, in order to avoid boilerplate code in each top-level object implementation, but I hope you get the point. What do you guys think?

On a side-note, is there Slack organization or anything alike set up somewhere? It could be helpful seeing that many things are still up in the air, and discussing those there could likely be more efficient and keep issue list clean(er).

Missing .merge and .throttle

Hi folks,

So I have the following:

public func mergedMovies(ids movieIDs: [String]) -> AnyPublisher<[Movie], APIError>{
        let movieIDs = Array(movieIDs.prefix(20))
        
        precondition(!movieIDs.isEmpty)

        let initialPublisher = movie(id: movieIDs[0])
        let remainder = Array(movieIDs.dropFirst())

        #if canImport(Combine)
        return remainder.reduce(initialPublisher) { combined, id in
          return combined
            .merge(with: movie(id: id))
            .eraseToAnyPublisher()
        }
        #else
        return remainder.reduce(initialPublisher) { combined, id in
          return combined
            .merge(with: movie(id: id))
            .eraseToAnyPublisher()
        }
        #endif

    }

I am getting two errors:

value of type 'Any' has no member 'eraseToAnyPublisher'
            .eraseToAnyPublisher()

and,

value of type 'AnyPublisher<[Movie], APIError>' has no member 'merge'
            .merge(with: movie(id: id))

Not sure where ocombine should reside to resolve that, if that is the fix.

Thanks

Still maintained?

Is this great repo still maintained? I see a bunch of PRs with missing operators that have not been merged for a long time...

Crash when deallocating a manually connected multicast before the Cancellable

What did you do?

Deallocated a manually connected multicast subject, before the Cancellable is released.

What did you expect to happen?

The deallocation to be handled cleanly.

What happened instead?

The running Application crashes with an EXC_BAD_INSTRUCTION due to an UnfairLock being recursively entered within SubjectSubscriber.swift.

Environment

OpenCombine version: 0.10.1
Xcode version: Any
Swift version: Any
Platform(s) running OpenCombine: iOS

Example code

        var originalSubject: PassthroughSubject<Void, Never>? = PassthroughSubject<Void, Never>()
        var newSubject: PassthroughSubject<Void, Never>? = PassthroughSubject<Void, Never>()
        var cancellable: Cancellable? = originalSubject?.multicast(subject: newSubject!).connect()

        newSubject = nil
        originalSubject = nil
        cancellable = nil

Notes

You can work around this by ensuring your Cancellable is released before the subjects.

the `*` in DispatchQueue scheduler's SchedulerTimeType gives wrong value

Hello,

the SchedulerTimeType of DispatchQueue has a Stride struct, I was wondering why the implementation of this returns Stride(magnitude: 0) not something like return Stride(.nanoseconds(lhs.magnitude * rhs.magnitude))

lets say for some reason you have the following func:

func multiply<S: Scheduler>( lhs: S.SchedulerTimeType.Stride,
                             rhs: S.SchedulerTimeType.Stride,
                             on scheduler: S) -> S.SchedulerTimeType.Stride {
  return lhs * rhs
}

print("<><> \(multiply(lhs: 1, rhs: 10, on: DispatchQueue.main))")

in Combine this prints: <><> Stride(magnitude: 10000000000)
in OpenCombine prints: <><> Stride(magnitude: 0)

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.