在iOS中创建静态库

来源:互联网 发布:什么是网络空间安全 编辑:程序博客网 时间:2024/04/28 08:28
如果你作为iOS开发者已经有一段时间,可能会有一套属于自己的类和工具函数,它们在你的大多数项目中被重用。
 
重用代码的最简单方法是简单的 拷贝/粘贴 源文件。然而,这种方法很快就会成为维护时的噩梦。因为每个app都有自己的一份代码副本,你很难在修复bug或者升级时保证所有副本的同步。
 
这就是静态库要拯救你的。一个静态库是若干个类,函数,定义和资源的包装,你可以将其打包并很容易的在项目之间共享。
 

在本教程中,你将用两种方法亲手创建你自己的通用静态库。

为什么使用静态库
 
创建静态库可能出于以下几个理由:
1.你想将一些你和你团队中的同事们经常使用的类打包并轻松的分享给周围其他人。
2.你想让一些通用代码处于自己的掌控之下,以便于修复和升级。
3.你想将库共享给其他人,但不想让他们看到你的源代码。

开始
运行Xcode,选择File\New\Project,在Choose a template 对话框中选择iOS\Framework & Library\Cocoa Touch Static Library,如下图:

                 
 
点击Next。在工程选项对话框中,输入MyLibray作为产品名。再输入一个唯一的公司标识,如下图:

点击Next。最后,选择你想保存工程的位置并点击Create。
 
Xcode已经准备好静态库工程,甚至已经为你添加了一个Mylibray类。
 
注意: 你可以添加任意数量的类到静态库中或者从中删除原有的类。你也可以为自己的静态库添加资源文件xxxx.bundle方便管理,如下操作:

你的Xcode工程还是一片空白,现在我们添加一些代码进去!如下为类Print的头文件和实现文件代码,
#import <Foundation/Foundation.h>@interface Print : NSObject- (void)print;@end
#import "Print.h"@implementation Print- (void)print{    NSLog(@"这是一个具有打印功能的类");}@end
这些头文件中的声明定义了类的公开接口。其他开发者(包括你自己)使用该库时,只需通过阅读该头文件就可以知道类名和暴露的方法。现在构建并运行你的库。你会注意到Xcode的”Run”按钮只是执行了一次构建,而并不能真正的运行库去查看效果,因为并没有真正的app。
静态库的后缀名是.a而并不是一个.app或者.ipa文件。可以在工程导航栏中的Products文件夹下找到生成的静态库(变成了灰色代表静态库生成成功了)。右键点击MyLibray.a并在弹出菜单中选择Show in Finder。
注意:这里选择IOS Device或者实际的iPhone设备,表示生成的静态库用于实际设备,选择模拟器则只能用于模拟器
怎么办?
 
幸运的是,有一个更好的办法可以不建立多个配置或在工程中构建产品就可以支持多个平台。你可以创建一个对应 2个 架构的包含结果代码的universal binary。
通用二进制
通用二进制是一种特殊的二进制文件,它包含对应多个架构的结果代码。你可能在从PowerPC(PPC)到Inter(i386)的Mac电脑产品线的过渡中对其有所熟悉。在这个过程中,Mac应用程序通常迁移为包含 2个 可执行包的一个二进制文件,这样应用程序即能在Inter也能在PowerPC的Mac电脑上运行。
 
同时支持ARM和i386的概念并没有太大不同。在这里静态库要包含支持iOS设备(ARM)和模拟器(i386)的结果代码。Xcode可以识别通用库,每次你构建应用的时候,它会根据目标选择适当的架构。
在Xcode菜单中选择File/New/Target,选择iOS/Other并点击Aggregate,如图:

 
将目标命名为UniversalLib,确保选中ImageFilters工程,如图:



在工程导航视图中选中MyLibray,然后选择UniversalLib目标。切换到Build Phases标签;在这里设置构建目标时将要执行的动作。
 
点击Add Build Phase按钮,在弹出的菜单中选择Add Run Script,如下图:

现在你需要设置脚本项。展开Run Script模块,在Shell行下粘贴如下代码:
  1. # define output folder environment variable 
  2. UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal 
  3.   
  4. # Step 1. Build Device and Simulator versions 
  5. xcodebuild -target Mylibray ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" 
  6. xcodebuild -target Mylibray -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" 
  7.   
  8. # make sure the output directory exists 
  9. mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" 
  10.   
  11. # Step 2. Create universal binary file using lipo 
  12. lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" 
  13.   
  14. # Last touch. copy the header files. Just for convenience 
  15. cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/"
  16. cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/MyLibray.bundle" "${UNIVERSAL_OUTPUTF
  17. OLDER}/"
 
代码并不十分复杂,它是这样工作的:
UNIVERSAL_OUTPUTFOLDER 包括了通用二进制包将要被存放的文件夹:“Debug-universal”
 
Step 1. 第2行执行了xcodebuild并命令它构建ARM架构的二进制文件。(你可以看到这行中的-sdk iphoneos参数)
下一行再次执行了xcodebuild命令并在另一个文件夹中构建了一个针对Inter架构的iPhone模拟器的二进制文件,在这里关键参数是-sdk iphonesimulator -arch i386。(如果感兴趣,你可以在man page了解更多关于xcodebuild的资料)
 
Step 2. 现在已经有了2个.a文件分别对应两个架构。执行lipo -create,用它们创建出一个通用二进制。
 
最后一行的作用是复制头文件到通用构建文件夹的外层。(用cp命令),注意构建通用静态库之前应该先构建完用于模拟器和真机的静态库,因为这里要合并
点击Play按钮来为集合方案构建目标。
 
在libImageFilters.a上再次选择Show in Finder查看结果。将Finder切换到列视图查看文件夹层次,可以看到一个包含库的通用版本的叫做Debug-Universal的新文件夹(或Release-Universal如果你构建了发布版本)
在应用中使用静态库

有三种办法可以在工程中使用静态库:
方法 1: 直将静态库的头文件和二进制文件及资源文件拖入工程中,这种方式就像引用工程中的其它头文件一样,不需要配置静态库的搜索路径。每个工程都保留一份静态库的副本,当静态库更新时,每个副本也要及时更新。
方法 2: 直接引用头文件和库二进制文件,像引用系统库头文件一样,但是xcode需要配置静态库文件的路径,工程没有保留副本,随着静态库的更新一起更新
方法 3: 将库工程作为子项目
选择哪一种方法完全取决于你的喜好或者是否有静态库的源代码和工程配置文件任由你支配。
方法 1:直将静态库的头文件和二进制文件及资源文件拖入工程中
这种方式就不予介绍了,只需要把整个静态库的所有相关文件拖入工程即可,此时静态库已经作为工程的一部分了,当静态库更新时记得更新工程中的静态库
方法 2: 头文件和库二进制文件
为了方便起见,.a通用库文件和头文件已经复制了一份在工程中,但工程并未设置使用它们。你将从这里开始
注意: 标准的Unix引入惯例是一个include文件夹,用来存放头文件,一个lib文件夹用来存放库文件(.a)。这种文件夹结构这是一种惯例,并不强制。你并不需要一定遵从这种结构或者复制文件到工程文件夹中。在你自己的应用中,你可以任意选择头文件和库文件的位置,只要随后在Xcode工程中设置了适当的路径。
 
打开工程,构建并运行你的应用,将会看到以下错误:

正如所期望的那样,应用并不知道去哪里寻找头文件。为了解决这个问题,你需要在工程中添加一个Header Search Path,指明头文件存放的位置。设置头文件搜索路径始终是使用静态库的第一步。
 
按照下图示范,在导航栏中点击工程根节点(1),选择test目标(2)。选择Build Settings(3),选择All,在列表中找出Header Search Paths设置项。如果必要,可以在搜索框中输入”header search”来过滤庞大的设置列表(4)。
双击Header Search Paths项,弹出一个浮动窗口,点击+按钮,输入:
  1. $SOURCE_ROOT/include 
$SOURCE_ROOT是一个Xcode环境变量,指向工程根文件夹。Xcode会使用包含你工程的实际文件夹代替此变量,这意味着即使你把工程移动到其它文件夹或驱动器,它仍然可以指向最新的位置。
构建并运行应用,看看结果是什么。呃……一些链接错误出现了:

这看起来并不是很好,但是给了你另一个你所需要的信息。仔细看,会发现所有的编译错误全都消失了,全部被链接错误所代替。这表示Xcode找到了头文件并且用它去编译应用,但在链接阶段,Xcode无法找到Print类的结果代码。为什么?
 
很简单 — 你还没有告诉Xcode去哪里寻找包含类实现的库文件。(看,没什么大不了)
 
如下面的屏幕截图所示,回到test目标(2)的构建设置(1)。选择Build Phases标签(3),展开Link Binary With Libraries部分(4)。最后,点击+按钮(5)。
在出现的窗口中,点击Add Other…按钮,在工程根文件夹下的lib子目录中找到Mylibray.a库文件
最后一步是增加-ObjC链接标识。该链接尝试更高效的只包含需要的代码,而有时会排除静态库代码。使用该标识,库中的所有Objective-C类和类别都将被适当的加载。你可以从苹果的Technical Q&A QA1490了解详细信息。
点击Build Settings标签,找到Other linker Flags设置,在弹出窗口中,点击+按钮并输入-ObjC
恭喜 — 你已经构建了你的第一个静态库并在一个真正的应用里使用它!你会发现这种包含头文件和库的方法在很多第三方库中使用,如AdMob,TestFlight或一些不提供源代码的商业库。
方法 3: 子项目
将Mylibray库工程作为子项目所需的所有操作就是拖拽库工程文件到库文件树中。如果该工程已经在另一个Xcode窗口中被打开,那么Xcode无法正确将其添加为子工程。所以在继续本教程之前,确保ImageFilters库工程已经被关闭。
在Finder中找到名为Myilbray.xcodeproj库工程文件。拖拽它到test工程左侧的导航栏中,完成拖放后,你的工程浏览视图应该如下图所示:

现在Xcode已经识别了子工程,你可以将库添加为工程依赖。这样Xcode就可以在构建主应用之前确保库为最新版本。
点击工程文件(1),选择test目标(2)。点击Build Phases标签(3)并展开Target Dependencies(4),点击+按钮增加一个新依赖。如下图所示,确保你从弹出窗口中选择了Mylibray目标(不是universalLib):

最后,设置静态库工程链接到应用。展开Link Binary with libraries,点击+按钮,选择libMylibray.a,点击Add:
像方法一那样,最后一步是增加-ObjC链接标识。点击Build Settings标签,找到Other linker Flags设置,
如果按照第二种方法在应用中添加库(使用头文件和库文件),你可能注意到和第三种方法的区别。在方法三中,你没有在工程设置中添加任何头文件搜索路径。另一个区别是你没有使用通用库。
 
为什么会有这样的区别?当添加一个库作为一个子工程,Xcode会为你考虑几乎所有的事情。添加子工程和依赖后,Xcode知道去哪里寻找头文件和二进制文件,也知道根据你的设置去选择哪需要构建哪一个架构的库。这非常方便。
 
如果你使用你自己的库或者拥有源代码和工程文件,将库作为子工程不失为一个引入静态库的简便的方法。让你更容易作为工程依赖构建整合,并担心更少的事情。











0 0