2

working on backward compatibility for an macOS App that does not make use of SwiftUI but makes use of Combine Framework feature @Published declarations that is also not available in this particular targeted System and also not in this Swift version. The feature set that Combine offers is mimicked with OpenCombine see Github framework and seems to work. But @Published declarations are not available (yet).

As this Swift @Published directive can't be expressed in code how could i (hopefully) declare such feature instead?

My thought is i could make use of the @propertyWrapper feature instead and implement some extra function to make it work..

for discussion..
in the OpenCombine.ObservableObject aka ObservableObject is some property

import OpenCombine
class SomeObservableClass : OpenCombine.ObservableObject {
    @Published var highlighted = false //where @Published feature is not available
}

and on the other side in a NSView subclass

import OpenCombine
class SomeViewController: NSViewController {
    private var cancellables = Set<AnyCancellable>()
    
    override var representedObject: Any? {
        didSet {
            guard let observedObject = representedObject as? SomeObservableClass,
                  isViewLoaded else { return }
            observedObject.$highlighted
                .receive(on: DispatchQueue.main)
                .sink { [weak self] newValue in
                    self?.overlayView.needsDisplay = true
                }
                .store(in: &cancellables)
        }
    }
}

and the following looks promising to me, at least it would allow to mimic "@Published" directive manually in some way. The propertyWrapper i would place in #if .. #endif marks to be able to have the same codebase for Swift that supports Combine & @Published features then

@propertyWrapper
struct Published<T> {
    var wrappedValue: T {
        willSet { /*...*/ }
        didSet { /*...*/ }
    }
    init(wrappedValue: T) {
        
        // * -- maybe magic here! -- */
        
        /* or do more specific stuff.. ^^
        switch (wrappedValue) {
        //case is Optional<Any>.Type :
            //break
        case is Bool :
            break
        case is Error :
            break
        case is Optional<Error>.Type :
            break
        default: break
        }
        */
        self.wrappedValue = wrappedValue
    }

}

Reminder: Combine is an Apple Framework that is heavily used in the SwiftUI Framework but of course also available without. So targeting SwiftUI solutions are not really desired as thats the whole point to be able to use the "Combine-Design-pattern" without SwiftUI. link to Github issue i opened

Reminder2: we also have preprocessor directives like #if !canImport(Combine) or similar to limit where it would be allowed to be applied

EDIT1: very likely my issue can also be some troubleing typealias rule. Because
typealias Published = OpenCombine.Published fixed the propertywrapper issue, instead i am facing the following

observedObject.$highlighted
    .receive(on: DispatchQueue.main) //<---- Argument type 'DispatchQueue' does not conform to expected type 'Scheduler'
    .sink { [weak self] newValue in
        self?.overlayView.needsDisplay = true
    }
    .store(in: &cancellables)

likewise typealias Scheduler = OpenCombine.Scheduler did not fix that as i assumed.

Ol Sen
  • 3,163
  • 2
  • 21
  • 30
  • Funny enough, I was just about to open an issue on OpenCombine to ask if an implementation of `@Published` (and `ObservableObject`, `Binding`) should be added to the framework. It might be controversial, because these types are actually part of SwiftUI (for reasons I can't comprehend). I have no idea how `@Published` could be implemented (specifically, how it automatically wires itself up to the `objectWillChange` property of an `ObservableObject`), I wouldn't be surprised if it uses private compiler APIs. – Alexander Jun 17 '22 at 14:56
  • indeed good thought.. actually all missing directives in front of propertys could be done this way. My example (see Edit) does at least silence the warning on the "Published side", but of course does not solve the issue. So the code in "my" @propertyWrapper does just that.. – Ol Sen Jun 17 '22 at 15:26
  • 1
    Correction: `Published` and `ObservableObject` are from `Combine`. Only `Binding` is from `SwiftUI`. – Alexander Jun 17 '22 at 15:50
  • yep and linking SwiftUI in swiftprojects that do not support SwiftUI fully is troublesome as this leads to "missing Framework warnings" even tho Xcode 11.3.1 was delivered with the beta.. or i could not find some way to solve it – Ol Sen Jun 17 '22 at 15:51
  • 1
    I opened a forum topic for this: https://forums.swift.org/t/how-is-the-published-property-wrapper-implemented/58223 – Alexander Jun 17 '22 at 15:54
  • seems to be some typealias trouble that does not work as intended/expected with OpenCombine. Because i am now one step ahead and have missing protocol compliance of DispatchQue to Scheduler, but my typealias did not fix it the same way PS: i have all 4 partly libs included and imported – Ol Sen Jun 17 '22 at 20:09

1 Answers1

0

for the moment it seems indeed some @propertyWrapper is the solution to allow some
@Publishedvar somethingToObserve : Type declaration in Xcode 11.3.1 targeting macOS10.14 with Swift 5.. in front to do the magic.

But also some little change of DispatchQue.main to DispatchQue.main.ocombine

import OpenCombine
import OpenCombineDispatch

and declaration of the observable

class SomeObservable: OpenCombine.ObservableObject 
{
   //...
   @Published var someobservable: MyType
   //...
}

and on the "receiver" side..

typealias Published = OpenCombine.Published

// and somewhere below...

someObserveable.$highlighted
    .receive(on: DispatchQueue.main.ocombine) 
    .sink { [weak self] newValue in
        self?.overlayView.needsDisplay = true
    }
    .store(in: &cancellables)

compiles flawless.

Ol Sen
  • 3,163
  • 2
  • 21
  • 30