Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libxpc-shim

If your Swift code contains XPC_TYPE_DICTIONARY — or any other XPC constant — and you build it with Xcode 26 or 27, your app is terminated at launch on iOS 15. The build produces no warning, and the crash report does not name your code.

This package is twenty lines of C that avoid that. The code is the small part; the explanation below is the reason the repository exists.

.package(url: "https://github.com/owngoal-dev/libxpc-shim", from: "1.0.0")
import XPC
import XPCShim

guard xpc_get_type(message) == XPCConstants.typeDictionary else {  }
xpc_array_set_value(array, XPCConstants.arrayAppend, entry)
if message === XPCConstants.errorConnectionInvalid {  }

The failure

An app with IPHONEOS_DEPLOYMENT_TARGET = 15.0, built clean, installed on an iPhone running iOS 15.0.2, closes the instant it opens. The report is a dyld termination — six frames, all in dyld, nothing of yours:

"termination" : {
  "namespace" : "DYLD",
  "indicator" : "Library missing",
  "reasons" : [
    "Library not loaded: /usr/lib/swift/libswiftXPC.dylib",
    "Referenced from: /…/YourApp.app/YourApp",
    "Reason: tried: '/usr/lib/swift/libswiftXPC.dylib' (no such file), …"
  ]
}

Why

Recent SDKs ship a Swift overlay for XPCusr/lib/swift/XPC.swiftmodule and libswiftXPC.tbd. Older SDKs had none: import XPC resolved to the plain clang module over <xpc/xpc.h> and linked nothing, because every xpc_* function lives in libSystem.

With the overlay present, three things line up:

  1. import XPC now loads a Swift module whose flags include -autolink-force-load, so the object file asks for -lswiftXPC.
  2. XPC_TYPE_DICTIONARY, XPC_ARRAY_APPEND, XPC_ERROR_CONNECTION_INVALID and the rest are, in C, macros over libSystem globals (&_xpc_type_dictionary, present since iOS 8). In Swift they now resolve to accessor functions exported by the overlay dylib.
  3. libswiftXPC.tbd carries no back-deployment metadata, and the overlay annotates those constants @available(iOS 8.0). So the linker has no reason to weak-link the library, and emits a required load command.

/usr/lib/swift/libswiftXPC.dylib first shipped in iOS 16. On iOS 15 dyld cannot build the process, and kills it before main.

Nothing about this is visible while you work: the compiler accepts the code, the linker accepts the library, xcodebuild is green, and the App Store or your APT repository takes the build. The first person to find out is a user.

Check your own binary

otool -L YourApp.app/YourApp | grep swiftXPC
output meaning
…/libswiftXPC.dylib (…, weak) fine — dyld tolerates its absence
…/libswiftXPC.dylib (…) without weak your app cannot launch below iOS 16
nothing you never imported XPC

And to see exactly which symbols pulled it in:

nm -mu YourApp.app/YourApp | grep libswiftXPC
(undefined) external _$s3XPC0A16_TYPE_DICTIONARYs13OpaquePointerVvg (from libswiftXPC)
(undefined) external _$s3XPC0A13_ARRAY_APPENDSivg                   (from libswiftXPC)
(undefined) weak external __swift_FORCE_LOAD_$_swiftXPC             (from libswiftXPC)

After the fix, only the last line remains — and that one is weak, so the library becomes optional again:

(undefined) weak external __swift_FORCE_LOAD_$_swiftXPC (from libswiftXPC)

That is the whole mechanism: with no overlay symbol in use, the linker weak-links the dylib by itself. No linker flag is involved, and none helps.

What does not work

  • Removing import XPC. The import is harmless; the symbols are not. And a C header of your own that includes <xpc/xpc.h> still autolinks the overlay, because Swift loads the overlay for a clang module it imports transitively. Verified with swiftc -emit-ir: -lswiftXPC appears either way.
  • Weak-linking by hand. You can force it, but the five accessor symbols are then null and calling one is a crash at first use rather than at launch. The fix is to stop referencing them, not to make the failure later.
  • Branching on #available. Two paths mean the path the old OS takes is the one nobody ever tests. Use the C constants on every version; then iOS 26 and iOS 15 execute the same code.
  • Raising the deployment target to 16. That works, and it is a decision about your users rather than about XPC. Make it deliberately, not because a Swift overlay you never asked for shipped in an SDK.

What this package is

CXPCShim exposes each constant as a static inline C function; XPCShim wraps them in XPCConstants. Nothing is a wrapper around behaviour — each one compiles to a load of the same global the C macro names. There is no library to link, no runtime cost, and no behaviour of ours between you and XPC.

Copying Sources/CXPCShim/include/CXPCShim.h into your own project instead of adding a dependency is a completely reasonable thing to do; that is why it is one self-contained header.

Scope

xpc_type_t constants, XPC_BOOL_TRUE / XPC_BOOL_FALSE, XPC_ARRAY_APPEND, the three connection errors, and the two dictionary keys we needed. Pull requests adding a constant are welcome; pull requests adding behaviour are not — the value of this package is that there is nothing in it.

Related

The same trap has three other shapes, all of which raise your deployment floor without a warning:

  • a weak-imported symbol newer than your floor is null at runtime: nm -m <binary> | grep 'weak external', and each one has to be guarded;
  • an embedded framework built for a later OS: vtool -show-build … | grep minos;
  • an SF Symbol newer than your floor: UIImage(systemName:) returns nil and the control simply draws nothing. The availability table is on every Mac at /System/Library/CoreServices/CoreGlyphs.bundle/Contents/Resources/name_availability.plist.

platformize-app-ios carries scripts for all four checks.

Found while shipping Fila, a root file manager for jailbroken iOS, whose 0.1.6 release died at launch on two users' iOS 15 devices for exactly this reason.

License

MIT. See LICENSE.

About

Why your app dies at launch on iOS 15 when Swift names an XPC constant, and twenty lines of C that stop it

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages