最近在做一个iOS的统计SDK,需要从零开始做一个framework,同时为了方便开发,花了点时间折腾,于是顺便总结一下iOS framework的开发流程,不同的Xcode版本流程会有些小区别,以下使用的是7.3.1。
建立framework工程
建framework工程之前,要先了解framework
framework和.a
iOS一直只对用户开放静态库,直到iOS8用户才能用动态库,然而这个动态库貌似也只能是进程内,无法跨进程共享。
我们用的更多的还是静态库,iOS静态库有两种类型,framework和.a,看过linux系统方面的人对.a和.so应该都不陌生,事实上苹果下的.a和linux下的.a从格式上来看不是同一个东西,一个是Mach-O,一个是ELF,但其实Mach-O只是苹果在ELF的基础上添加一些额外的段,可以认为基本一样。那么framework呢?可以认为framework只是对.a的一层额外的封装,类似苹果对ipa的封装,把.a文件包装进一个结构化的目录结构,这样就是一个framework。由于framework本身是一个目录,于是可以把属于framework内部的一些资源也放进去,比如头文件、图片、音视频文件等等,从封装的角度来看,framework是比.a更好的选择。
其实,我们不需要了解这么多,也不需要自己建这些目录层次,xcode已经帮我们做了大部分的工作,我们需要的只是改几个配置。
基本设置
新建framework工程:
填好framework名称,确定,就生成了一个完整的framework工程:
运行一下,红色的framework就变黑,说明framework已经生成了。但这个framework并不是我们想要的
这个framework只有arm7和arm64两种架构的指令内容,如果直接发布这个framework,开发者将无法在模拟器上编译,后续我们会说到多架构指令的必要。另外,这是一个动态库,我们需要的是静态库。
在Build Setting里设置以下几个参数
Architectures:包含哪些架构指令,这里我们需要添加armv7s架构
Build Active Architecture Only:是否只为当前架构编译
Dead Code Stripping:是否从framework中删除未使用的代码
Link With Standard Libraries:是否链接苹果标准库
Mach-O Type:这里有好几种类型,我们需要选中static library
Other Linker Flags:链接参数,如果framework中使用了category,最好加上ObjC、all_load,确保运行时系统会加载其中的category
Other C Flags:额外的C语言链接参数,如果需要支持bitcode,需要加上-fembed-bitcode
重新编译运行:
这离我们想要的framework不远了。
另外,我们新建一个类,毕竟,我们要做一个统计SDK
#import <Foundation/Foundation.h>
@interface DTTrackManager : NSObject
- (void)trackEvent:(NSString *)event;
@end
@implementation DTTrackManager
- (void)trackEvent:(NSString *)event {
NSLog(@"you are tracking event: %@", event);
}
@end
我们想要把DTTrackManager开放给开发者,让他们能使用这个类,还需要做一点额外设置:
这样,打包出来的framework就包含了我们想要公开的头文件了
多架构和打包
不同的CPU使用不同的指令,我们一般称一套指令为一种架构,比如笔记本电脑的CPU和移动端手机的CPU使用不同的指令,类似的,32位CPU与64位CPU使用不同的指令,因此我们需要一个能包含所有架构指令的framework,这样,开发者才能在不同的CPU环境下都能使用我们的framework。
我们现在的framework包含了3种架构指令,armv7、armv7s、arm64,这些都是iOS系统使用的架构,macbook等macOS系统是无法使用这个framework的,这就导致开发者目前是无法用这个framework在模拟器上调试。
因此,我们需要再添加i386和x86_64这两种桌面端系统使用的架构。我们可以单独编译这两个架构,然后用lipo命令手工合成,但xcode提供了一个更好的方案。
选中刚创建的DTTrackerCombiner,在Build Phase下添加一个执行脚本:
脚本内容:
set -e
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -arch x86_64 -arch i386 -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"
open "${PROJECT_DIR}"
ps:
-arch x86_64 -arch i386
为模拟器编译的两个架构需要显式指定,否则编译报错
选择正确的scheme和目标,开始编译
如果编译成功,会弹出编译好的framework所在目录,检查下是不是包含了我们想要的全部架构
One More Thing!
把打包scheme的配置改为release
这样,我们只要把源文件放入这个工程里,设置好需要公开的头文件,就能编译出我们想要的framework了,非常方便。但是,实际开发中,我们需要一边开发framework,一边在使用这个framework的Demo工程里测试这个framework,如果每次都需要编译一遍framework工程,然后在Demo工程里替换framework,这会严重影响开发效率。
Demo工程
新建一个DTTrackerDemo工程,注意Deployment Target设置不要低于framework的。
接入framework工程
接入framework工程很简单,在Finder里找到DTTracker.xcodeproj,直接拖入DTTrackerDemo工程中就行了
这样,就可以直接在Demo工程里运行framework工程的target来编译framework了。注意,framework工程只能存在一份,额外的framework工程必须关掉,否则DTTrackerDemo工程里会找不到DTTracker.xcodeproj下的文件和target。
我们需要在每次运行Demo工程时先运行framework工程,这样才能获取最新代码,因此在Demo工程的Build Phase中添加对framework的依赖
这里的Target Dependencies选择DTTracker而不是DTTrackerCombiner只是因为可以缩短打包时间,毕竟DTTrackerCombiner需要把5份架构打进去,而DTTracker只需要打3份。至于为什么DTTracker明明不包含模拟器的架构却可以启动Demo APP,这个需要看下Demo APP的所在目录
Demo APP被放在Debug-iphonesimulator目录下,再看下该目录下的DTTracker.framework
没毛病,是模拟器需要的架构。
开发流程
万事具备,来测试一下我们的framework。
在Demo工程的ViewController.m引入framework头文件:
#import <DTTracker/DTTrackManager.h>
初始化一个DTTrackManager并调用trackEvent方法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
DTTrackManager *tracker = [[DTTrackManager alloc] init];
[tracker trackEvent:@"viewDidLoad"];
}
运行
完美!
那打包framework呢?直接在Demo工程里,选中DTTracker.xcodeproj,选中我们之前添加Aggregate target对应的scheme:DTTrackerCombiner,直接打包
至此,我们完成了
- framework工程的建立和优化,能独立编译可以直接发布的framework
- 在Demo工程添加对framework工程的依赖,建立了framework开发流程
这样,整个我们就可以愉快地开发framework了!
关于framework还有一些没提到的内容,比如bundle资源、添加framework自定义编译宏,后续有时间再写。