Xcode 工程添加 “动态” Framework 的几种方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Xcode 工程添加 “动态” Framework 的几种方式相关的知识,希望对你有一定的参考价值。
参考技术A如何在 Xcode 工程中添加动态库(Dynamic Library)。
首先我们知道,对于 “.a 静态库” 和 “静态 framework”,直接把相关文件拖拽到工程中,并勾选 Copy if needed 选项即可,无需其它额外的设置;
而对于添加“动态 framework”,稍微比较麻烦,主要有以下几种方式。
PS:我们这里说的“添加动态库”是指第三方动态库,而不是像 UIKit.framework、Foundation.framework 或者 libc++ 等系统自带的动态库,对于它们的依赖添加很简单,直接在 General -> Linked Frameworks and Libraries 中点击加号搜索添加即可。
在 Xcode 工程中选中 app 对应的 target,然后在 General -> Embedded Binaries 下点击加号,如图 1,在弹出的窗口选择 Add Other…,最后在 Finder 中选择你要添加的“动态 framework”,并勾选 Copy if needed 即可。需要注意的是,你不能直接在 Finder 中把 .framework 文件拖拽到 Embedded Binaries 中,否则会报错。
关于手动添加动态库的更多细节以及遇到问题的解决办法,可参考苹果官方的教程: 《Embedding Frameworks In An App》
但是!这种方式看似很方便,其实有个坑是:我们上一条小集提到,一般动态二进制文件都会包含很多处理器架构,例如:i386, x86_64, armv7, armv7s, arm64 等,然后 Xcode 在编译链接时,对动态二进制文件是直接拷贝到 .ipa 包中,并不会像链接静态库那样筛选掉未用到 architecture,而苹果又不允许把包含 i386, x86_64 等模拟器架构的包上传到 App Store Connect 后台,会报错。因此,我们在打 Release 正式包时往往需要手动通过 lipo 命令或者编写脚本移除掉这些 Invalid Architectures。(除非你的开发工程只通过真机来调试,不准备在模拟器里运行,且添加的动态库刚好又不包含 i386、x86_64)
对于通过 Carthage 集成的第三方库,在 Cartfile 文件中添加好依赖后,然后执行 carthage update 命令会帮我们生成一个个“动态 framework”,例如 AFNetworking.framework、SDWebImage.framework 等,然后把它们拖拽到工程中的 General -> Linked Frameworks and Libraries ,然后在配置相关拷贝脚本和命令,详细可参考 Carthage 的 Quick Start 教程。
这里有个关键操作是,需要在 Xcode 工程的 Build Phases 中添加一个执行脚本(New Run Script Phase),并在脚本中执行如下命令:
如图 2 所示:
该命令的作用大概就是,在打包拷贝动态库时自动帮我们移除掉其中的 i386、x86_64。
同样地,通过 CocoaPods 集成动态库时,也会在工程中自动帮我们添加一个 Shell 脚本用于做这件事,如图 3 中的 [CP] Embed Pods Frameworks,
大家可以自行查阅该 Pods-xxx-frameworks.sh 脚本的内容,里面有个函数 strip_invalid_archs() 就是用于在打包时移除无用的处理器架构,如图 4:
因此,我们可以把自己开发的或者他人提供的动态 framework,通过 CocoaPods 来集成到工程中:创建一个 Pods 私有 git 库(相信大家已经很熟悉了),在 git 库中添加相关动态 .frameworks 文件,然后其 Podspec 文件的写法大致如图 5 所示,最后在你的工程中 pod install 即可。
最后我们思考一个问题:“静态 framework” 和 “动态 framework” 在使用上似乎也没什么不同,而工程添加 “动态 framework” 又比较繁琐,那么在 ios/macOS 开发中什么情况下会使用动态库呢?
Xcode制作动态及静态Framework和各种坑
有没有写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/78571-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文件中
以上是关于Xcode 工程添加 “动态” Framework 的几种方式的主要内容,如果未能解决你的问题,请参考以下文章