How to conditionally use Combine @Published when minimum deployment target is set to iOS 12.2?

Viewed 663

I have an app with minimum deployment target of iOS 12.0. There are parts written in SwiftUI and Combine. All methods and types using the two libraries are marked with @available(iOS 13.0, *). The app has been running in this mixed setup since January without problems.

To take advantage of ABI stability and reduce the app size I want to set the new minimum deployment target to iOS 12.2.

When I do that the app crashes on launch (iOS 12.4 simulator) with the following printed to the console:

dyld: lazy symbol binding failed: can't resolve symbol _$s7Combine9PublishedVMa in /Users/YYYYYYYYY/Library/Developer/CoreSimulator/Devices/UUD-UUD-UUD-UUD/data/Containers/Bundle/Application/UUD/XXXXXXX.app/XXXXXXX because dependent dylib #29 could not be loaded dyld: can't resolve symbol _$s7Combine9PublishedVMa in /Users/YYYYYYYYY/Library/Developer/CoreSimulator/Devices/UUD-UUD-UUD-UUD/data/Containers/Bundle/Application/UUD-UUD-UUD-UUD/XXXXXXXapp/XXXXXXX because dependent dylib #29 could not be loaded

The app also crashes when attempting to launch on iPhone running iOS 12.4.

If I remove all @Published from the code but leave all other Combine and SwiftUI related bits the app can be used on iOS 12.

Even though all the classes containing @Published are marked with @available(iOS 13.0, *) I have tried wrapping them additionally in #if canImport(Combine) or applying #if canImport(Combine) to each @Published variable. This does not help.

I know I could change

@Published var name = ""

to

var name = "" {
    willSet {
        objectWillChange.send()
    }
}

But that seems like a nasty workaround.

How can I increase deployment target to iOS 12.2 and keep using @Published when device is running iOS 13.0 or higher?

2 Answers

You need to weakly link the Combine (and SwiftUI if you're also using that) framework(s) to your project. You can achieve this using the OTHER_LDFLAGS build setting.

OTHER_LDFLAGS = -weak_framework Combine -weak_framework SwiftUI

You don't actually need canImport statements, you only need the framework to be weakly linked. You of course still need the @available annotation on all types that use Combine.

You cannot use the property wrapper @Published in a deployment target lower than iOS 13. The only way to do this is as you described above.

In your class (which should conform to the ObservableObject protocol), instead of:

@Published var name = ""

You have to have:

var name = "" {
    willSet {
        if #available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) {
            self.objectWillChange.send()
        }
    }
}

You may also need to explicitly add the objectWillChange declaration in your class:

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public lazy var objectWillChange = ObservableObjectPublisher()

Related