Xcode 6制作动态及静态Framework
http://www.cocoachina.com/ios/20141126/10322.html
有没有写SDK或者要将一些常用的工具类做成Framework的经历? 你或许自己写脚本完成了这项工作,相信也有很多的人使用 iOS-Universal-Framework ,随着Xcode 6的发布,相信小伙伴们已经都知道了,Xcode 6支持做Framework了. 同时iOS-Universal-Framework开发者也宣布不在继续维持此项目的开发,建议开发者使用Xcode 6制作,目前网上也有很多制作iOS Framework的资料,但大多都不够详细,接下来本文会详情介绍一下在Xcode 6下制作iOS Framework.
关于静态库和动态库的概念,网上资料很多,这里不做叙述,只讲解制作过程。
创建iOS动态库
新建工程并选择默认Target为Cocoa Touch Framework, 如图:
做编码工作,在这里我简单的写了一个Utils的类,并写了一个log方法
设置开放的头文件:Framework中有些类可能是一些私有的辅助工具,不需要使用者看到,在这里只需要把开放出去的类放到Public下, 如图
这样生成的Framework的Headers目录下也只能看到Public的头文件
编码完成之后,直接Run就能成功生成Framework文件了,选择 xCode->Window->Organizer->Projects->Your Project, 打开工程的Derived Data目录,这样就能找到生成的Framework文件了,如图
新建测试工程,使用生成的Framework
将Framework文件导入到测试工程,调用Framework中的代码
1
2
|
MyUtils *utils = [MyUtils new ];
[utils log:@ "didFinishLaunchingWithOptions" ];
|
运行报错(Reason: Image Not Found)
为什么会这样的?因为我们做的是动态库,在使用的时候需要额外加一个步骤,要把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)
为什么会这样?错误提示已经很明显了,因为我们制作动态库的时候,选的设备是模拟器,如果选真机的话,那生成的库也只能在真机上使用,那我们该怎样制作一个通用的动态库呢? 简单的方法是分别生成模拟器和真机上运行的库,然后在合并,这个方法,在每次生成动态库的时候,过程都会很繁琐,下面我们用一个脚本来自动完成它。
制作通用动态库
新建Aggregate Target
添加script到新建的Target
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 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文件
这样生成的动态库就能同时支持模拟器和真机了。
Xcode 6下制作通用静态库
上面我们也提到了,这样生成的动态库恐怕很难在Xcode 5上使用,那我们为什么非要用动态库呢,一般情况下不是用静态库就好了吗? So Easy!只需要修改一个参数即可生成静态库了。
使用静态库的话,就可以把Framework从‘Embedded Binaries’中删除了. 亲测在Xcode 5下可用。把新生成的库导入到测试工程,试试在模拟器和真机上运行,一切OK.
不巧,如果你用的真机是iPhone5 C, 那悲剧又要发成了,生成的Framework竟然不支持armv7s,不知是Xcode 6的bug,还是因为苹果认为使用armv7s的设备太少,可以不支持了.Xcode 新建工程,默认的Architectures竟然不包含armv7s.
想要生成的库支持armv7s,把armv7s添加到Architectures中,重新生成Framework即可
判断一个Framework支持哪些架构
我们该怎么验证生成的Framework支持哪些平台呢,总不能一个个测试吧?当然不用.下面的命令是加上armv7s前后生成的framework的对比
1
2
3
4
|
Yearsdembp:Products Years$ lipo -info ./MyFramework.framework/MyFramework Architectures in the fat file: ./MyFramework.framework/MyFramework are: i386 x86_64 armv7 arm64
Yearsdembp:Products Years$ lipo -info ./MyFramework.framework/MyFramework Architectures in the fat file: ./MyFramework.framework/MyFramework are: armv7 armv7s i386 x86_64 arm64
|
4、建立一个真机和模拟器通用的framework
首先用finder找到framework所在的位置
然后找到framework中的文件,例如这里的 Kalagame-library,并且纪录其路径 os_frame_path
同样方法打开另一个文件夹,纪录其中库的路径,simulator_frame_path
然后打开控制台,输入 lipo -create os_frame_path simulator_frame_path -output newframe
这样就完成了模拟器和真机版本framework的合并,用finder找到这个newframe,然后把newframe改名字(例如这里的Kalagame-library),并放回到framework文件夹中,替换原来的文件。
例如:
1. lipo -create /Users/chengdeluo/Library/Developer/Xcode/DerivedData/MyFramework-dkfgbkcpzmnceoenwrpehqyoopof/Build/Products/Debug-iphoneos/MyFramework.framework/MyFramework /Users/chengdeluo/Library/Developer/Xcode/DerivedData/MyFramework-dkfgbkcpzmnceoenwrpehqyoopof/Build/Products/Debug-iphonesimulator/MyFramework.framework/MyFramework -output /Users/chengdeluo/Desktop/newFramework
2. 接下来用finder找到这个newFramework,然后把newframe改名字为:MyFramework ,并放回到framework文件夹中,替换原来的(MyFramework)文件。
1. 如果框架本身要引用到.dylib(动态链接库), (通常会报错: include of non=modular header inside framework 'Hbb_FileFramework.unzip')那么你需要做如下配置:
应用工程需要配置 allows non-modular includes in framework modules 为YES
2. 如果你的静态frameowrk/静态库(.a)中包含有category文件(如:NSString+StackSymbol.h)
那么你恭喜你要继续配置,就是这么麻烦, 但是没办法
应用工程 project -> target -> build settings -> linker -> other linker flags
中添加 -force_load $(SOURCE_ROOT)/UseOfHbb_LogDemo/Hbb_Log.framework/Hbb_Log
注意: 这里是加载框架Hbb_Log.framework里的名为Hbb_Log的文件, 你可以看成框架源代码的打包文件
3. 如果框架工程中警告:
Error: “File was built for archive which is not the architecture being linked (armv7s)”
那么只是叫你把真机和模拟器生成的工程合并起来
4. 如果框架工程中出现declaration of must be imported from module before it is required
或者 duplicate file.. 等编译错误的话, 很可能框架工程本身引用到框架本身, 照成定义重复.
我们这种情况常发生在 在一个工程里, 有2个target, 一个target用来生成静态framework, 另一个则是用来使用这个静态框架, 这个时候如果是使用不当就会出现上述情况.
解决方法: 将framework所属的类全部拖入framework工程文件夹中, 并在.h和.m选中 framework target, 注意千万不要选择demo target, 一个都不要
在demo中这样使用框架
project -> demo target -> general -> linked frameworks and libraries 加入该静态framework
2. 编译静态framework, 模拟器和真机都要
3. 在代码中导入要使用的头文件如:
#import <HbbLogFramework/Hbb_Logger.h>
然后就可以尽情使用了
5. 真机运行前警告: App installation failed
The application does not have a valid signature.
解决办法:
处理办法是: project -> target -> Hbb_LogDemo(框架工程) -> general -> embed binary 删除框架引用
6. 所有操作必须使用clean才能使用, 否则将不准确
7. static 的 framework 不能在访问框架内部的资源文件(也就是bundle文件), 所以如果framework中如果需要使用到资源文件,
那么需要同时创建一个bundle文件, 将资源文件放入其中, 使用时: framework和bundle文件一同拉入工程, 才可以使用.
8. framework的代码本身含有category文件, 并且demo外部也引用了含有category文件的库
连坑不断, 唉, 没办法, 谁叫我是coder呢
解决方法:
project -> target -> Hbb_LogDemo(框架工程) -> build settings -> linking -> other linker flags
添加:
-force_load
$(BUILT_PRODUCTS_DIR)/Hbb_ShareFramework.framework/Hbb_ShareFramework
Hbb_ShareDemo/libWeiboSDK/libWeiboSDK.a
注意: 框架应该排在前面, 顺序不对的话, 在真机上同样没效果的,切记!!
$(BUILT_PRODUCTS_DIR)是产品框架生成根目录
8.2 如果同时有多个框架需要配置-force_load
那么应该这么写:
-force_load
Hbb_ShareFramework.framework/Hbb_ShareFramework
ManyFrameworkDemo/libWeiboSDK/libWeiboSDK.a
-force_load
Hbb_LogFramework.framework/Hbb_LogFramework
9.Missing sumodule 'Hbb_LocalDataBaseFramework.HP_FMDBOperator'
解决方案: 所有公开的类 (标识为public), 必须在Hbb_LocalDataBaseFramework.h中导入, 这是这个框架的头文件, 否则就会有这样的警告
10. dyld: Library not loaded: Reason: image not found
dyld: Library not loaded: @rpath/Hbb_LocationFramework.framework/Hbb_LocationFramework
Referenced from: /private/var/mobile/Containers/Bundle/Application/44478571-ECDB-4BC7-B440-AECCF28B20C7/Hbb_LocationDemo.app/Hbb_LocationDemo
Reason: image not found
证明你创建的是动态的framework, 我们要改成静态的
解决方案: project -> framework target -> build settings ->linking -> Mach-O Type 改为 static
默认是dynamic
11. 引用到libxml2.dylib动态库
框架文件本身要加入libxml2.dylib, 并且还要配置search paths ->header search paths 中加入 /usr/include/libxml2
12. 框架的.h文件中声明多个类interface, 而没有@implementation
这个时候需要为这多个interface声明 @implementation, 不能直接写.h文件里, 而是需要创建.m文件实现这多个@interface
如: Hbb_IMComm.h
/**
* 登陆信息
*/
@interface Hbb_IMLoginParam :TIMLoginParam
@end
那么就需要创建一个Hbb_IMComm.m文件, 并且在其中编写如下代码:
@implementation Hbb_IMLoginParam
@end
13.参考自: 真机运行出现 file was built for archive which is not the architecture being linked (armv7s) 报错
真机file was built for archive which is not the architecture being linked (armv7s)
原因是在在真机上生成真机自身architecture, 没有生成别的型号的architecture, 比如我在iphone5s上生成了framework, 放在ipad mini上就运行不了
解决方法:
找到项目的Build Settings- > Build Active Architecture Only,将其从NO 设为 YES
14. 声明为public的头文件, 不需要全部导入到xxx_framework.h文件中
15.
方法一. xib文件必须放在[NSBundle mainBundle](主套装)里,才能被编译运行
方法二. 在framework中打包xib target->build phases->copy bundle resources + (把自己的framework加上去, 那么framework里的的资源都会顺带被存储在main bundle里)