Dynamic framework / module support

202 views
Skip to first unread message

Kevin Lord

unread,
Feb 13, 2015, 11:16:30 AM2/13/15
to mobile-c...@googlegroups.com
Hi,

I'm trying to compile a custom framework module written in Swift that relies on Couchbase Lite as a dependency. I've been having a really tough time finding any way to integrate it with any maintainability and was hoping you might have a suggestion.

The main problem appears to be twofold:
- Apple dropped support for using a bridging header within a framework target
- You cannot import a non-modular header into the umbrella header for the framework

Are there any plans to add proper CocoaPods support instead of the vendored framework method currently used? I think this will likely become a bigger issue soon when the official 0.36.0 release comes out that supports Swift and modules / frameworks, as I believe that if a single pod must be built as a dynamic framework, they will all be built that way.

Carthage is another dependency manager gaining some traction, and though it seems to build the OS X targets fine, it doesn't build the iOS targets as they are defined as a library / bundle instead of as a framework like OS X.

It seems the solution is to add an additional iOS framework target that defines a module, however I'm still trying to wrap my head around all of this.

Any thoughts?

Kevin

Jens Alfke

unread,
Feb 13, 2015, 11:52:51 AM2/13/15
to mobile-c...@googlegroups.com

On Feb 13, 2015, at 8:16 AM, Kevin Lord <lor...@gmail.com> wrote:

The main problem appears to be twofold:
- Apple dropped support for using a bridging header within a framework target
- You cannot import a non-modular header into the umbrella header for the framework

Could you explain this in more detail? (I haven’t used Swift much, yet.)

Are there any plans to add proper CocoaPods support instead of the vendored framework method currently used?

I don’t think so. We prefer to build Couchbase Lite ourselves and test the actual bits we build, then release exactly those bits. That way we know what people are getting. The CBL/iOS project and repo structure are fairly complex and I really don’t want to be in a world where I have to deal with every developer having to (indirectly) check out and build it themselves.

It seems the solution is to add an additional iOS framework target that defines a module, however I'm still trying to wrap my head around all of this.

The root of the problem is that for some reason Xcode has really half-assed support for distributing iOS libraries in binary form. Unlike OS X there’s no official support for frameworks, although you can assemble your own pseudo-framework by putting a static library inside a “.framework” directory, which is what we do. 

Worse, Apple never addressed the problem of creating libraries that can be used for both device and simulator builds. First, you can’t create both libraries in one build action: you have to build, then manually switch the scheme’s destination between device and simulator, then build again. Second, you end up with two copies of the library (one for each platform) that both have to be added to the developer’s app target, and then (IIRC) one or the other of them will trigger link warnings on any build because they’re for the other architecture. There is a half-assed solution for this involving a very nasty build script that recursively invokes xcodebuild to build the ‘other’ architecture, then uses lipo to mush both builds into a single fat .a file.

Things look like they’ll be better in the future because iOS 8 has finally added support for dynamic library loading, so (I think) we’ll be able to build as a real dylib-based framework. (I say ‘I think’ because I haven’t tried this yet.) But before we can do that we have to wait until our app developers don’t need iOS 7 support any more, which is probably a few years away.

—Jens

Kevin Lord

unread,
Feb 13, 2015, 1:42:36 PM2/13/15
to mobile-c...@googlegroups.com


On Friday, February 13, 2015 at 12:52:51 PM UTC-4, Jens Alfke wrote:

On Feb 13, 2015, at 8:16 AM, Kevin Lord <lor...@gmail.com> wrote:

The main problem appears to be twofold:
- Apple dropped support for using a bridging header within a framework target
- You cannot import a non-modular header into the umbrella header for the framework

Could you explain this in more detail? (I haven’t used Swift much, yet.)

So the idea is that in a normal application target you can make any Obj-C code in the target accessible from Swift by adding the relevant headers to the bridging header for the target, which is a fairly standard header that's simply specified as a bridging header in the build config. Unfortunately, for whatever reason, in one of the later Xcode 6 betas Apple dropped this idea for the new iOS dynamic framework targets. Instead, there is an umbrella header for the framework that is used to export all public API, but is also used as a bridging header for the framework. However, unlike the standard bridging header, you can only import headers that are marked public and part of a Clang module. At least that's how I understand it. 

Here's another case of someone running into an issue with a similar library / setup (FMDB w/sqlite): 


Also, here's a little more background from Cocoapods on all of this:



Are there any plans to add proper CocoaPods support instead of the vendored framework method currently used?

I don’t think so. We prefer to build Couchbase Lite ourselves and test the actual bits we build, then release exactly those bits. That way we know what people are getting. The CBL/iOS project and repo structure are fairly complex and I really don’t want to be in a world where I have to deal with every developer having to (indirectly) check out and build it themselves.

It seems the solution is to add an additional iOS framework target that defines a module, however I'm still trying to wrap my head around all of this.

The root of the problem is that for some reason Xcode has really half-assed support for distributing iOS libraries in binary form. Unlike OS X there’s no official support for frameworks, although you can assemble your own pseudo-framework by putting a static library inside a “.framework” directory, which is what we do. 

Worse, Apple never addressed the problem of creating libraries that can be used for both device and simulator builds. First, you can’t create both libraries in one build action: you have to build, then manually switch the scheme’s destination between device and simulator, then build again. Second, you end up with two copies of the library (one for each platform) that both have to be added to the developer’s app target, and then (IIRC) one or the other of them will trigger link warnings on any build because they’re for the other architecture. There is a half-assed solution for this involving a very nasty build script that recursively invokes xcodebuild to build the ‘other’ architecture, then uses lipo to mush both builds into a single fat .a file.

Things look like they’ll be better in the future because iOS 8 has finally added support for dynamic library loading, so (I think) we’ll be able to build as a real dylib-based framework. (I say ‘I think’ because I haven’t tried this yet.) But before we can do that we have to wait until our app developers don’t need iOS 7 support any more, which is probably a few years away.

—Jens

Yeah, it is kind of a mess. I definitely understand your points, however I have a feeling that there will need to be some kind of solution much sooner than when iOS 7 support can be dropped. Apple has really been pushing developers to start moving shared code into these dynamic frameworks, especially with the advent of extensions. We're already only supporting iOS 8+ on any new apps being developed, partly for this reason. One of the most obvious first things to put into a framework is your model level code so it can be easily shared between app and extension, but this is currently exceedingly difficult with the existing build targets in CBL. It seems to me that one potential solution is to simply create an additional dynamic framework build target with a deployment target of iOS 8+ with 'Defines Module' set to YES, that those developers can use, while other users can use the existing targets. Might this be an option? I'd really like to avoid having to fork for our internal use, especially as I imagine other developers may soon face the same dilemma. :/


Jens Alfke

unread,
Feb 13, 2015, 2:23:11 PM2/13/15
to mobile-c...@googlegroups.com

On Feb 13, 2015, at 10:42 AM, Kevin Lord <lor...@gmail.com> wrote:

It seems to me that one potential solution is to simply create an additional dynamic framework build target with a deployment target of iOS 8+ with 'Defines Module' set to YES, that those developers can use, while other users can use the existing targets. Might this be an option?

Totally. I hope I can say ‘patches welcome!’ without sounding snarky — I’d love to have this as an option, but I don’t know when we’d get around to it ourselves.

If you want to tackle this, take a look at the “CBL iOS Library” target, which is what builds the static library for iOS. That will show you which sources and build settings you need. The “CBL Mac” target is what builds the Mac OS framework, so that might be useful for dylib-specific things; for example, it specifies a .exp file that lists the symbols to export.

(Oh, also, check whether we have an issue covering this in the Github tracker. If we don’t, please add one.)

—Jens

PS: Since you’re using Swift, you might want to look at commit 041e3b0, pushed to master yesterday. It adds the new “nullable” annotations to the public @interfaces, so if you use Xcode 6.3 you’ll get cleaner bridged Swift APIs with fewer “?” and “!”s.

Kevin Lord

unread,
Feb 13, 2015, 2:42:47 PM2/13/15
to mobile-c...@googlegroups.com
Awesome, not snarky at all. Happy to tackle it. I'll open an issue now and should hopefully have a PR in the next few days.

I'll check out that commit as well, sounds great.

Thanks!
Reply all
Reply to author
Forward
0 new messages