What does it mean to “weak-link” a framework?

Important note:
This answer was written before iOS 8 was announced. While the technical details still apply to system frameworks, it is now possible to build your own, dynamically linked frameworks that ship within your app bundle. There are restrictions, e.g., only an app and its extensions can link to the same instance of an embedded framework, but the fact remains that custom, dynamically linked framework are possible since iOS 8. If you want to learn more, refer to this guide (Using an Embedded Framework to Share Code) and WWDC 2014 session 416, Building Modern Frameworks.

Original Answer:
None of the (platform) frameworks is really “included in the bundle“. Instead, your app has a reference (“link“) to a framework once you add it to the “Link Binary with Library” build phase. The frameworks are pre-installed on the devices. When you run an app, all the app’s framework references are resolved by the dynamic linker (on the device), which means the framework code is loaded so your app can use it.

Some frameworks may not be available on all the devices you intend to support, e.g., PassKit was introduced in iOS 6. If you run an app that links against PassKit on an iOS 5 device, it crashes right after launch, because the dynamic linker cannot find the framework on the device. However, if you weak-link PassKit, the dynamic linker will set all the framework’s symbols to nil, if the framework could not be found. This prevents the app from crashing and you can check for the symbols’ availability at runtime, e.g.:

if ([PKPass class]) {
  // Class is available - use it
  PKPass *pass = [[PKPass alloc] init];
}

[PKPass class] is safe to use on all devices/systems since the PKPass class symbol will be nil on older systems, and messaging nil is not a problem in Objective-C.

More on Weak-Linking: Apple Documentation

To really answer your question:

Does that mean the framework is only included in the bundle when it is imported somewhere?

No. The framework will always be linked from the app. Only when the framework is not found on the actual device your app is running on, then the framework will not be loaded.

One solution would be to have separate targets for Debug and App Store Builds. An alternative is to not use the built-in “Link Binary with Library” build phase from Xcode, but to link the Debug frameworks via linker options. These can be specified for each configuration (Debug/Release/…) separately, like so:

Adding framework via linker flags

If you’d want to weak-link it, use -weak_framework PassKit (PassKit, of course, being just an example here… insert the name of your framework) instead. If your Debug framework is not in one of the default framework directories, you might have to provide a full path or modify the Frameworks Search Path. Plus, you should probably use macros to make sure none of the code using the debug framework(s) makes it to the App Store build.

Edit: Another option since Xcode 5 is to use @import <FrameworkName>;. This way, you can leave your “Link Binary…” phase empty and trigger the linking of frameworks in code. You can then use macros such as DEBUG to make sure some frameworks aren’t used for App Store builds. There’s an excellent answer regarding @import.

Leave a Comment