- 库
- 库的分类
- 静态库
- 动态库
- 静态库与动态库的区别
.a与.framework区别- 制作静态库.a
- 制作静态库 framework
- 动态库
- 添加资源
- Framework
- Create the path to the real Headers die
- Create the required symlinks
- Copy the public headers into the framework
- 推荐方案:CocoaPods 库
- Uncomment the next line to define a global platform for your project
- 指明库podspec的路径,这里是BFDynamicFramework.podspec的路径,在此,BFDynamicFramework和BFTestFramework同级,所以path是../BFDynamicFramework
- Workaround for Cocoapods issue #7606
库
库就是程序代码的集合,将N个文件组织起来,是共享程序代码的一种方式。库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库实现了程序某个功能模块的模块化,它便于我们共享(公用)、维护、独立和安全。
一般分为静态库和动态库。
- 静态库:以
.a和.framework为文件后缀名。 - 动态库:以
.tbd(之前叫.dylib) 和.framework为文件后缀名。(系统直接提供给我们的framework都是动态库!)
库的分类
- 开源库:源代码是公开的,可以看到每个实现文件(.m文件)的实现,例如GitHub上的常用的开源库:AFNetworking、SDWebImage等;
- 闭源库:不公开源代码,是经过编译后的二进制文件,看不到具体的实现。闭源库又分为:静态库和动态库
静态库
静态库链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
动态库
动态库链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
- Framework 是 Cocoa/Cocoa Touch 程序中使用的一种资源打包方式,可以将代码文件、头文件、资源文件(nib/xib、图片、国际化文本)、说明文档等集中在一起,方便开发者使用。Framework 其实是资源打包的方式,和静态库动态库的本质是没有什么关系。
- 在 iOS 8 之前,iOS 平台不支持使用动态 Framework,开发者可以使用的 Framework 只有苹果系统提供的 UIKit.Framework,Foundation.Framework 等。开发者要进行模块化,只能是打包成静态库 .a 文件,同时附上头文件。但是这种方式打包不够方便,使用时也比较麻烦,没有 Framework 的便捷性。但是这时候的 Framework 只支持打包成静态库的 Framework。
- iOS 8/Xcode 6 推出之后,添加了动态库的支持,Xcode 6 支持动态 Framework 动态 Framework 和系统的 UIKit.Framework 还是有很大区别。系统的 Framework 不需要拷贝到目标程序中,是一个链接,而开发者打包的 Framework,还是要拷贝到 App 中,因此苹果又把这种 Framework 称为 Embedded Framework(可植入性 Framework)。
静态库与动态库的区别
| | 静态库 | 动态库 | | :—- | :—- | :—- | | 定义 | 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。 | 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。 | | 形式 |.a、.framework|.dylib、.tbd和.framework| | 场景 | 避免少量改动经常导致大量的重复编译连接 | 使用动态库,可缩小最终可执行文件体积
使用动态库,多个应用程序共享内存中得同一份库文件,节省资源
使用动态库,可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的 |
.a 与.framework 区别
两者关系是:
.a + .h + sourceFile = .framework
| .a | .framework | |
|---|---|---|
| 文件 | 是一个纯二进制文件 | 除了有二进制文件之外还有资源文件 |
| 使用 | 文件不能直接使用,至少要有.h 文件配合 | 文件可以直接使用 |
- 无论是
.a静态库还.framework静态库,我们需要的都是 “二进制文件 +.h + 其它资源文件” 的形式,不同的是,.a本身就是二进制文件,需要我们自己配上.h和其它文件才能使用,而.framework本身已经包含了.h 和其它文件,可以直接使用。 - 图片资源的处理:两种静态库,一般都是把图片文件单独的放在一个
.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。.bundle文件新建一个文件夹,把它改名为.bundle就可以了,右键,显示包内容可以向其中添加图片资源。 - 如果一个静态库很复杂,需要暴露的
.h比较多的话,就可以在静态库的内部创建一个.h文件(一般这个.h文件的名字和静态库的名字相同),然后把所有需要暴露的.h 文件都集中放在这个.h文件中,而原来都不需要再暴露了。制作静态库.a
一、简单静态库
简单静态库指的是,不包含其他静态库的静态库,常用于封装一些基础的工具类。1. 创建静态库工程

然后直接编译,可以得到.a 为后缀的静态库。
屏幕快照 2018-09-19 上午9.28.03
2. 编译静态库
设置 Edit Scheme -> Build Configuration-> 选为 Release。
完成代码编写,并选中 Device 为真机编译,还是模拟器编译,不同的 Device 下编译出的静态库是不同的,真机编译可能在模拟器中使用会 crash,同样模拟器编译出来在真机运行中会发生问题。
导出后,可以查看对应的库信息:
$ lipo -info libBFStaticLib_device.ainput file libBFStaticLib_device.a is not a fat fileNon-fat file: libBFStaticLib_device.a is architecture: arm64$ lipo -info libBFStaticLib_simulator.ainput file libBFStaticLib_simulator.a is not a fat fileNon-fat file: libBFStaticLib_simulator.a is architecture: x86_64
3. 合并静态库
上一步生成的不同指令集下的静态库,需要合并才能支持多设备,如下:
//lipo -create ***.a ***.a -output output_lib_path$ lipo -create libBFStaticLib_simulator.a libBFStaticLib_device.a -output ./libBFStaticLib$ lipo -info libBFStaticLibArchitectures in the fat file: libBFStaticLib are: x86_64 arm64
4. 相关设置
a. 添加 static library 的头文件
在 Targets->build settings->user header search paths 设置 static library 的头文件搜索路径
b. arc/mrc
可设置对应的文件是否开启 ARC:-fobjc-arc/-fno-objc-arc。
制作静态库 framework
一、创建工程及工程设置

创建一个项目名称为:BFStaticFramework 的静态库。
其工程设置中,如下
- 在 Linking 里,将
Dead Code Stripping设置为 NO,用于删除对象文件中不需要加载的符号,减小二进制文件大小 Link With Standard Libraries设置为 NO,链接使用标准静态库。- 将
Mach-O Type改为 Static Librariy,选择 Mach-O 的类型。
二、添加头文件
将需要暴露的头文件放在 Build Phases 对应的分组下:
- Public:对外暴露的头文件。
- Private:查看
Framework下的头文件,依然是暴露出来的。 - Project:该分组头文件对工程来说才是 “私有” 的
所以,将你的头文件或者在 Public 下,或者在 Project 下。尽可能少的暴露头文件,将其他类放在 Project 中。
三、导出 Framework
添加一个脚本:
屏幕快照 2018-10-17 下午4.07.14
为 OutputFramework 添加一个脚本
# 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 theframework creation.WRK_DIR=buildDEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.frameworkSIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework# -configuration ${CONFIGURATION}# Clean and Building both architectures.# 在Xcode 10时,去掉下面每一句末尾的clean build,因为会每次都清空build文件夹,导致最后到处的framework为空xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneosxcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator# Cleaning the oldest.if [ -d "${INSTALL_DIR}" ]thenrm -rf "${INSTALL_DIR}"fimkdir -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}"
运行脚本,输出 Framework,记住,一定要选中真机,否则生成的架构只支持 x86_64 arm64 。
四、验证
$ cd framework路径$ lipo -info BFStaticFrameworkArchitectures in the fat file: BFStaticFramework are: armv7 i386 x86_64 arm64
使用,直接拖入工程代码即可。
动态库
一、制作
制作动态库大体流程和制作静态库 framework 一致,只需要在第一步中工程配置中,将 Mach-O Type 改为 **Dynamic Librariy**。
二、使用
将动态库拖入工程中,直接运行,会出现下面错误
dyld: Library not loaded: @rpath/BFDynamicFramework.framework/BFDynamicFramework Referenced from: /Users/wenghengcong/Library/Developer/CoreSimulator/Devices/
注意这一点与静态库有所不同!
如何解决?
- 方案一:需要将该库,添加到
Embedde Binaries中。
- 方案二:尝试将动态库的 Status 的状态修改为
Optional,改成 Optional 有效的前提是动态库中不包含分类,或者包含了分类但是未进行调用。否则,只能按方案一。
添加资源
一、创建 Bundle Target
在当前工程中,新建一个 Target。
屏幕快照 2018-10-18 下午6.14.40
二、Build Settings 设置
下面点击 Build Settings,进行一些设置:
Base SDK: 选中,并按下 Delete,就会默认使用最新的 iOS SDK。Supported Platforms:选中 iOSProduct Name:替换为BFStaticFrameworkEnable Bitcode:设置为 NO。
如果不设置,可能会遇到错:ld: -bundle and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used togetherCOMBINE_HIDPI_IMAGES:设置 NO
经测试在高版本的 XCode 中,设置为 YES 也不会导致,打包出的 framework 中的png图片格式变为tiff。
所以一定要修改。
三、编译出包
1. 直接出包
编译出包,得到 BFStaticFramework.bundle。
为了确保编译 BFStaticFramework.framework 时,也编译 BFStaticFramework.bundle,在之前输出 Framework 的 target 下,添加 target dependencies,如下:
并且,为了是的 bundle 文件和 Framework 在同一目录下。
在 OutputFramework 对应的脚本中添加:
.....# Copy the resources bundleditto "${BUILT_PRODUCTS_DIR}/${FMK_NAME}.bundle" \"${SRCROOT}/Products/${FMK_NAME}.bundle"open "${INSTALL_DIR}"
2. 导入 Framework
进一步,如果要将 bundle 文件打入 Framework 包中:
首先,将上一步中得到的 bundle 文件直接拖入 BFStaticFramework 工程中,并选中不要 Copy items if needed。
四、使用
如果,需要将对应的 bundle 拖到对应的工程中去使用:
UIImage *image = [UIImage imageNamed:@"BFStaticFramework.bundle/qq"];//orUIImage *image = [UIImage imageNamed:@"qq" inBundle:[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"BFStaticFramework" ofType: @"bundle"]] compatibleWithTraitCollection:nil];
或者新建分类:
//.h#import <UIKit/UIKit.h>@interface UIImage (Resource)+ (UIImage *)resourceImageNamed:(NSString *)name;@end/.m#import "UIImage+Resource.h"@implementation UIImage (Resource)+ (UIImage *)resourceImageNamed:(NSString *)name{//先从默认目录里读UIImage *imageFromMainBundle = [UIImage imageNamed:name];if (imageFromMainBundle) {return imageFromMainBundle;}//读不到再去Bundle里读//此处Scale是判断图片是@2x还是@3xNSInteger scale = (NSInteger)[[UIScreen mainScreen] scale];for (NSInteger i = scale; i >= 1; i--) {NSString *filepath = [self getImagePath:name scale:i];UIImage *tempImage = [UIImage imageWithContentsOfFile:filepath];if (tempImage) {return tempImage;}}return nil;}+ (NSString *)getImagePath:(NSString *)name scale:(NSInteger)scale{NSURL *bundleUrl = [[NSBundle mainBundle] URLForResource:@"Resource" withExtension:@"bundle"];NSBundle *customBundle = [NSBundle bundleWithURL:bundleUrl];NSString *bundlePath = [customBundle bundlePath];NSString *imgPath = [bundlePath stringByAppendingPathComponent:name];NSString *pathExtension = [imgPath pathExtension];//没有后缀加上PNG后缀if (!pathExtension || pathExtension.length == 0) {pathExtension = @"png";}//Scale是根据屏幕不同选择使用@2x还是@3x的图片NSString *imageName = nil;if (scale == 1) {imageName = [NSString stringWithFormat:@"%@.%@", [[imgPath lastPathComponent] stringByDeletingPathExtension], pathExtension];}else {imageName = [NSString stringWithFormat:@"%@@%ldx.%@", [[imgPath lastPathComponent] stringByDeletingPathExtension], (long)scale, pathExtension];}//返回删掉旧名称加上新名称的路径return [[imgPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:imageName];}@end
Framework
上面我们讲述了如何制作,使用动态库和静态库的 Framework,下面我们针对 Framework 本身进行一些探讨。
配置
| Key | Description |
|---|---|
CFBundleName |
The framework display name |
CFBundleIdentifier |
The framework identifier (as a Java-style package name) |
CFBundleVersion |
The framework version |
CFBundleExecutable |
The framework shared library |
CFBundleSignature |
The framework signature |
CFBundlePackageType |
The framework package type (which is always 'FMWK') |
NSHumanReadableCopyright |
Copyright information for the framework |
CFBundleGetInfoString |
A descriptive string for the Finder |
结构
下面以 Alamofire 作为参考,目录如下::
$ tree -a -L 3.├── Alamofire -> Versions/Current/Alamofire├── Headers -> Versions/Current/Headers├── Modules -> Versions/Current/Modules├── Resources -> Versions/Current/Resources└── Versions├── A│ ├── Alamofire│ ├── Headers│ ├── Modules│ └── Resources└── Current -> A
而我们上面制作的,以 BFDynamicFramework 为例,结构如下:
.├── .DS_Store└── BFDynamicFramework.framework├── BFDynamicFramework├── Headers│ ├── BFDeviceUtils.h│ ├── BFDynamicFramework.h│ └── UIView+Frame.h├── Info.plist└── Modules└── module.modulemap
如何将我们默认的工程结构,修改成 Alamofire 类似的结构呢?
下面调试中的两种方法都可以做到。
- 子工程调试: 手动写脚本,改变 Framework 结构。
进入 target,选中 BFStaticFramework->Build Phases->Add “New Run Script Phases”
```bash set -e export FRAMEWORK_LOCN=”${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework”
Create the path to the real Headers die
mkdir -p “${FRAMEWORK_LOCN}/Versions/A/Headers”
Create the required symlinks
/bin/ln -sfh A “${FRAMEWORK_LOCN}/Versions/Current” /bin/ln -sfh Versions/Current/Headers “${FRAMEWORK_LOCN}/Headers” /bin/ln -sfh “Versions/Current/${PRODUCT_NAME}” \ “${FRAMEWORK_LOCN}/${PRODUCT_NAME}”
Copy the public headers into the framework
/bin/cp -a “${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/“ \ “${FRAMEWORK_LOCN}/Versions/A/Headers”
之后,编译输出的结构就会更改:```bash$ tree -L 3.├── BFStaticFramework├── Headers│ ├── BFDeviceUtils.h│ ├── BFStaticFramework.h│ ├── Headers -> Versions/Current/Headers│ └── UIView+Frame.h├── Info.plist└── Versions├── A│ └── Headers└── Current -> A
Cocoapods:自动生成规范的目录结构。
调试
在前面,我们创建了 BFStaticFramework 静态库(也适用于动态库),现在我们创建一个调试工程 BFTestFramework。
打开 BFTestFramework,将 BFStaticFramework.xcodeproj 拖入 BFTestFramework,注意,拖入是,一定要确保 Xcode 没有打开 BFStaticFramework.xcodeproj,因为同一个工程,不能再两个窗口中打开。如下图:
现在,要将 BFStaticFramework 静态库链接到 BFTestFramework,点击 TARGETS-BFTestFramework,Build Phases 下,展开 Link Binary With Libraries 面板,添加 BFTestFramework。
之后,BFStaticFramework 任何的修改,都会同步到测试 demo 中。
推荐方案:CocoaPods 库
BFDynamicFramework 集成到 Cocoadpods,必须要将 BFDynamicFramework 发布,在这里,我们采用本地路径,无须发布到 Cocoapods,podspec 文件以及如何发布库在CocoaPods私有库配置笔记有详细说明。
- 创建库
Podspec文件
进入到 BFDynamicFramework 路径,创建 Podspec 文件
$ cd BFDynamicFramework$ pod spec create BFDynamicFramework
该文件指定了库的基本信息:
Pod::Spec.new do |s|s.name = "BFDynamicFramework"s.version = "0.0.1"s.summary = "build frameworks by cocoapods."s.description = <<-DESCshow you how to build frameworks by cocoapodsDESCs.homepage = "https://github.com/wenghengcong/BeeFunMac"s.license = "MIT"s.author = { "wenghengcong" => "wenghengcong@gamil.com" }s.source = { :path => '.' }# 注意代码路径s.source_files = "Classes", "BFDynamicFramework/Classes/**/*.{h,m}"# 注意资源路径s.resources = "BFDynamicFramework/Assets/*.{png,jpg,xib,storyboard,xcassets}", "BFDynamicFramework/Assets/**/*.{png,jpg,xib,storyboard,xcassets}"end
创建测试工程的 Podfile
进入测试的工程目录:cd BFTestFramework
$ pod init$ tree -a -L 1.├── BFTestFramework├── BFTestFramework.xcodeproj└── Podfile
- PodFile 文件编辑如下:
```ruby
Uncomment the next line to define a global platform for your project
platform :ios, ‘9.0’
target ‘BFTestFramework’ do use_frameworks!
指明库podspec的路径,这里是BFDynamicFramework.podspec的路径,在此,BFDynamicFramework和BFTestFramework同级,所以path是../BFDynamicFramework
pod ‘BFDynamicFramework’, :path => ‘../BFDynamicFramework’
end
Workaround for Cocoapods issue #7606
post_install do |installer| installer.pods_project.build_configurations.each do |config| config.build_settings.delete(‘CODE_SIGNING_ALLOWED’) config.build_settings.delete(‘CODE_SIGNING_REQUIRED’) end end
- 安装集成 `pod install` 如下:[](http://blog-1251606168.file.myqcloud.com/blog/2018-10-18-022412.png)在 `Deveopment Pods` 下集成了我们需要的 `BFDynamicFramework`,之后,我们可以在测试工程中 `BFTestFramework` 直接修改库源码,并会同步到 `BFDynamicFramework` 库。<a name="lmw3c"></a>### 自动化上面集成过程冗余,每次创建一个库,要测试还需要创建一个 Demo app 测试来,需要编辑两个文件:- Podspec:库信息文件- Podfile:Demo app 的引用文件需要保证以上文件正确,然后才能正确发布、测试。<br />最糟心的是两个分开的工程,需要单独维护。<br />那么,如何在一个工程里维护库源码,而且集成一个 Demo app,最好自动化。<br />就是下面的命令,在 [CocoaPods私有库配置笔记](https://www.yuque.com/liboy/ios-study-notes-zgkfns/cocoapods-si-you-ku-pei-zhi-bi-ji?view=doc_embed)有更详细的说明:```ruby$ pod lib create BFTestFramework
以上路径中,Example–就是 Demo app 的可测试工程。对应源码在 Development Pods 中,所有以上,都在一个代码仓库中,可维护性极高。
打包
上面,我们通过将测试与库本身的开发都集中在 Cocoapods 中管理,同样的,我们需要打包,导出 Framework 也采用 Cocoapods 的工具。
// 安装`cocoapods-packager`$ gem install cocoapods-packager//打包$ pod package BFDynamicFramework.podspec --force
其中 package 命令,打包成静态.a 文件:
$ pod package BFDynamicFramework.podspec --library --force
具体查看 BFDynamicFramework.framework 目录,和我们预想的一致:
$ tree -L 4.├── BFDynamicFramework -> Versions/Current/BFDynamicFramework├── Headers -> Versions/Current/Headers├── Resources -> Versions/Current/Resources└── Versions├── A│ ├── BFDynamicFramework│ ├── Headers│ │ └── BFDeviceUtils.h│ └── Resources│ ├── G-symbol.png│ ├── ItemView.xib│ └── Media.xcassets└── Current -> A
而且这种打包出来的格式,是 armv7 armv7s i386 x86_64 arm64 多架构的。
所以,这就是库开发的最佳方案之一。














