说说iOS中静态库的开发

好久没整理文章了,最近事儿很多,其中一个重要的任务就是SDK的开发。所谓SDK(software development kit),无非就是以包的形式提供的一个开发库。本篇文章就整理下iOS中SDK库开发中的一些事儿。

0. 对库的粗浅理解

无论是操作系统层面的开发,还是上层应用的开发,库这个东西都是少不了的。我没有对库这个概念去找比较精准的定义,不过无论哪个平台,一般应该具有如下特征:

  • 非最终的应用,一般都是给应用提供通用功能服务的,非独立运行的程序集合
  • 一般都是编译过的,方便连接使用

既然库是提供服务的,那么应用程序一定是需要库、依赖于库的,需要和库整合到一起运行,我们可以粗略理解这个过程为“连接”。而对库的连接,主要有两大类方式:静态连接、动态连接。

静态连接,一般是指在创建应用程序的时候,将库集成进去,这样做的好处就是应用程序包自身可以独立运行,而不好的地方就是包会略显臃肿,库不能共享。

动态连接,创建应用的时候只约定好与库之间的调用关系,而不彻底将库包集成进应用。这样在应用运行时,需要运行环境中提供库,并且连接装载。优劣与静态库相反,动态链接库需要库环境,但由于本身不集成库内容,会比较小,同时也为和其他应用共享库的使用提供了可能。

1. 苹果的库

大家都知道苹果的产品很酷很优雅,设计完美,苹果平台上的应用UI都很漂亮,这自然离不开苹果提供的图形界面库。

当我们打开Xcode,创建应用,打开工程文件中的Build Phases,里面的Link binary with Libraries为我们提供了各个方面的系统库,有的以.dylib的形式提供,有的以Framework的方式给出。

Framework实际上就是苹果平台特有的对二进制库的一种包装方式,除了代码编译后的包,还有头文件等内容。关于具体的介绍,可参见官方文档,其中给出了Framework的文件包结构等描述:

https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html

从连接方式上来看,动态连接和静态连接在Mac OSX上都对开发者开放,而在iOS上目前已知的只支持静态方式的连接库开发,没有找到可以支持动态库的官方说明,动态库只能使用系统提供的。

2. iOS中库的开发

由于移动设备软硬件资源有限,苹果对iOS上库的开发有了严格限制。

在iOS平台上,打开Xcode,选择工程或者Target类型,默认情况下我们可以看到的只有Cocoa Touch Static Library。这个实际上就是我们前面提到的静态链接库,编译出来的Product就是一个.a形式的程序包。

在静态的.a库使用时至少需要注意以下两点:

  • product仅仅是一个.a包,使用时需要另外配合.h头文件
  • 静态的.a库是会根据工程和target配置,使用特定的处理器架构指令集编译打包的,一般不会同时支持iOS设备的arm指令集和模拟器的i386

由于.a的方式是Xcode默认支持的,所以使用也很多。但从发布和使用方便的方面来看,Framework的方式则是更优的选择。而Xcode本身并没有提供iOS平台上的工程模板支持,第三方的一个比较优秀的工具就是iOS Universal Framework。

3. iOS Universal Framework

这是非苹果官方的一个制作Framework库的第三方工具,目前已经出了第8个版本(mk8)。这个工具提供了Fake Framework和Real Framework两类,其中后者需要执行一个脚本安装,会在Xcode的默认配置上做一些改动,此后工程和Target模板中就会多一个Framework的选择。Real Framework的开发和引入使用都需要执行shell脚本安装iOS Universal Framework,但这些通常来说都不是问题,而且由于Real Framework是”Real的Framework”,避免掉了Fake Framework的一些问题,更多的被接受使用。

如上面所述,iOS Universal Framework解决了如下问题,方便发布和使用:

  • 会根据可见级别配置,将必要的头文件自动添加到Framework中,开发给应用者使用
  • 既然叫“Universal”,自然是将真机的arm指令集和模拟器的i386整合到了一起,一个包打出来,方便在各个平台上调试和使用
  • 还提供了embedded framework方式,可以把nib、图片等资源文件一起集成

打开Xcode中使用iOS Universal Framework的库工程,其Build Phases中会有一个或者几个Run Script,这实际上就是这个工具在Build过程中所做的事情。

其中在老版本(mk7)中,前前后后使用了3个shell脚本,而在最新的mk8中,只在最后执行了一个python的脚本,感兴趣的可以看看其中的内容。

https://github.com/kstenerud/iOS-Universal-Framework

再稍微说一句,就是最新的mk8版本,打包的时候需要配好architecture,并且用Archive来打包,否则Build出来的包依然是处理器架构指令集支持不全的。

比如,我的配置是

Architectures: armv7, armv7s

Valid Architectures: armv7, armv7s, i386

4. Umbrella库和符号冲突

如果你已经看过上面苹果官方介绍Framework的文档链接内容,你应该会看到其中提到了一个Umbrella Framework的概念。这里我顺便造了一个词“Umbrella库”。其实大概的意思就是两个库存在包含关系,进而形成了库的包含层次关系。

其实这种库的封包形式或者依赖关系在其他开发平台也常见,比如在Java开发中使用Maven管理依赖关系。但这种包含会带来冲突问题。比如Maven中有不同版本但却同名的包,这时Maven提供的是一种仲裁机制,提示使用哪个版本的。

那么在没有命名空间概念,只靠前缀来进行类区分的iOS平台开发中,出现这种冲突的概率似乎显得更大。一个常见的问题就是“duplicate symbol”。

这个问题并不是一定有包含关系的.a或者.framework造成的,如果在构建一个.a的源码中使用了一个第三方的源码一起编译,而在使用.a的工程中或者另一个.a中也用到了这套源码,那么duplicate symbol也是必然的。

由于.a和.framework的构建中也可以集成引入其他的.a库,那么使得这种duplicate symbol错误的可能出现场景变得更多,而且解决起来更为麻烦。

所以,库开发比较有经验的“燕子姐姐”给了一个规则,就是构建framework的时候,只要所需要的头文件能找到就好,使.a库不要再link的libraries中出现。

5. 最后稍微说说处理器架构相关

iOS设备目前都是采用ARM(Advanced RISC Machine)指令集架构的处理器,但不同代的产品对应不同的版本。

大概一个对应列表如下:

  • armv6:iPhone 2G/3G,iPod 1G/2G
  • armv7:iPhone 3GS/4/4s,iPod 3G/4G,iPad 1G/2G/3G
  • armv7s:iPhone5
  • arm64: iPhone5s

我们用Xcode5默认的是支持armv7和armv7s,支持arm64的iPhone5s是兼容和可以使用这个配置打包出来的应用的。

如果想把不同指令集的.a包合并起来,可以使用lipo工具。

就先写到这。

相关文章:

此条目发表在 iOS 分类目录,贴了 , , , , , , 标签。将固定链接加入收藏夹。

说说iOS中静态库的开发》有 19 条评论

  1. 小飞侠 说:

    写的很好哈,我最近也在看sdk的制作。想请教一个Universal framework的问题,就是我感觉使用真假框架好像没有什么区别呢。该项目的作者在首页的介绍中”Libraries being linked or not being linked into the final framework“这部分写道,真框架不会链接我们所使用的静态库到最终生成的框架中,而假框架会,但是我实际测试时,无论真假框架,如果在Link Binary With Libraries中添加了使用的静态库,则最终生成的框架都会把静态库包含进来。不知道实际工作中哪一种方式更好一点呢,是把用到的静态库包含在我们的框架中(可能会和用户使用的其他第三方框架重定义冲突),还是把静态库单独拿出来,和框架一起给用户呢。

    • 1. 你说的框架是指.framework文件对吧,实际上我们自己开发出的.framework也是静态库,会比.a方式多包涵一些头文件等内容。至于你说的包含应该是指第三方开发而非自己开发的是么?这类不要link在自己的库中,否则有可能会出现duplicate symbol,给使用方带来麻烦,应该由最终使用者来link。2. 我本人没有使用过fake的,都在用real的,real的唯一要求就是framework的开发者和使用者都需要安装iOS Universal framework,据说fake的使用中会有一些问题但具体我不清楚,详细区别可参看原作者在github上的说明。3. 另外,苹果在最新的Xcode6中貌似已经有了对framework方式的静态库支持,可以关注下

  2. 小飞侠 说:

    1.我说的框架确实是指.framework文件哈。然后我看蛮多的厂商都是只提供一个单独的framework(比如支付宝,facebook,百度等),这种情况可能是它们本身就不链接其他第三方的库,又或者是链接了但又不显示说明。如果是后面一种情况就容易造成冲突对吧。我原先也是这么认为的,但是我用Real Framework创建了两个使用同一个静态库(libLogInfo.a)的框架(StaticFramework.framework和StaticFramework_6.framework)。我把这个静态库link到了这两个框架中。然后再建立一个同时使用这两个框架的项目(useStaticFramework.xcodeproj)。我发现并不会冲突,就算把libLogInfo.a静态库链接进来也不会冲突(加不加libLogInfo.a都可以正常运行)。不过如果在Building Settings中将other linker flags设置为-ObjC,或者是-all_load的时候,这时候就会冲突。不知道这个问题应该怎么理解呢。如果是这样会冲突的话,那么在创建框架的时候也最好不要把使用到的静态库link进来对吗?因为用户很可能在使用我们的框架或者其他第三方的框架时候会加上-ObjC等等这些标志吧。下面是我测试中的一些截图,其中StaticFramework_6和StaticFramework的内容是一样的。图一图二图三图四图五图六2.使用Real Framework的话,如果发布给用户的仅仅是框架而不是项目的话,貌似是不需要安装Universal的。3.看资料有说Cocoa Touch Framework暂时只支持iOS8以上的系统,这个可以再了解看看。

    • 1. 正确的做法是不要链接.a到framework中,这样必然会造成符号定义被包含,最终使用者可能存在冲突。在没有使用other link flags的时候没有出现问题,我的理解或者说猜测是,最终的工程build的时候有可能没有使用到其中的符号,没有进行load,所以没有发现冲突。而使用link flag就强制加载了所有的符号,此时发现符号被重复定义。2. 我记得使用方的Xcode环境没有安装iOS Universal Framework是build不过的,貌似会有类似不能识别product type的错误,你可以再看看。3. 貌似是的,我还没开始使用。

    • 关于问题2,我刚刚去看了下文档,貌似确实不需要使用方环境安装了。“so please install it on all development machines that will build your real static framework projects (this isn’t needed for users of your framework, only for builders of the framework)”

  3. 小飞侠 说:

    想问两个问题哈,(1)就是我使用了Real Framework,然后在设置中Architectures项选择了(armv7,armv7s,arm64),编译后使用lipo -info测试得到的framework支持的版本:在Debug-iphoneos目录中支持的是armv7 armv7s arm64,在Debug-iphonesimulator目录中支持的是i386 x86_64,但是有时候又不是这样的,有时会得到两个目录中的framework都支持所有处理器的情形,即armv7 armv7s arm64 i386 x86_64。请问您有出现这样的情况吗,或者说是在脚本还是哪里有设置的选项。(2)如果一个第三方静态库不支持armv7s(新浪微博),那么我制作framework的时候是不是也无法支持armv7s,这样的话iPhone5是不是就不能使用这个框架了?

  4. 小飞侠 说:

    您好,我最近在测试时发现一个问题,就是(1)我创建了一个embedded framework——MyFramework_1.embeddedframework,并且在这个库中使用了第三方网络工具“库”——MKNetworkKit,这个网络库只有源文件,没有静态库。(2)然后在测试工程——UseMyFramework_1中导入了MyFramework_1.embeddedframework。(3)运行时,由于MyFramework_1.embeddedframework在调用MKNetworkKit相关功能的时候发生了错误,导致测试工程崩溃,并且在工程中跳转到了MKNetworkKit这个网络库的源文件MKNetworkEngine.m中,但是我在(1)的步骤中是有把源文件加入到Compile Sources编译目录里面的,而且整个framework对外只提供了一个头文件,发生错误时显示的视图如下:很奇怪为什么在测试工程中可以看的到我用来生成framework的源文件,我看这里也有人提到了这个问题,https://github.com/kstenerud/iOS-Universal-Framework/issues/126不知道您有碰到过吗,或者对这个问题是怎么看待的呢?

    • 你所说的工程崩溃是指程序crash了么?报什么类型的异常?

      • 小飞侠 说:

        不好意思因为水平有限,我一些术语没能表达清楚,确实是指您说的程序crash的,具体异常我没记录下来,不过报的就是我在framework中使用的MKNetworkKit的错误。然后就像上面那张图一样,并没有在UseMyFramework_1的Xcode界面上显示这个错误源文件MKNetworkKitEngine.m的完整路径,一般如果是项目的源文件,会显示如下的目录结构的。然后我在UseMyFramework_1中点击MKNetworkKitEngine.m的右键列表的Show in Finder选项,它会打开我的MyFramework_1项目中的目录,如下图所示所以我在猜想,是不是Xcode在创建framework的时候可能记录了我framework中一些源文件的路径关联信息,比如MKNetworkKit.m。然后在使用framework时,如果(1)程序crash了,且crash和framework相关(2)这些framework的源文件还存在于framework项目的目录中Xcode就有可能把这些源文件显示在当前使用framework的项目视图中。这只是我的猜想,如果真是这样的话倒没关系,因为最终交付给用户的只是framework。还有我不是很明白https://github.com/kstenerud/iOS-Universal-Framework/issues/126这位开发者所说的意思,所以我也不知道该如何重现我的这个问题。

  5. 雯瑶 说:

    您好,我想问下,我们的项目是一个FrameWork ,有多个版本,给其他开发者的时候,不知道其他开发者用的是哪个版本,所以想获取到 FrameWork的版本号。我在网上查找的时候查找到的是可以获取到当前应用的版本号。能问下怎么能获取到 FrameWork的版本号 ?好让我们随时知道其他开发者是用的我们最新德FrameWork还是老版本的FrameWork。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>