前语

咱们之前经过一篇文章在评论“移动客户端架构规划的时分,仅是经过一篇文章进行了总述。
后来,我就换工作,来到了Nike。近半年一向很忙,最近接到一个工单,要求升级SDK。借此关键,简略回顾一下iOS开发包二进制化,并进一步认识一下咱们比较陌生的 XCFramework

一、总述

项目中库的区分:

  • 咱们在实践开发中,若是项目比较巨大,咱们能够将 库 区分为:
    • 私有库: (若是咱们的公司移动开发建造比较成熟,也能够理解为咱们内部移动开发套件的私有库)
    • 二方库: 因项目建造需求而收购的,由 服务 供应商 供给的SDK库。如:埋点服务SDK、阿里的反爬虫服务SDK、OCR扫描服务等。(一般需求付费 或 有限 免费体会 SDK功用)
    • 三方库: 免费运用的外部库(一般来源于Github等开源渠道)

iOS开发中的库的二进制化处理(功用模块闭源处理)

  • 依据是否将库的功用服务完成代码开源,咱们又能够将库 区分为 “开源库”、“闭源库
    • 二进制化: 把功用服务模块的源码 打包成 【闭源库】的开发,也就是 iOS开发包二进制化 处理!!!!(二进制化处理也能够称为SDK开发)
    • 在iOS二进制化开发中,能够将模块打包成静态库/动态库的方式
    • 这些 静态库动态库,能够统称为 二进制库
    • 二进制化库开发完毕后,咱们能够把库集成在一个项目里边进行运用;

1. 二进制化库的几种分类

在iOS开发中的二进制化库能够简略区分为,静态库动态库(.framework)(注:不是一切的.framework就一定是动态库)

  • 静态库:.a 或许 .framework 作为文件的扩展名。链接时完整地拷贝至可执行文件中,被屡次运用就有多份冗余拷贝
  • 动态库:.dylib 或许 .framework 作为文件的扩展名。链接时不仿制,程序运转时由体系动态加载到内存,供程序调用,体系只加载一次,多个程序共用,节省内存。留意
    • 动态库只能苹果运用,假如项目中运用了动态库不允许上架(如:jspatch)
    • 咱们在开发进程中,即便有用到体系动态库的需求,也要常常查看对动态库的引进需求,因为这会影响App的发动速度。(若是不太了解这一块的朋友,能够经过我的这篇文章简略了解一下:App发动装载的进程)
  • .a.framework的差异?
    • .a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件;
    • .a文件不能直接运用,至少要有.h文件配合,.framework文件能够直接运用。
    • .a + .h + sourceFile = .framework
    • 主张用.framework

2. 简略了解CPU架构

咱们在开发好功用服务模块,预备导出二进制化包的时分,咱们要依据 本身投放应用市场的需求硬件适配需求,对库进行的CPU架构的支撑。从而在满意本身需求的一同,防止导出二进制化包的体积过大。

2.1 什么是CPU架构?

CPU架构是CPU厂商给归于同一系列的CPU产品定的一个标准,首要目的是为了区分不同类型CPU的重要标明。

现在市面上的CPU分类首要分有两大阵营,一类是复杂指令集(CISC)CPU,另一类是精简指令集(RISC)CPU。咱们今日要评论的ARM指令集就归于精简指令集(RISC)CPU。

2.2 ARM处理器

ARM处理器是英国Acorn有限公司规划的低功耗本钱的第一款RISC微处理器。全称为Advanced RISC Machine。

咱们之前有一篇文章是关于探究iOS底层原理|ARM64汇编的。其间简略介绍了,不同的型号真机硬件设备的CPU硬件架构:

架构 设备
armv6 iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch
armv7 iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4
armv7s iPhone5, iPhone5C, iPad4(iPad with Retina Display)
arm64 iPhone5S 以后 iPhoneX , iPad Air, iPad mini2以后

ARM处理器因其低功耗和尺寸小而闻名,iPhone的处理器全部都根据ARM。
咱们经常见到的armv7 | armv7s | arm64都是ARM处理器的指令集。
指令集应用于开发有如下特色:

  • 一切指令集原则上向下兼容。
  • Xcode的模拟器是运转在电脑上的,所以iPhone模拟器并没有运用ARM指令集(在Intel芯片的老款Mac上的iPhone模拟器),而是运用的X86(64为处理器)或许i386(32位处理器)。
  • 经过Xcode打包时,会为支撑的一切的指令集编译出对应的指令集代码的数据包,所以工程支撑的指令集越多,生成的二进制包就越大。

2.3 进一步认识 指令集 对应的机型

机型-架构 对照表

架构 设备
armv6 iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch
armv7 iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4
armv7s iPhone5, iPhone5C, iPad4(iPad with Retina Display)
arm64 iPhone 5S、iPhone 6、iPhone 6 Plus、iPhone 6S、iPhone 6S Plus、iPhone 7、 iPhone 7 Plus、 iPad (2018)、 iPhone 8 、 iPhone 8 Plus 、 and iPhone X
arm64e iPhone XS 、iPhone XS Max 、 iPhoneXR、iPhone 11 、 iPhone 11 Pro、 iPhone 11 Pro Max、iPhone 12 、 iPhone 12 Mini 、 iPhone 12 Pro 、 iPhone 12 Pro Max … 等

模拟器

    • 模拟器32位处理器测验需求i386架构
    • 模拟器64位处理器测验需求x86_64架构
    • 模拟器64位处理器测验需求arm64架构(采用了苹果公司内部自行规划的M1系列芯片的新款Mac,该Mac上跑的模拟器架构也是arm64架构,但不是iPhone真机那种arm64)

真机

    • 真机32位处理器需求armv7,或许armv7s架构
    • 真机64位处理器需求arm64架构

应对当时(2023年)市场常用的设备推荐适配 指令集

  • 模拟器: 64位处理器x86_64架构、arm64架构
  • 真机:64位处理器arm64架构、armv7s架构(iPhone5及其以上的设备)

2.4 二进制化库适配架构的设置Build Setting

Build Settings中查找Architecture,能够看到如下图的设置项:

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

  • Architectures
    指定当时Target被编译时支撑的指令集的列表,假如指定了多个架构,机会生成多个架构需求的二进制文件。

  • Build Active Architecture Only
    指定是否只编译当时衔接设备所支撑的指令集。
    默认Debug时为YESRelease时为NO。设置为YES时,编译后只生成当时衔接设备所支撑的指令集代码,编译速度更快。

  • Excluded Architectures
    指定当时Target被编译时回绝接收的指令集列表,编译后终究生成的二进制文件支撑的指令集是Architectures减去Excluded Architectures

二、开发二进制化库的实践

咱们前面有提及 静态库动态库,的差异。咱们这儿就不再重复介绍。
咱们直接进入创立一个.framework静态库的实践(此次我采用的是Xcode14.1,与之前的Xcode版本的创立进程总体上大致相似,略有不同。若是同学们用比较老的Xcode版本,请自行寻找相应教程)

1. 首先创立.frmawork项目

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

framewrok中能够封装入自己需求封装的内容

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】
eg: 我在Logger.h中加入了一个测验办法

//
// Logger.m
// NKTestOCFramework
//
// Created by Van Zhang on 2023/2/27.
//
#import "Logger.h"
@implementation Logger
+ (void)logWithString:(NSString *)log{
    NSLog(@"Log:%@",log);
}
+ (NSString *)testString:(NSString *)string {
    return [@"NKTestOCFramework: " stringByAppendingString:string];
}
@end

2. 接下来进行项目配置:

1、设置Build Setting参数 将Build Active Architecture only设置为NO

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

2、设置Build Setting参数 Mach-O Type 为Static Library (配置静态、动态)

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

3、设置Build Setting参数 在Architectures下增加 需求适配的硬件CPU,如 armv7sarmv7(咱们当下盛行的设备基本都是iPhone5s以上了,一般咱们直接arm64就够用了)

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

4、在Build Phases中设置需求公开和需求隐藏的头文件

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

5、将头文件引进到NKTestOCFramework (自己SDK的头文件)

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

6、Command + B运转项目,在Product中找到framework (在Xcode14创立的工程中,咱们找不到Product文件夹,需求做如下调整:)

  • 1、找到.xcodeproj文件
  • 2、右键显示包内容
  • 3、找到project.pbxproj并双击翻开
  • 4、在文件内查找productRefGroup,周围还有一个mainGroup,把mainGroup后边的值仿制一下替换到productRefGroup后边,然后封闭文件而且保存。

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

3.framework的运用将封装好的.framework拉入需求运用的项目中

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

4.工程运转

想要对Demo进行运转实践的朋友,能够经过以下链接,下载:

  • github.com/VanZhang-CN…

三、二进制包的架构查看,包的兼并与拆分

1、相关概念以及惯例处理

  • 二进制包文件都是 Mach-O 文件的一种(能够经过这篇文章简略了解Mach-O文件)
  • iOS 的 二进制包 文件 分为 fat 二进制文件 和 thin 二进制文件
    • 胖二进制文件:包括多套指令集架构的Mach-O文件(比方一同包括armv7armv7sarmv64i386x86_64其间的两种以上)
    • thin二进制文件: 仅包括一套指令集架构的Mach-O文件
  • 二进制文件处理:
    1. lipo -info: 查看二进制文件支撑的架构
    // 判别静态库所支撑的渠道 armv7 x86_64 arm64
    lipo -info + 二进制文件途径
    
    1. lipo -remove: 移除二进制文件支撑的某架构
    // 判别静态库所支撑的渠道 armv7 x86_64 arm64
    lipo -remove + 要删去的指令集架构 + 二进制文件途径 + -output + 输出的二进制文件途径
    如:
    lipo -remove armv7 origin_xxx.a -output op_xxx.a // 删去静态库包括的armv7渠道
    
    1. lipo -thin: 导出二进制文件支撑的某架构
    // 判别静态库所支撑的渠道 armv7 x86_64 arm64
    lipo -thin + 要导出的指令集架构 + 二进制文件途径 -output + 输出的二进制文件途径
    如:
    lipo -thin arm64 origin_xxx.a -output op_xxx.a // 拆分静态库,只保存arm64 CPU架构
    
    1. lipo -create: 创立一个fat二进制文件使其支撑的多个架构
    // 判别静态库所支撑的渠道 armv7 x86_64 arm64
    lipo -create + 二进制文件途径1 + 二进制文件途径2 -output 输出的二进制文件途径
    如:
    lipo -create device_xxx.a simulator_xxx.a -output universal_xxx.a //对真机或许模拟器分别打出 .a 文件兼并
    

2. 了解XCFramework

快速了解XCFramework:
官方视频链接:developer.apple.com/videos/play… 官方文档链接:developer.apple.com/documentati…

  • XCFramework 是苹果新出的库类型,在 Xcode 11 及 cocoapods 1.9 以上版本被支撑
  • 与普通动态库/静态库最大的差异是将多个渠道的二进制库,绑缚到一个可分发的.xcframework绑缚包中,支撑一切的苹果渠道和架构。
    • 这儿的关键词是多个渠道(iOS, macOS, tvOS, watchOS, iPadOS, carPlayOS)
      05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】
    • 咱们运用的普通动态库/静态库归于fat file,仅仅是包括多个架构,如armv7 armv7s arm64 arm64e x86_64
    • XCFramework 能够包括 iOS 设备,iOS 模拟器和 Mac Catalyst 等多个渠道的二进制库。
      05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】
    • XCFramework库是把不同渠道、不同架构的Framework库放在一同(与Framework的开发相同,支撑C、OC、C++、Swift)
      05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】
      • 留意: 若是原本的Framework库中有C++文件(.hpp、.cpp),要尽或许地转换成Objective-C++文件(.h、.mm)。
      • 咱们的项目若是存量项目(旧项目),一般开发项目的时分还是会或多或少地引进不同开发言语的库。
        • 比方:Swift言语开发+Swift库+OC库
        • OC言语开发+Swift库+OC库
      • 只要咱们用到OC或OC库,内部若是运用了runtime 或 KVC、KVO等。无法防止的,咱们需求在项目主工程的Build Setting-> Other Linker Flag 增加一项: -all_load
      • 增加 -all_load 会导致 包括 了 C++ 的库,在导入 主工程 后 发生冲突
      • 因此要尽或许 把 在 OC库中的C++文件(.hpp、.cpp)转换成Objective-C++文件(.h、.mm)

3. 终端指令制造XCFramework

经过xcodebuild -create-xcframework命令即可完成制造 XCFramework 示例如下:

.
├── ios_arm64
│ ├── 0CD1FB8D-9D63-3092-B68B-2E579A306D3F.bcsymbolmap
│ ├── NKTestOCFramework.framework
│ └── NKTestOCFramework.framework.dSYM
└── ios_simulator_x86-64_arm64
    ├── NKTestOCFramework.framework
    └── NKTestOCFramework.framework.dSYM

经过xcodebuild -create-xcframework命令来兼并为 XCFramework。

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

xcodebuild -create-xcframework\
           -framework ios_arm64/NKTestOCFramework.framework\
           -framework ios_simulator_x86-64_arm64/NKTestOCFramework.framework\
           -output NKTestOCFramework.xcframework

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

兼并后的 NKTestOCFramework.xcframework 目录结构如下,包括 arm64x86_64 版本,这和 lipo 操作相似,兼并其他渠道时操作相似。

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

NKTestOCFramework.xcframework
├── Info.plist
├── ios-arm64
│ └── NKTestOCFramework.framework
└── ios-x86_64-simulator
    └── NKTestOCFramework.framework

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】
假如是静态库 .a 文件,则需求用-library-headers来指定静态库和头文件。

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

xcodebuild -create-xcframework\
           -library ios-arm64/libNKTestOCLib.a\
           -headers ios-arm64/include/NKTestOCLib\
           -library ios-simulator-arm64-x86_64/libNKTestOCLib.a\
           -headers ios-simulator-arm64-x86_64/include/NKTestOCLib\
           -output NKTestOCLib.xcframework

05-iOS架构设计|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、XCFameworks等】

4.制造XCFramework的脚本

4.1 创立Framework脚本:模拟器形式

xcodebuild archive -project 'NKTestOCFramework.xcodeproj'\
    -scheme 'NKTestOCFramework'\
    -configuration Release\
    -destination 'generic/platform=iOS Simulator'\
    -archivePath './archives/NKTestOCFramework.framework-iphonesimulator.xcarchive'\
    SKIP_INSTALL=NO
  • xcodebuild: 在Xcode中实践运用的命令
  • archive: 打包
  • project: 工程名
  • scheme: 选择 scheme
  • configuration: 哪种环境下
  • destination: 要分发的渠道,当时指定的是 iOS Simulator
  • archivePath: 紧缩之后,存放的途径
  • SKIP_INSTALL=NO:假如设置为YES,则不会将生成的framwork文件存放在Products目录下

4.2 创立Framework脚本:真机形式

xcodebuild archive -project 'NKTestOCFramework.xcodeproj'\
    -scheme 'NKTestOCFramework'\
    -configuration Release\
    -destination 'generic/platform=iOS'\
    -archivePath './archives/NKTestOCFramework.framework-iphoneos.xcarchive'\
    SKIP_INSTALL=NO

4.3 创立Framework脚本

framework->XCFramework:

# 参数
scheme="<your scheme>"
framework_name=${scheme}
archive_path="archives"
archive_iphoneos_path="${archive_path}/iphoneos.xcarchive"
archive_iphone_simulator_path="${archive_path}/iphonesimulator.xcarchive"
out_dir="out"

# 清理
function build_clean() {
    echo "======build_clean======"
    rm -rf ./archives
    rm -rf ./${out_dir}
} 

# 编译
function build_framework {
    echo "======build_framework======"
    xcodebuild archive -scheme ${scheme} -sdk iphoneos -archivePath ${archive_iphoneos_path} BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO || exit 1
    xcodebuild archive -scheme ${scheme} -sdk iphonesimulator -archivePath ${archive_iphone_simulator_path} BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO || exit 1
}

# 兼并
function build_xcframework() {
    echo "======build_xcframework======"
    xcodebuild -create-xcframework -framework "${archive_iphoneos_path}/Products/Library/Frameworks/${scheme}.framework" -framework "${archive_iphone_simulator_path}/Products/Library/Frameworks/${scheme}.framework" -output ${out_dir}/${framework_name}.xcframework || exit 1
}

# 紧缩
function build_zip {
    echo "======build_zip======"
    cd ${out_dir}
    time=`date +%y%m%d%H%M%S`
    name=${framework_name}_${time}.zip
    zip -r -m -o ${framework_name}.xcframework.zip ${framework_name}.xcframework || exit 1
}

# 调用
build_clean
build_framework
build_xcframework
build_zip

留意修改参数值

  • scheme=”要打包的scheme的具体值”
  • archive_path=”archives文件夹的途径”
  • out_dir=”输出文件夹的途径”

.a->XCFramework

# 参数
scheme="<your scheme>"
library_name=${scheme}
archive_path="archives"
archive_iphoneos_path="${archive_path}/iphoneos.xcarchive"
archive_iphone_simulator_path="${archive_path}/iphonesimulator.xcarchive"
# 静态库公共头文件目录
library_public_header_path="<your library public header path>" # 如:${scheme}/public_headers
out_dir="out"

# 清理
function build_clean() {
    echo "======build_clean======"
    rm -rf ./archives
    rm -rf ./${out_dir}
} 

# 编译
function build_library {
    echo "======build_library======"
    xcodebuild archive -scheme ${scheme} -sdk iphoneos -archivePath ${archive_iphoneos_path} BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO || exit 1
    xcodebuild archive -scheme ${scheme} -sdk iphonesimulator -archivePath ${archive_iphone_simulator_path} BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO || exit 1
}

# 兼并
function build_xcframework() {
    echo "======build_xcframework======"
    xcodebuild -create-xcframework -library "${archive_iphoneos_path}/Products/usr/local/lib/lib${scheme}.a" -headers "${library_public_header_path}/" -library "${archive_iphone_simulator_path}/Products/usr/local/lib/lib${scheme}.a" -headers "${library_public_header_path}" -output ${out_dir}/${library_name}.xcframework || exit 1
}

# 紧缩
function build_zip {
    echo "======build_zip======"
    cd ${out_dir}
    time=`date +%y%m%d%H%M%S`
    name=${library_name}_${time}.zip
    zip -r -m -o ${library_name}.xcframework.zip ${library_name}.xcframework || exit 1
}

# 调用
build_clean
build_library
build_xcframework
build_zip

专题系列文章

iOS架构规划

  • 1.iOS架构规划|总述
  • 2.iOS架构规划|iOS模块化开发(待输出)
  • 3.iOS架构规划|iOS组件化开发-依靠包管理工具Cocoapods常用实践:组件库(本地库、远程库(公有、私有))创立、模板工程、区分、资源管理、优化等
  • 4.iOS架构规划|iOS组件化开发【组件管理、组件分类、组件区分、组件开发】(待输出)
  • 5.iOS架构规划|iOS开发包二进制化【.a静态库、.framework(静态库、动态库)、.dyld动态库、.xcfameworks等】

探究iOS底层原理

  • 探究iOS底层原理|总述