① 创建静态库调试项目:
新建一个用于调试静态库的 Project(HcgStaticLibraryTest)
② 创建静态库:
在 Project(HcgStaticLibraryTest) 中添加一个静态库的 Target(HcgStaticLibrary)
它会生成以下几样东西:
③进行静态库代码的开发:
创建一个类 HcgStaticLibraryLog
并在这个类里面添加一个单纯打印字符串的简单方法 log
然后添加需要公开的头文件 HcgStaticLibraryLog.h
④ 编译生成静态库:
将 Target 切换到 HcgStaticLibrary,检查静态库代码生成的目标架构
并使用 Command + B 进行编译(在这里我们选择真机编译)
然后到 Products 中找到编译生成的静态库,Show in Finder 进行查看(看到 XCode 生成了真机环境对应的静态库,里面包含了我们选择公开的头文件)
⑤ 调试和使用静态库:
将 Target 切换到 HcgStaticLibraryTest,在 Build Phases - Link Binary With Libraries 中添加对静态库的引用
并在 ViewController.m 中导入相应的头文件
然后就可以使用静态库里面的方法了
⑥ 打开 .app 文件查找静态库:
找到 Products 文件夹下的 HcgStaticLibraryTest.app 文件,右键显示包内容,并没有看到静态库 libHcgStaticLibrary.a
因为静态库在链接时,会被完整地复制到 App 的 MachO 可执行文件中
① 创建静态 Framework 调试项目:
新建一个用于调试静态 Framework 的 Project(HcgStaticFrameworkDemo)
② 创建静态 Framework:
在 Project(HcgStaticFrameworkDemo) 中添加一个 Framework 的 Target(HcgStaticFramework)
它会生成以下几样东西(其中 HcgStaticFrameworkTests 是用于对 HcgStaticFramework 进行单元测试的 Target):
因为 Framework 实际上是 Cocoa / Cocoa Touch 程序中使用的一种资源打包的方式,里面存储的既可以是静态库也可以是动态库
又因为 XCode 生成的 Framework 默认是动态库
所以需要到 Build Settings - Linking - MachO Type 中,将 Framework 的类型改为静态库
③ 进行静态 Framework 代码的开发:
创建一个类 HcgStaticFrameworkLog
并在这个类里面添加一个单纯打印字符串的简单方法 log
然后添加需要公开的头文件 HcgStaticFrameworkLog.h
这里有个细节:
需要在与 Framework 同名的头文件中(在这里是 HcgStaticFramework.h)导入所有需要公开的头文件
否则,使用 Framework 下相应的头文件时,会报 missing module 的警告
④ 编译生成的静态 Framework:
将 Target 切换到 HcgStaticFramework ,检查静态 Framework 代码生成的目标架构
并使用 Command + B 进行编译(在这里我们选择真机编译)
然后到 Products 中找到编译生成的静态 Framework,Show in Finder 进行查看(看到 XCode 生成了真机环境对应的静态 Framework,里面包含了我们选择公开的头文件)
⑤ 调试和使用静态 Framework:
将 Target 切换到 HcgStaticFrameworkDemo,在 Build Phases - Link Binary With Libraries 中添加对静态 Framework 的引用(XCode 默认会帮我们添加好对于 Framework 的引用)
并在 ViewController.m 中导入相应的头文件
然后就可以使用静态库里面的方法了
⑥ 打开 .app 文件查找静态 Framework:
找到 Products 文件夹下的 HcgStaticFrameworkDemo.app 文件,右键显示包内容,可以在 Frameworks 目录下找到 HcgStaticFramework.framework
注意:不管是静态 Farmework 还是动态 Framework,在 Command + B 的时候,都会被拷贝到 .app 文件的 Frameworks 目录里面
① 创建动态库调试项目:
新建一个用于调试动态库的 Project(HcgDylibDemo)
② 创建动态库:
在 Project(HcgDylibDemo)中添加一个动态库的 Target(HcgDylib)
iOS 下并不存在创建 dylib 动态库的模板
创建 dylib 动态库的模板位于 macOS - Framework & Library 下
从这一点可以看出,苹果不建议开发者在 iOS 项目中使用动态库
它会生成以下几样东西:
因为,dylib 动态库默认是给 macOS 平台上的 App 使用的
所以,Target(HcgDylib)里面的相关配置,默认都是 macOS 平台的
为了让动态库(libHcgDylib.dylib)能够在 iOS 平台上使用,需要修改 Target(HcgDylib)里面的一些配置:
③ 进行动态库代码的开发:
创建一个类 HcgDylibLog
并在这个类里面添加一个单纯打印字符串的简单方法 log
然后添加需要公开的头文件 HcgDylibLog.h
④ 编译生成的动态库:
将 Target 切换到 HcgDylib,检查动态库代码生成的目标架构
并使用 Command + B 进行编译(在这里我们选择真机编译)
然后到 Products 中找到编译生成的动态库,Show in Finder 进行查看(看到 XCode 生成了真机环境对应的动态库,里面包含了我们选择公开的头文件)
⑤ 调试和使用动态库:
将 Target 切换到 HcgDylibDemo,在 Build Phases - Link Binary With Libraries 中添加对动态库的引用
因为,动态库(libHcgDylib.dylib)在 Target(HcgDylibDemo)进行编译的时候,不会自动拷贝到安装包(HcgDylibDemo.app)中
所以,需要在 Target(HcgDylibDemo)的 Build Phases 中,新建一个 Copy File,使用 Copy File 在编译阶段将动态库(libHcgDylib.dylib)拷贝到 Target(HcgDylibDemo)安装包(HcgDylibDemo.app)中
(这里有个细节需要注意:Copy File 的 SubPath 需要和动态库 Target(HcgDylib) - Build Settings - Deployment - Installation Directory 中设置的目录保持一致,否则 dyld 会无法加载动态库 libHcgDylib.dylib)
Target(HcgDylibDemo)中引用动态库(libHcgDylib.dylib)的相关配置都设置完毕之后
在 ViewController.m 中导入相应的头文件
然后就可以使用动态库里面的方法了
⑥ 打开 .app 文件查找动态库:
找到 Products 文件夹下的 HcgDylibDemo.app 文件,右键显示包内容,看到动态库被拷贝到了我们指定的目录下(@executable_path/HcgFrameworks)
① 创建动态 Framework 调试项目:
新建一个用于调试动态 Framework 的 Project(HcgEmbeddedFrameworkDemo)
② 创建动态 Framework:
在 Project(HcgEmbeddedFrameworkDemo)中添加一个 Framework 的 Target(HcgEmbeddedFramework)
它会生成以下几样东西(其中 HcgEmbeddedFrameworkTests 是用于对 HcgEmbeddedFramework 进行单元测试的 Target):
Framework 实际上是 Cocoa / Cocoa Touch 程序中使用的一种资源打包的方式,里面存储的既可以是静态库也可以是动态库
虽然,XCode 生成的 Framework 默认是动态库
但是,为了严谨,还是到 Build Settings - Linking - MachO Type,检查一下 Framework 的类型
③ 进行动态 Framework 代码的开发:
创建一个类 HcgEmbeddedFrameworkLog
并在这个类里面添加一个单纯打印字符串的简单方法 log
然后添加需要公开的头文件 HcgEmbeddedFrameworkLog.h
这里有个细节:
需要在与 Framework 同名的头文件中(在这里是 HcgEmbeddedFramework.h)导入所有需要公开的头文件
否则,使用 Framework 下相应的头文件时,会报 missing module 的警告
④ 编译生成的动态 Framework:
将 Target 切换到 HcgEmbeddedFramework ,检查动态 Framework 代码生成的目标架构
并使用 Command + B 进行编译(在这里我们选择真机编译)
然后到 Products 中找到编译生成的动态 Framework,Show in Finder 进行查看(看到 XCode 生成了真机环境对应的动态 Framework,里面包含了我们选择公开的头文件)
⑤ 调试和使用动态 Framework:
将 Target 切换到 HcgEmbeddedFrameworkDemo,在 Build Phases - Link Binary With Libraries 中添加对动态 Framework 的引用(XCode 默认会帮我们添加好对于 Framework 的引用)
并在 ViewController.m 中导入相应的头文件
然后就可以使用动态库里面的方法了
⑥ 打开 .app 文件查找动态 Framework:
找到 Products 文件夹下的 HcgEmbeddedFrameworkDemo.app 文件,右键显示包内容,可以在 Frameworks 目录下找到 HcgEmbeddedFramework.framework
注意:不管是静态 Farmework 还是动态 Framework,在 Command + B 的时候,都会被拷贝到 .app 文件的 Frameworks 目录里面
iOS 设备的 CPU 架构
// iOS 设备所对应的 CPU 架构
2013 A7 芯片 arm64 : iPhone 5S
2014 A8 芯片 arm64 : iPhone 6、iPhone 6 Plus
2015 A9 芯片 arm64 : iPhone 6S、iPhone 6S Plus
2016 A10 芯片 arm64 : iPhone 7、iPhone 7 Plus、iPad (2018)
2017 A11 芯片 arm64 : iPhone 8、iPhone 8 Plus、iPhone X
2018 A12 芯片 arm64e : iphone XS、iphone XS Max、iphoneXR
armv7 : iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
armv7s : iPhone5|iPhone5C|iPad4(iPad with Retina Display)
// 模拟器和真机输出的可执行文件的架构类型
32 bit 模拟器:Intel i386
64 bit 模拟器:Intel x86_64
32 bit 真机 : armv7 / armv7s
64 bit 真机 : arm64 / arm64e
合并 .a 格式的静态库
将两个单一平台的静态库:libHcgStaticLibrary_armv7s.a、libHcgStaticLibrary_arm64.a
合并成一个通用平台的静态库:libHcgStaticLibrary_standard.a
再配合上头文件,就可以提供给其他人调用
/Users/Airths/Desktop/StaticLibrary
~/Desktop/StaticLibrary > lipo -info libHcgStaticLibrary_armv7s.a
Non-fat file: libHcgStaticLibrary_armv7s.a is architecture: armv7s
~/Desktop/StaticLibrary > lipo -info libHcgStaticLibrary_arm64.a
Non-fat file: libHcgStaticLibrary_arm64.a is architecture: arm64
~/Desktop/StaticLibrary > lipo -create libHcgStaticLibrary_armv7s.a libHcgStaticLibrary_arm64.a -o libHcgStaticLibrary_standard.a
~/Desktop/StaticLibrary > lipo -info libHcgStaticLibrary_standard.a
Architectures in the fat file: libHcgStaticLibrary_standard.a are: armv7s arm64
合并 .dylib 格式的动态库
将两个单一平台的动态库:libHcgDylib_armv7s.dylib、libHcgDylib_arm64.dylib
合并成一个通用平台的动态库:libHcgDylib_standard.dylib
再配合上头文件,就可以提供给其他人调用
/Users/Airths/Desktop/dylib
~/Desktop/dylib > lipo -info libHcgDylib_armv7s.dylib
Non-fat file: libHcgDylib_armv7s.dylib is architecture: armv7s
~/Desktop/dylib > lipo -info libHcgDylib_arm64.dylib
Non-fat file: libHcgDylib_arm64.dylib is architecture: arm64
~/Desktop/dylib > lipo -create libHcgDylib_armv7s.dylib libHcgDylib_arm64.dylib -o libHcgDylib_standard.dylib
~/Desktop/dylib > lipo -info libHcgDylib_standard.dylib
Architectures in the fat file: libHcgDylib_standard.dylib are: armv7s arm64
合并 .framework 格式的库(静态库或者动态库)
Framework 实际上是 Cocoa / Cocoa Touch 程序中使用的一种资源打包的方式,可以将二进制代码文件、头文件、资源文件、说明文档等按一定的结构打包在一起,方便管理和分发
严格意义上讲,Framework 与 库(静态库 .a | 动态库 .dylib)这两个概念不在同一个维度上。Framework 不是库,它只是一种打包方式,它既可以是静态库也可以是动态库
因此,合并 Framework 格式的库,实际上合并的是 Framework 目录里面的二进制代码文件
并且,为了符合 Framework 的文件结构,需要将合并完的结果,替换回原来的 Framework 目录中
/Users/Airths/Desktop/dylibFramework
~/Desktop/dylibFramework > lipo -info HcgEmbeddedFramework_armv7s
Non-fat file: HcgEmbeddedFramework_armv7s is architecture: armv7s
~/Desktop/dylibFramework > lipo -info HcgEmbeddedFramework_arm64
Non-fat file: HcgEmbeddedFramework_arm64 is architecture: arm64
~/Desktop/dylibFramework > lipo -create HcgEmbeddedFramework_armv7s HcgEmbeddedFramework_arm64 -o HcgEmbeddedFramework
~/Desktop/dylibFramework > lipo -info HcgEmbeddedFramework
Architectures in the fat file: HcgEmbeddedFramework are: armv7s arm64
注意
① lipo 命令用于查看、拆分、合并 machO 格式的文件
.a 格式的静态库、.dylib 格式的动态库、.framework 格式的库(里面的二进制可执行文件)都是 machO 文件
② 我们看到:
支持 i386 和 x86_64 架构的库只能用于模拟器,在真机上调用会报错
支持 armv7 和 arm64 架构的库只能用于真机,在模拟器上调用会报错
如果在开发阶段,为了调试方便(模拟器调试 + 真机调试),可以使用 lipo 命令合并模拟器和真机上的同一个库
即,lipo 命令可以将 i386、x86_64、armv7、armv7s、arm64、arm64e 这些平台的库,合并成一个通用库
dylib(动态库)的 Buling Settings - Linking - Dynamic Library Install Name
Dynamic Library Install Name 本质上是一个路径,用于告诉动态链接器在运行时到哪里寻找需要的库
比如,动态库 libFoo.dylib 的 Dynamic Library Install Name 为(/usr/lib/libFoo.dylib)
那么 libFoo.dylib 在编译阶段被"链接"到 bar.app 上时, libFoo.dylib 的 Dynamic Library Install Name 会被拷贝到 bar.app 的 MachO 文件里面
当 bar.app 在运行过程中需要调用 libFoo.dylib 的时候,动态链接器会从 bar.app 的 MachO 文件里面找到 libFoo.dylib 的 Dynamic Library Install Name,并到相应路径下(/usr/lib/libFoo.dylib)寻找和加载 libFoo.dylib 这个库
这里有个细节需要注意:
在 XCode 中 dylib(动态库)的 Buling Settings - Linking 下
Dynamic Library Install Name 被设置为宏 $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)
Dynamic Library Install Name Base 被设置为宏 $(INSTALL_PATH)
而 dylib(动态库)的 Buling Settings - Deployment 下
Installation Directory 即为宏 $(INSTALL_PATH)
这也是为什么,在上面 [使用 XCode 创建 .dylib 格式的动态库] 这一小节中
可以通过修改 Installation Directory 来修改动态库安装路径的原因
dylib(动态库)的安装路径(Installation Directory / Dynamic Library Install Name / Dynamic Library Install Name Base)
可以通过下列 4 种方式设置:
① Absolute paths(绝对路径)
② @executable_path
③ @loader_path
④ @rpath
Absolute paths(绝对路径)
适用于安装在共享目录下的动态库(共享目录的路径是固定的),例如:
Install path = /Library/Frameworks/Foo.framework/Versions/A/Foo
@executable_path
适用于内置在应用程序包里面的动态库(此时,动态库相对于应用程序可执行文件的位置是固定的)
@executable_path 将被解析成应用程序可执行文件所在的目录
路径名称 | 路径值 |
---|---|
Install path | @executable_path/…/Frameworks/Foo.framework/Versions/A/Foo |
Application location | /Applications/Foo.app |
Executable path | /Applications/Foo.app/Contents/MacOS |
Framework location | /Applications/Foo.app/Contents/Frameworks/Foo.framework |
动态链接器根据以上路径信息最终解解析出来的 Install Path | /Applications/Foo.app/Contents/MacOS/…/Frameworks/Foo.framework/Versions/A/Foo |
@loader_path
适用于内置在 Plug-Ins 里面的动态库(此时,动态库相对于 Plug-Ins 代码的位置是固定的)
@loader_path 将被解析成调用动态库的可执行文件所在的目录
因为,Plug-Ins 无法事先知道它将被安装在哪里,也无法知道调用它的程序的位置
所以,在这种情况下使用 @executable_path 无法达到我们想要的效果
路径名称 | 路径值 |
---|---|
Install path | @loader_path/…/Frameworks/Foo.framework/Versions/A/Foo |
Application location | /Applications/Foo.app |
Plug-in location | /Library/Application Support/Foo/Plug-Ins/Bar.bundle |
Executable path | /Applications/Foo.app/Contents/MacOS |
Loader path | /Library/Application Support/Foo/Plug-Ins/Bar.bundle/Contents/MacOS |
Framework location | /Library/Application Support/Foo/Plug-Ins/Bar.bundle/Contents/Frameworks/Foo.framework |
动态链接器根据以上路径信息最终解解析出来的 Install Path | /Library/Application Support/Foo/Plug-Ins/Bar.bundle/Contents/MacOS/…/Frameworks/Foo.framework/Versions/A/Foo |
注意:
如果动态库的调用者不是 Plug-Ins,而是应用程序的可执行文件
那么,@loader_path == @executable_path
@rpath
我们来理一理:
① Absolute paths 适用于安装在共享目录下的动态库,但不适用于内置在应用程序内的动态库
② @executable_path 适用于内置在应用程序内的动态库,但不适用于内置在 Plug-Ins 里面的动态库
③ @loader_path 适用于内置在 Plug-Ins 里面的动态库,也适用于内置在应用程序里面的动态库
那么,是不是今后只要把所有的动态库的安装位置(Install path)都设置为 @loader_path 相关的,就一劳永逸了呢?
我们思考一个问题:
虽然 @executable_path 和 @loader_path 可以避免动态库的安装位置(Install path)直接使用绝对路径所带来的问题,增加了动态库安装位置(Install path)设置时的灵活性
但是 @executable_path 和 @loader_path 所带来的灵活性是有限的。因为不论是 @executable_path,还是 @loader_path,都要求动态库相对于可执行文件的位置是固定的(即要求动态库的安装位置相对于可执行文件要有固定的层级结构)
假设有两个应用,Bar.app 与 Lar.app,使用同一个动态库 libFoo.dylib
Bar.app 要求动态库安装在 /Applications/Bar.app/Contents/MacOS/Library 下
Lar.app 要求动态库安装在 /Applications/Lar.app/Contents/Library 下
此时动态库的安装位置(Install path),无论是使用 @executable_path 或者 @loader_path,都不能同时满足这两个应用的需求,除非重新设置动态库的安装位置(Install path),再打包出一个新的动态库
造成这个问题的根本原因是:
动态库的主要作用是在不同的应用程序之间共享代码,以优化整体的性能
在开发动态库的时候,我们不能确切地知道各个调用者将会如何使用动态库,将会把动态库安装在哪里,动态库安装的目录层次结构是否相同
因此,在动态库中单方面确定动态库的安装位置(Install path)是不合适的,这在设计上不够灵活
既然动态库是供不同应用程序调用的,那么动态库的安装位置(Install path)就应该与调用它的每个一应用程序分别商定,并把安装位置(Install path)的商定结果放在调用动态库的应用程序上
① @rpath 是一组存放在应用程序中的路径列表,用于告诉动态链接器到哪里寻找动态库(动态库需要以 @rpath 作为其 Install Path 的开头,才会触发动态链接器进行 @rpath 路径检索)。@rpath 的作用类似于系统环境变量 PATH
② 当动态库使用 @rpath 作为 Install Path 的时候,调用动态库的应用程序需要到自己的 Build Settings - Linking - Runpath Search Paths 中设置 @rpath 的详细路径列表
③ 使用 @rpath 作为 Install Path 的动态库,可以被不同的应用程序所调用,而不用受限于 @executable_path 或 @loader_path(因为不同的应用程序可以设置适合自己的 @rpath)