Xcode7 开发静态库和动态库
cup架构说明
- arm7: 在最老的支持iOS7的设备上使用
- arm7s: 在iPhone5和5C上使用
- arm64: 运行于iPhone5S的64位 ARM 处理器 上
- i386: 32位模拟器上使用
- x86_64: 64位模拟器上使用
一、开发SDK时的支持情况:
OC语言制作动态库时,支持iOS8+;OC语言制作静态库,支持iOS7+。
Swift语言制作动态库时,支持iOS8+;Swift不支持静态库。
对于SDK来说,支持情况非常重要。像我就是一开始就被坑了,我使用Swift开发动态库的方式提供SDK,所以只能支持到iOS8+。但这意味着所有使用我的SDK的客户的APP都必须到iOS8+,这就坑爹了。
所以假如需要支持iOS7的话,只有使用OC语言开发.a静态库的一条路。
二、第三方库的使用:
原本SDK已经作为别人APP工程里的第三方了,假如SDK中需要引用AFNetworking类似的三方库。
对于动态库:
(1)外部引用:第三方库不打进去,放在外部,比如cocoapods的方式。别人编译的时候需要在他的环境里有该第三方依赖库。当提供给别人SDK的时候,你还需要给别人一个podfile。
(2)内部:直接引入源码,由于swift含有命名空间,所以不会有冲突。
对于静态库:
1.静态库无法再包含其他的.a静态库。只能把源码放进去一起编译。
2.静态库无法把第三方放在外部,否则就不叫“静态”了。只能打包进SDK内部,并修改类名,防止外部冲突。 3.第三方的管理: 在开发SDK时,开发者应该有一个共识,那就是千万不能把三方的源码打包进自己的SDK。比如我需要用到MBProgressHUD,我把它封进SDK里,不但程序会很大,别人再次导入也会产生编译冲突。所以最好还是使用CocoaPods,在文档中告诉别人使用的三方库和版本号。
声明:为方便测试,采用工程组合的方式开发静态库和动态库
三、静态库开发
- 新建一个普通(如Single View Application)项目作为测试项目。
- 新建一个iOS->Framework & Library->Cocoa Touch Staic Libryry的SDK项目,在选择工程目录时选择Add To:添加到测试项目中(测试项目的窗口必须打开,不然不会出现此选项)。
- 在文件树选中测试项目然后选择对应的target->Build Phases里的Target Dependencies 和 Link Binary With Libraries分别添加SDK项目的静态库和.a依赖库(Target Dependencies里的静态库可以不添加)。
- SDK项目会自动生成一个与项目名相同的类,如不需要可以删除.m文件,.h文件可以留下来以后当主头文件使用。
- 在测试项目的Build Settings里的Header Search Paths里添加SDK项目的头文件搜索路径(选中SDK项目Show in Finder,然后将下图的两个目录都拖入相应位置,之后就可以引入头文件测试SDK了)。
- 进行SDK开发,这里只是写了一个简单的log方法。
- 选中SDK项目,在Build Phases->Copy Files里面添加SDK需要暴露的头文件。
- 在Xcode左上角分别选择SDK项目和测试项目进行编译(SDK项目编译后可以看到其在Products里的.a文件由红色变成了黑色),然后在测试项目里测试SDK开发的功能。
工程组合方法测试没问题之后新建一个新的项目对SDK进行测试
- 选中SDK项目的.a文件Show in Finder,将.a文件和include文件夹拖入新工程,记得勾选Copy items if needed。
- 检查Build Phases里的Link Binary With Libraries里面是否已经存在SDK的.a文件(一般Xcode会自动加入)。
- 在新项目里测试SDK。
至此,只是生成了Debug模式下模拟器的SDK,想要生成Release模式下模拟器的SDK需要更改SDK工程的Scheme,然后重新编译即可生成。
至于Debug、Release模式下的真机SDK,只要在设备列表里选中对应的真机设备编译即可。
开发静态库时的资源管理
- 选择SDK的工程,点击target中的+,然后选择OSX,增加资源Bundle的target。
- 修改bundle的Build Settings的Base SDK为iOS(也可以直接删除就会变为iOS了)。
- 在bundle的Build Phases->Copy Bundle Resources添加xib、image等资源文件。
在组合工程里对带bundle的SDK进行测试时,需要写明bundle路径,新建项目测试调用bundle资源一样需要写明bundle路径。提供SDK时,需要将include文件夹,.a,.bundle文件一起提供。
生成通用静态库没有找到好的脚本,后续如果找到了有时间再添加。
四、动态库开发
- 新建一个普通(如Single View Application)项目作为测试项目。
- 新建一个基于iOS->Framework & Library->Cocoa Touch Framework的SDK项目,在选择工程目录时选择Add To:添加到测试项目中(测试项目的窗口必须打开,不然不会出现此选项)。
- 在文件树选中测试项目然后选择对应的target->Build Phases里的Target Dependencies 和 Link Binary With Libraries分别添加SDK项目的framework和.framework依赖库(Target Dependencies里的framework可以不添加)。
- 在测试项目的Build Settings里的Header Search Paths里添加SDK项目的头文件搜索路径(选中SDK项目Show in Finder,然后将下图的两个目录都拖入相应位置,之后就可以引入头文件测试SDK了)。
- 进行SDK开发,这里只是写了一个简单的log方法。
- 在SDK项目的Build Phases->Headers里设置开放的头文件,把需要开放的放到Public,不希望被人看到的放到Project,需要公开的头文件必须使用#import
方式添加到主头文件中。Private里的外部一样可以看到,可以使用,但是不可以添加到公共头文件中。 - 在Xcode左上角分别选择SDK项目和测试项目进行编译(SDK项目编译后可以看到其在Products里的.framework文件由红色变成了黑色,可能有延迟,但是Show in Finder可以查看。多编译几个模拟器,或者模拟器、真机都编译应该可以消除延迟),然后在测试项目里测试SDK开发的功能。
工程组合方法测试没问题之后新建一个新的项目对SDK进行测试
- 选中SDK项目的.framework文件Show in Finder,将.framework文件拖入新工程,记得勾选Copy items if needed。
- 调用framework中的功能,运行报错(Reason: Image Not Found),原因是因为我们做的是动态库,在使用的时候需要额外加一个步骤,要把Framework同时添加到'Embedded Binaries'中(保证Embedded Binaries 和 Linked Frameworks and libraries里都有framework。framework分为动态库和静态库,动态库在Embedded Binaries需要添加,静态库不需要,静态库下面会提到)。注意: 在XCode 6之前是没有这个选项的(我没发现),所以理论上XCode 5及之前的版本无法使用Xcode 6下生成的Framework动态库。
- 尝试将测试工程部署到真机上,问题来了。
ld: warning: ignoring file /work/ios/MyFrameworkTest/MyFrameworkTest/MyFramework.framework/MyFramework, file was built for x86_64 which is not the architecture being linked (armv7): /work/ios/MyFrameworkTest/MyFrameworkTest/MyFramework.framework/MyFramework
Undefined symbols for architecture armv7:
"_OBJC_CLASS_$_MyUtils", referenced from:
objc-class-ref in AppDelegate.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
为什么会这样呢?错误提示已经很明显了,因为我们制作动态库的时候,选的设备是模拟器,如果选真机的话,那生成的库也只能在真机上使用,那我们该怎样制作一个通用的动态库呢? 简单的方法是分别生成模拟器和真机上运行的库,然后在合并,这个方法,在每次生成动态库的时候,过程都会很繁琐,下面我们用一个脚本来自动完成它。
##### 制作通用动态库(framework)
* 新建Aggregate Target
![](/assets/[email protected])
* 新建Run Script到新建的target
![](/assets/1416969056242621.png)
```ObjC
``` # Sets the target folders and the final framework product.
# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME
# 例如: FMK_NAME = "MyFramework"
FMK_NAME=${PROJECT_NAME}
# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
# -configuration ${CONFIGURATION}
# Clean and Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build
# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
rm -r "${WRK_DIR}"
open "${INSTALL_DIR}"
- 选中新建的Target,Run, 如果没有异常的话,会自动弹出生成的Framework文件,这样生成的framework就能同时支持模拟器和真机了。
Xcode制作通用静态库(framework)
这样生成的动态库恐怕很难在Xcode 5(iOS7)上使用,那我们为什么非要用动态库呢,一般情况下不是用静态库就好了吗? So Easy!只需要修改一个参数即可生成静态库了。 使用静态库的话,就可以把Framework从'Embedded Binaries'中删除了. 亲测在Xcode 7下可用。把新生成的库导入到测试工程,试试在模拟器和真机上运行,一切OK. 不巧,如果你用的真机是iPhone5 C, 那悲剧又要发成了,生成的Framework竟然不支持armv7s,不知是Xcode 6的bug,还是因为苹果认为使用armv7s的设备太少,可以不支持了.Xcode 新建工程,默认的Architectures竟然不包含armv7s. 想要生成的库支持armv7s,把armv7s添加到Architectures中,重新生成Framework即可 判断一个Framework支持哪些架构
我们该怎么验证生成的Framework支持哪些平台呢,总不能一个个测试吧?当然不用,下面的命令是加上armv7s前后生成的framework的对比。
xinyea:~ xinyea$ lipo -info ./MyFramework.framework/MyFramework
Architectures in the fat file: ./MyFramework.framework/MyFramework are: i386 x86_64 armv7 arm64
xinyea:~ xinyea$ lipo -info ./MyFramework.framework/MyFramework
Architectures in the fat file: ./MyFramework.framework/MyFramework are: armv7 armv7s i386 x86_64 arm64
framework的资源管理
具体步骤不再做介绍(同时framework本身也支持添加资源文件,如果需要公开也可以添加到Headers->Public里,但这样可能不太规范,不推荐使用)
添加完bundle之后注意事项
- 点击Build Settings栏,搜索base sdk,选择Base SDK这一行,按下delete键,这一步将OS X切换为iOS(或者直接更改也可)。
- 同时你需要将Product Name改为与Framework同名。搜索product name,双击进入编辑模式,将${TARGET_NAME}替换为Framework的名字。
- 默认情况下,有两种resolutions的图片可以产生一些有趣的现象。例如,当你导入一个retina @2x版本的图片时,普通版的和Retina版的将会合并成一个多resolution的TIFF(标签图像文件格式,Tagged Image File Format)。这不是一件好事。搜索hidpi将COMBINE_HIDPI_IMAGES设置为NO。
- 现在,你将确保当你编译framework时,bundle也能被编译并将framework作为依赖添加到集体目标中。选中Framework目标,选择Build Phases栏,展开Target Dependencies面板,点击 + 按钮,选择bundle目标将其添加为依赖。
使用bundle
- 需要在Bulid Phases的Target Dependencies添加bundle依赖
- 将xxx.bundle拖到Copy Bundle Resources里
注意事项
如果你在静态库工程中使用了category,可能会碰到链接问题,解决的办法就是需要同时在生成静态库的工程和使用静态库的工程中使用"-all_load"编译选项,即在对应target的"Build Settings"中的"Other Linker Flags"选项添加"-all_load"(建议先添加-ObjC,如果依然有问题,再添加-all_load),注意:使用静态库的工程中是一定要加该编译选项的!至于生成静态库的工程中加不加没有试过,不过建议还是加上该编译选项。
Build Settings -> Architectures -> Build Active Architecture Only -> Release 选择NO,Yes表示只编译选中模拟器设备对应的架构,No则为编译所有模拟器设备支持的cup架构(Debug版本同理,提供给他人是使用时,建议Debug也选中NO),然后分别在模拟器和真机下Command+B编译一下,会看到Products文件夹下的.a文件变为黑色,这个.a文件就是我们想要得到的静态库,这里会出现一个问题你先编译的模拟器会发现.a依然是红色,你需要模拟器和真机都编译后.a才会变成黑色,这应该是Xcode本身的问题。