打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Avoiding Dependency Collisions in an iOS Library | Esri PDX

The Problem

Symbol collisions. How to deal with them? When creating a static library for iOS that uses open source libraries, you don't want tohinder your users' abilities to include those same open source libraries in their app. Without some foresight and care, your userswill be slapped with a bunch of "duplicate symbols" errors at link time. Providing a difficult experience integrating your libraryis one of the quickest ways to leave a sour taste in a user's mouth.

This article describes the solution that we used to address this problem, as well as some of the alternative solutions that we explored.

The Solution

The solution we chose was inspired by this answer on StackOverflow, but it tookme a while to get all the pieces in place. I wanted to write this article to illustrate the specifics in a bit more detail, so that your process will go more smoothly. The idea is to compileall external dependencies into the SDK but to add a prefix to all of the symbols at compile time. We accomplish this via a header file that definespreprocessor macros for every symbol in these dependencies. These macros map the calls to the original symbols to the "new" prefixed symbols. Thistricks our code into thinking it's using the regular version of the libraries, however the compiled .a file contains only the prefixed versions of these symbols.

This is done by first compiling the external libraries into a temporary .a file and running a shell script which uses nm (a command lineutility which lists symbols in compiled object files) to find all of the symbols in that .a file that are not from the OS/CocoaFrameworks. It then writes preprocessor macros into a header file that add our class prefix to the symbol names. Then, in our buildprocess, we make sure to include this generated header in all files that reference the external dependencies.

The Good

  • Simple for your users - As with the other renaming solutions, it Just Works?.
  • Simple for you - After some initial time spent setting it up (which, in fairness, isn't exactly "simple") it also Just Works?,including after upgrading versions of your dependencies.
  • Relatively fool-proof - It is not completely fool-proof; if you do find that the script missed something you can goadd a special case for that thing and it will stay solved until that thing changes.

The Bad

  • Increases app size - Doubles the app size impact of any libraries that are used by both your library and the user's app. However this is also true if you rename them manually.
  • Adds complexity- Your build process gets some complexity added to it which can be a maintainability problem,but ultimately I find the tradeoff in minimized support problems makes this worthwhile.
  • Increases your build times - Because you're basically building the external dependencies twice, your build times will increase slightly.

Here's the step-by-step version of how to set accomplish this:

  1. Add a new target to your project for the external dependencies, I called mine ext. This target will build the temporary .a file andrun the script which generates the namespaced header file.
  2. In the target's Build Phases tab in Xcode:
  3. Add all of the dependencies' source files to the Compile Sources phase.
  4. Add any libraries needed by the dependencies to the Link Binary With Libraries phase.
  5. Add a new script phase (Editor > Add Build Phase > Add Run Script Build Phase). I prefer to put the script contents in a file and referto that in the Shell text box: /bin/sh Scripts/generate_namespace_header.sh but you can also just put the following script in the textarea below that. It's up to you where you want to put it, but here is the script we use. It is slightly modified fromthis one. You'll need to edit the header and prefix variables at the top of that script. You'll also probably want toadd the NamespacedDependencies.h file to your xcodeproject. I put it in an ext subfolder along with the external dependencies' sources.
  6. If your dependency has any special build properties or compiler flags that need to be set, set those for this target.
  7. In any file that you use the dependencies, add an #import "NamespacedDependencies.h" before the imports of their header(s). I chose to dothis in the precompiled header.
  8. In your library's target's Build Phases tab add the ext target as a dependency. This makes sure the external dependencies lib is compiledand the header is generated before compiling your lib.

To verify everything is set up and working properly build your .a file and then run nm -a lib.a and you should see you external dependencies'symbols all with the prefix you specified in the shell script.

Alternative Solutions

There are several other options for a library developer to avoid their users encountering these errors. Let's go over a few of them andsee why we went with the above solution.

Declare External Dependencies

You could declare your third party dependencies and require users of your library to also link to theselibraries. This is certainly the simplest way of dealing with the issue for you, but not necessarily for your users. Thingslike CocoaPods can make this easier, but doesn't solve the other problems with this method.

The Good

  • Simple for you - You just declare that in order for anybody to use your library they must first get and link to these otherlibraries.
  • Minimizes built app size - Since your library and the app your user is building will link to the same library object files,there is no duplication of symbols in the end product.

The Bad

  • Complicated for your users - If your users aren't already using the same libraries then this is an extra step for them to getup and running with your library, which is almost never a Good Thing?. Again, CocoaPods alleviates the complicationfor your users, but can't address the next Bad, due to Objective-c's lack of namespacing or packaging.
  • Forces your users to use the same versions of the libraries as you are using - If your users are already using the samelibraries, they have to be sure they are using a version of these libraries that is compatible with the version you're using.So now you are dictating what external libraries users of your library are using, which can be very frustrating for them and amaintenance/support nightmare for you.

Compile External Dependencies

You can include your external dependencies in your library's compiled output and let any users who are using the same libraries worryabout renaming these libraries if they are also using them in their project.

The Good

  • Simple for you - You just include the source for your dependencies in the Compile Sources build phase and you're done.
  • Simple for your users who don't the external libraries - As far as these users are concerned, all they have to do is link to yourlibrary and it Just Works?.

The Bad

  • Makes your library basically unusable for users who are also using those external libraries - These users could rename all of thesymbols in their version of the external libraries... but forcing your users to do this is just mean, and will make users reconsider ifit's worth using your library (or the external libraries) at all. And that's just not cool.

Manually Rename All of the Symbols

The alternative to just compiling the dependencies in just as they are is to first rename the symbols in all of your external dependenciesyourself, then compile them into your library.

The Good

  • Simple for your users - Just like compiling them without renaming them, as far as your users are concerned, it Just Works?,and this time it works for your users regardless of whether they are using the same libraries or not.
  • (Mostly) simple for you - A simple Find & Replace in your IDE for the external libraries' prefix should take care of it.

The Bad

  • Manually renaming the symbols could be error-prone - As the good above says, a Find & Replace should catch everything. Butsometimes it doesn't, and then you're back at square one.
  • Updating your external libraries becomes a hassle - You have to do this manual renaming process anytime you want to update these librariesand that reintroduces the possible error vectors.
  • Doubles the app size impact of any libraries that are used by both your library and the user's app - Since the compiler sees yourrenamed symbols as completely seperate objects, if a user is using the same version of the same library as you are, they are effectivelyincluding the library twice. This sounds bad, but it's usually quite minimal, and definitely worth what you gain by doing so.
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
1.16. Building a Static Library with GNU Make
Experimental Plugin User Guide(From Android Tools Project Site) | 子勰的博客
Microsoft Enterprise Library 5.0 系列教程(十) Configuration Application Block
NuGet
maven指定构建的编码格式
maven学习笔记
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服