前文《百度App Objective-C/Swift 组件化混编之路》已经介绍了百度App 引入 Swift 的影响面评估以及落地的实施步骤,本文主要以依赖管理工具为支撑,介绍百度App 如何实现组件内的 Objective-C/Swift 混编、单测、二进制发布和集成,以及组件间的依赖和引用。
百度App 自研的依赖管理工具 EasyBox 工具链已经把混编作为功能子集,如果你感兴趣,可以阅读百度App 技术公众号往期文章《百度App iOS工程化实践: EasyBox破冰之旅》。掌握 Xcode 编译、链接选项等相关知识点,有助于理解混编的实现过程。
为解决大规模并行开发问题,百度App 将工程进行了组件化拆分,并实现组件的二进制化,一个组件即为一个独立的功能单元和编译单元,具有两种形态,源码形态和二进制形态,开发过程中可以按需进行组件的源码/二进制切换。所以我们要解决这两种形态下的组件内混编和组件间混合调用问题。
在介绍混编之前,我们先来了解两个重要的概念:组件 Target 类型和 Module。
EasyBox 工具链会为源码形态的组件生成一个 Xcode 子工程和对应的 Target,Target 可以是以下类型中的一种:
.a 与 .framework 的区别是:Framework 是分层目录,它将共享资源(例如动态共享库,nib 文件,图像文件,本地化字符串,头文件和参考文档)封装在一个程序包中。动态库与静态库的区别是:系统根据需要将动态库加载到内存中,可以被多个应用程序同时访问,并在所有可能的应用程序之间共享资源的一份副本。静态库则是链接到某个应用程序的二进制中。
这些 Target 可能还存在一个或多个伴生 Target :
What's the Xcode target?
https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Targets.html
对于伴生 Target,与 Swift 混编相关的只有单测;而对于主 Target,按照 Target 的文件组织形式可以分两类:
当 Target 中只有 Objective-C 源码(.h、.m)时,无论哪种 Target,源文件之间都可以通过 import 头文件的方式进行引用,但 Swift 语言是强制以 module 形式 引用的,所以在 Swfit 中需要将 Target 的产物转换为一个独立的 module,供其他 module 依赖并引用。所以要实现 Swift 混编,每个组件对应的主 Target (源码或二进制)都必须以一个 module 的形式存在。下面介绍如何实现 Target 内的 module 混编、以及 Target 之间的 module 依赖。
1.2.1 基本概念
<span style="font-size: 16px;color: rgb(99, 99, 99);"><Foundation/Foundation.h>
为例:对编译器来讲,每次编译过程一个 module 只会加载一次,避免多次引入并加载相同的头文件带来的编译耗时问题。所以 module 化后编译效率更高。
framework module SwiftOCMixture {
umbrella header "SwiftOCMixture.h"
export *
module * { export * }
}
module SwiftOCMixture.Swift {
header "SwiftOCMixture-Swift.h"
requires objc
}
ModuleMap采用模块映射语言,但是到现在( 2020 年 Q3 为止)该语法依然不够稳定,所以建议:编写 modulemap 时需要尽可能使用少的关键字实现 module 功能,比如 framework、umbrella、header、extern、use。
建议 modulemap 内声明一个umbrella header,便于快速引用对应的头文件,但必须将所有公开的头文件填充到 umbrella header 文件内。否则将得到一个警告:
<module-includes>
Umbrella header for module 'XXX' does not include header 'absolute path to a public header'
不包含 umbrella header 的 module ,modulemap 中不必添加
<span style="font-size: 14px;">module * { export * }
包含 umbrella header 的 framework,不用配置任何(包括 MODULEMAP_FILE )即可自动 module 化
1.2.2 module 相关的 build setting 参数
上古时期,程序员通过 Makefile 来控制程序的编译链接过程。现如今在 IDE 的封装下,复杂度大大降低,只需要通过 IDE 来控制关键变量和自定义变量,在 Xcode 中,这个控制变量被称为 build setting,build setting 和 Module 化相关的变量主要有这些:- 对module自身的描述:
DEFINES_MODULE:YES/NO,module 化需要设置为 YES
MODULEMAP_FILE:指向 module.modulemap 路径
HEADER_SEARCH_PATHS:modulemap 内定义的 Objective-C 头文件,必须在 HEADER_SEARCH_PATHS 内能搜索到
PRODUCT_MODULE_NAME:module 名称,默认和 Target name 相同
对外部module的引用:
FRAMEWORK_SEARCH_PATHS:依赖的 Framework 搜索路径
OTHER_CFLAGS:编译选项,可配置依赖的其他 modulemap 文件路径 -fmodule-map-file=${modulemap_path}
HEADER_SEARCH_PATHS:头文件搜索路径,可用于配置源码中引用的其他 Library 的头文件
OTHER_LDFLAGS:依赖其他二进制的编译依赖选项
SWIFT_INCLUDE_PATHS:swiftmodule 搜索路径,可用于配置依赖的其他 swiftmodule
OTHER_SWIFT_FLAGS:Swift 编译选项,可配置依赖的其他 modulemap 文件路径 -Xcc -fmodule-map-file=${modulemap_path}
本文的后续部分也会用到 build setting 中的其他关键变量。
包含 Swift 源码的非 framework 的 module,建议在 buildphase 的 script 里处理编译后的两个事情:
编译生成的 interface header,拷贝作为公开头文件,供其他 Target 访问编译生成的 Swiftmodule,配置追加到 modulemap 文件中
至此,我们已经了解了单个组件的 module 化过程。
根据官方说明,Target 内支持 Objective-C 和 Swift 语言的混编,无外乎解决两个问题:
下面我们针对 Framework 和 Library(非 Framework 静态库)两种类型,分别介绍下组件内的混编实现。
针对 Framework 类型的 Target 内混编,我们要做的就是什么都不做。
简单吧,对于全新生成的有 umbrella header 的 Framework 默认就是 Module化 的,不需要做任何操作即可实现 Target 内混编。对于没有umbrella header
的Framework,需要参照 如何实现 Module化 进行 Module 化改造。
Objective-C 引用 Swift 在头文件内添加引入 Swift 的 Interface 头文件即可,可以访问 Swift 中以 @objc public
或 @objc open
修饰的类和方法,或者 class 修饰为 @objcMembers public
#import <xxx/${ModuleName}-Swift.h>
因为 Xcode 在编译时已经对 framework 进行 Module 化处理,并自动生成该 Interface 头文件,编译成功时拷贝 Headers 文件夹内
针对 Library 类型的 Target 内混编,我们首先依然需要参照如何实现 Module 化改造。
不足:无法开启跨 Swift 版本兼容的功能
-import-underlying-module
该构件标记由 Xcode 隐式创建下层 Module,并隐式引入当前 Module 内所有的 Objective-C 的公开头文件,Swift 可以直接访问。该标记需要配合 USER_HEADER_SEARCH_PATHS
或者 HEADER_SEARCH_PATHS
来搜索当前 module 所需的公开头文件OTHER_SWIFT_FLAGS = $(inherited) -import-underlying-module
不足:因为隐式创建下层 module,也会将 Swift 的类和方法包含到 Swift 的 Interface 头文件中,需要在 Swift 的类和方法之前添加
<span style="font-size: 14px;">@objc open
,经测试发现,这样会造成 module 将近一秒延迟(即修改 Swift 的部分接口后 Interface 文件不立即变更)。
组件间依赖调用的核心依然是 Module 化,否则 Swift 无法调用其他组件,下面介绍组件间依赖调用相关的 Build Settings
参数。
单测也是组件间依赖的一种,单测的 Target 依赖其他需要测试的组件,并且该组件以源码形态集成
集成单测,除了配置组件间依赖的
Build Settings
,还需要注意两个要点:
- 第一,需要链接对应的静态库到目标
testbundle
- 第二,如果当前单测是 Objective-C 源码,而依赖的库文件包含 Swift 相关的库或 Target,必须在单测的 Target 内添加空的 Swift 占位源文件(空文件真的可以,后缀为 .swift),否则链接时会报错。
如果依赖组件的Target类型是Framework,So Easy,因为Framework已经是一个module了(包含umbrella header),直接配置BuildSettings:
<xxx.framework
文件// 当依赖组件是二进制时,可以不用设置该项
OTHER_LDFLAGS = $(inherited) -framework xxxA -framework xxxB ...
当依赖组件的Target类型是Library,配置稍微复杂一点:
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${path_dir}/xxxA/module.modulemap" -fmodule-map-file="${path_dir}/xxxB/module.modulemap" ...
OTHER_LDFLAGS = $(inherited) -l"xxxA" -l"xxxB" ...
HEADER_SEARCH_PATHS = $(inherited) "${xxxA_public_header_dir}" "${xxxB_public_header_dir}" ...
3.2.2 当前组件包含 Swift 源码
OTHER_CFLAGS = $(inherited) -Xcc -fmodule-map-file="${path_dir}/xxxA/module.modulemap" -Xcc -fmodule-map-file="${path_dir}/xxxB/module.modulemap" ...
3.2.3 依赖 swiftmodule
当依赖的 Library 中包含 Swift 源码,那么该源码编译后将生成 swiftmodule,或依赖 Library 二进制中包含 swiftmodule,那么当前组件需要配置:
*.swiftmodule
SWIFT_INCLUDE_PATHS = $(inherited) "${xxxA_swift_module_dir}" "${xxxB_swift_module_dir}" ...
当依赖的组件是 Library,并且包含 Swift 的源码,需将当前 Target 的 Scheme 编译条件配置为非并行编译 uncheck Parallelize Build
(如下图所示),达到控制编译顺序的目的,避免因为依赖组件还未生成的 *-Swift.h 文件(依赖组件编译成功后生成),造成当前组件源码的编译错误。
为了提升产品线的编译速度,业界内很多产品线均做了组件二进制化,即将组件源码编译为多种架构的二进制,并合并架构后以二进制的方式引入工程,避免了大量源码的重复编译,提升编译效率,对于 Swift 的组件来说,如何做二进制化?
参考 1.2 Module 化要点
虽然 ABI 稳定了,但是根据 Swift 的设计,各自 Swift 编译器打出的二进制并不能在其他版本使用,需要使用到跨 Swift 版本调用的 interface 文件(在编译产物 swiftmodule 文件夹中),设置 BUILD_LIBRARY_FOR_DISTRIBUTION = YES
即可生成,但该标记与bridging 冲突,即在混编的 Library 且使用 bridging header
的工程中不可用;如果真要使用 Library 又想 Swift 二进制跨 Swift 版本兼容,参考 2.2 介绍的 -import-underlying-module
对于 Framework ,Swift 源码编译产生的 Objective-C Interface 文件会被自动拷贝到公开头文件夹,只需要合并多架构 Interface 头文件即可;但对于 Library 则需要先手动移动头文件再合并 Interface 头文件,建议在 BuildPhase 添加 Script Phase 在编译完成后拷贝操作:
// 仅供参考
COMPATIBILITY_HEADER_PATH="${公开头文件目录}/${PRODUCT_MODULE_NAME}-Swift.h"
ditto "${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h" "${COMPATIBILITY_HEADER_PATH}"
不同架构的 *-Swift.h 文件的合并方式:
- 以
<span style="font-size: 14px;">#ifdef 架构
的方式进行(当各架构提供的接口没有区别的情况下,可直接使用模拟器架构)- 合并为 XCFramework 的形式
对于包含 Swift 源码的产物中将包含 swiftmodule 文件夹,直接合并两个 swiftmodule 目录即可,不同架构以不同的文件名呈现
对于开启
BUILD_LIBRARY_FOR_DISTRIBUTION
的 module 来说,swiftmodule 文件夹内包含 *.interface 即为跨 Swift 版本兼容文件
使用 lipo
命令进行二进制架构的常规合并,这里不做赘述
如下图:模拟器架构 Framework 形态的 *.swiftmodule
(.a的 *.swiftmodule与之类似),其中 x86_64-apple-ios-simulator.swiftinterface
是跨 Swift 版本调用的 interface 文件
已知:有组件 A 依赖组件 B,组件 B 依赖组件 C 在 Objective-C 中,B 对外暴露的头文件中引用了 C 的公开头文件,我们叫组件 B 传递依赖 C,结果就是编译组件 A 时必须同时能找到组件 B 和组件 C 的头文件,否则编译失败。
然而 Swift 并没有公开头文件一说,只要组件 B import C
,导致 swiftmodule 中也明确标记了 import C
,当组件 A import B
时,也同时 import C
,如果组件 A 找不到组件 C 的 module,那组件 A 将编译失败。
对于百度App 的开发者来说,不用去关心混编的是如何实现的,只需要跟正常开发一样,组件内引用所需的头文件(#import <ModuleXX/xx.h>)或module(@import ModuleXX),组件间在声明依赖后亦可直接引用头文件或 module ,EasyBox 工具链会根据源码文件或配置进行module 化和 Xcode Build setting
相关的处理,以下情况将判定为需要 module 化:
对于混编组件的二进制打包,开发者们也不用去关心如何处理编译产物,诸如 *-Swift.h
、二进制架构、*.swiftmodule
、*.interface
等,EasyBox 工具链打包命令 box package
会全权处理,降低开发者们的配置难度和协同成本。
如果组件以源码的方式被集成,是可行的。
Framework 中将私有头文件声明为一个私有 module(modulemap内声明),由组件内的 Swift 源码 import 该私有 module 即可
Library 中使用 bridging header
如果组件是以二进制方式被集成,则不可以:
集成 Framework 二进制,由于 Swiftmodule 的传递依赖的这个特性,这种调用方式将导致其他组件依赖这个组件的二进制时,无法找到对应的私有 module,导致编译失败
集成 Library 二进制,由于编译二进制时无法同时开启 Bridging Header 和 BUILD_LIBRARY_FOR_DISTRIBUTION
,开启 Bridging Header 后该二进制将无法在不同的 Swift 版本下被集成
建议直接全部使用 Framework ,因为 Framework 针对 Swift 混编支持非常简单
对于最低支持版本在 iOS8 及以下的 App,由于 Apple 限制 ipa 中二进制包大小为 80M,为了缩小二进制体积,一般都采用内置动态库,如果动态库也建议使用 Framework,而非动态库的 Library
当一个组件或产物需要链接其他 Swift 的产物时,比如 App、单测、动态库等,需要告诉 Xcode 开启 Swift 链接功能,开启方法就是添加一个 Swift 文件,否则报错。
官方文档
https://swift.org
What are Frameworks?
https://developer.apple.com/library/archive/documentation/MacOSX/ Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html
Clang Module
http://clang.llvm.org/docs/Modules.html
Importing Objective-c Into Swift
https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift
Xcode Release Notes
https://developer.apple.com/documentation/xcode_release_notes
Xcode Build Settings
https://xcodebuildsettings.com/#category-core-build-system
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/s0RZrMQQKVdxLS4Ni8yO6g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。