Intro
I'm trying to determine whether Xcode's new Mergeable Libraries feature can remove duplicated symbols in a scenario where an app links to two dynamic frameworks which both link to the same static library, as follows:
|--App
|--DynamicFramework1
|--StaticLibrary
|--DynamicFramework2
|--StaticLibrary <- the same library that DynamicFramework1 links to
Project setup
Here are some details about the app's structure:
The static library is named StaticLibrary. It:
Contains a single Objective-C class named SLClass1
The first dynamic framework target is named DynamicFramework1. It:
Contains a single Objective-C class named DF1Class1 that calls into SLClass1; and
Has a MERGEABLE_LIBRARY build setting value of YES.
The second dynamic framework target is named DynamicFramework2. It:
Contains a single Objective-C class named DF2Class1 that calls into SLClass1; and
Has a MERGEABLE_LIBRARY build setting value of YES.
The app target is named App. It:
Contains a single Swift class named ViewController that calls into the DF1Class1 and DF2Class1 classes; and
Has a MERGED_BINARY_TYPE build setting value of Automatic. (1)
I've created a minimal Xcode project here that has the above structure.
Expected result
The merged app binary, when built it in Release mode, contains the symbols for DF1Class1, DF2Class1 and SLClass1 directly within it and it contains only one instance of each of these symbols (i.e. no duplicates).
I am basing this expectation on the Benefits of mergeable libraries section of the Meet mergeable libraries talk from WWDC 2023 where the speaker said:
When merging, the linker can de-duplicate content, such as strings, across all libraries. For instance, it removes redundant symbol references, Objective-C selectors, and objc_msgsend stubs.
Actual result
The merged app binary, when built it in Release mode, contains the symbols for DF1Class1, DF2Class1 and SLClass1 directly within it but it contains two instances of SLClass1's symbols (i.e. it contains duplicates). (2)
My Question
Have I missed something in the app's build settings or have I misunderstood the capability of Mergeable Libraries? Can I get the Xcode linker to deduplicate symbols in the app's merged binary?
Footnotes
I have tried with a MERGED_BINARY_TYPE build setting value of Manual also but the result is the same.
I have validated this by running the nm command on the app's executable file and inspecting the output.
Post
Replies
Boosts
Views
Activity
I'm having a problem defining the Package.swift file for my binary closed-source dynamic framework which depends on a number of non-binary open-source dynamic frameworks.
My Package.swift file looks like this:
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
import Foundation
let package = Package(
name: "MyLibrary",
platforms: [
.iOS(.v12)
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibraryWrapper"]
),
],
dependencies: [
.package(url: "<dependency-url>", branch: "master"),
],
targets: [
.target(
name: "MyLibraryWrapper",
dependencies: [
.target(name: "MyLibrary"),
.product(name: "<dependency-product-name>", package: "<dependency-package-name>")
],
path: "MyLibraryWrapper"
),
.binaryTarget(
name: "MyLibrary",
url: "<url-to-xcframework-zip>",
checksum: "<checksum>"
)
]
)
The package builds successfully. However, when I add it to any app, I get the following runtime error:
Library not loaded: @rpath/<dependency-product-name>.framework
The only workaround I know is to add type: .dynamic to the library(name:type:targets:) declaration in the Package.swift file of each of my framework's dependencies. This requires me to fork each of my framework's dependencies and maintain the forks. That's not something I'm keen on.
Is there is a better, simpler solution to this problem?
Notes
I need to declare the MyLibraryWrapper target in my Package.swift file because the binaryTarget(name:url:checksum:) method does not offer a dependencies parameter.
The MyLibraryWrapper directory which the MyLibraryWrapper target declares as its path contains a single empty source file and nothing else.
The non-binary dynamic frameworks which my binary dynamic framework depends on do not specify an explicit type value in their Package.swift files based on Apple's recommendation in the library(name:type:targets:) method documentation: "It’s recommended that you don’t explicitly declare the type of library".