1

I am trying to create a Swift Package from a custom xcframework that I'm building for a client. The framework has a dependency on a couple of 3rd party frameworks. I've read about how even though binary frameworks don't support dependencies directly, there is way to do this with a 'wrapper' target, so this is what I came up with for Package.swift:

let package = Package(
    name: "SBFramework",
    platforms: [
            .iOS(.v16)
        ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "SBFramework",
            targets: ["SBFramework-Target"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
        .package(url: "https://github.com/fakeurl/NumbersKit.git", from: "1.0.0")
    ],
    targets: [
        .target(name: "SBFramework-Target",
                    dependencies: [
                        .target(name: "SBFramework", condition: .when(platforms: [.iOS])),
                        .product(name: "Algorithms", package: "swift-algorithms"),
                        .product(name: "NumbersKit", package: "NumbersKit")
                    ]
                ),
        .binaryTarget(name: "SBFramework", path: "SBFramework.xcframework")
    ]
)

This works, and I can add the package to my test project and import the framework and it links against the dependancies as well, and functionally it all works correctly.

The problem is that every time I run the app, it also shows these messages in the Xcode console:

objc[845]: Class _TtC14NumbersKitP33_0FE53357E470A64027C8F0CAF7B114C812BundleFinder is implemented in both /private/var/containers/Bundle/Application/EEE0C0A6-4FF5-44BC-B81A-F95401219D32/TestSBFrameworkImport.app/Frameworks/SBFramework.framework/SBFramework (0x100f4aaf0) and /private/var/containers/Bundle/Application/EEE0C0A6-4FF5-44BC-B81A-F95401219D32/TestSBFrameworkImport.app/TestSBFrameworkImport (0x10069b778). One of the two will be used. Which one is undefined.

There's multiple lines for different classes that show the "Class X is implemented in both [.../MyApp.app/Frameworks/MyFramework.framework/MyFramework/] and [.../MyApp.app/MyApp]". I'm not sure how to avoid this problem, and whether this could cause a problem down the line. The framework that is the basis for this Swift Package is linked against the two dependencies, because I wouldn't be able to build the framework without them. But they also need to be added to the app target (at least, and if I don't, I get a run-time crash when using the NumbersKit initializer.

Is there a good way to resolve this issue? I'm worried this will could be a problem for the client when they integrate it into their app, and the app is deployed to 1000s of devices.

Z S
  • 7,039
  • 12
  • 53
  • 105

1 Answers1

2

Yes, this is a real problem, and you should fix it. It can create very subtle errors if the implementation conflict.

I expect that you're linking NumbersKit both in your framework and also in your app. You must not do this. NumbersKit must be linked exactly one time. This is likely due to mixing SPM with direct linking rather than letting SPM do all the work. If your main app also needs NumbersKit, make sure to add it as a package, just like your framework is doing, so SPM can resolve a single copy to link.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • So the main SBFramework.xcframework needs both these 3rd-party frameworks, otherwise it won't build, so I have to include it there. If I just include the SBFramework.xcframework into a test app, it doesn't build because it can't find the "Algorithms" module. So I thought I could use SPM to add the dependencies to the client's project (I could be wrong here), and as far as I can tell, SPM does add the 3rd-party frameworks to the test app (with the Package.swift as above). So what do you think is the best way to approach this? – Z S Jun 01 '23 at 03:44
  • 1
    If at all possible I would work to remove the dependency. But see https://forums.swift.org/t/issue-with-third-party-dependencies-inside-a-xcframework-through-spm/41977/13. Generally the answer is the `@_implementationOnly` import discussed there. I've shipped products with that. It is the tool Apple intends us to use. The leading `_` is because it isn't sufficiently "hardened." If you use it incorrectly, it will crash at runtime. See also: https://stackoverflow.com/questions/71824920/add-dependencies-to-binary-targets-in-swift-package-manager/71936528#71936528 – Rob Napier Jun 01 '23 at 13:06
  • Thanks for the tip. Just to be clear, I would need to use @_implementationOnly for the 3rd parties in my own framework code, and then remove them from my framework's linked libraries, but still include it as part of the swift package? – Z S Jun 01 '23 at 15:30
  • That should be correct. – Rob Napier Jun 01 '23 at 16:11
  • Doing this, the framework doesn't build anymore, when I try to use xcodebuild archive : error: no such module 'NumbersKit' @_implementationOnly import NumbersKit – Z S Jun 01 '23 at 16:38
  • 1
    I think I got it to work by adding the dynamic framework for NumbersKit and also specifying "Do not embed" in the framework target. And in the package file, the dependency is to the NumbersKit-dynamic as well. That seems to resolve the issue. – Z S Jun 01 '23 at 18:36