百度App Objective-C/Swift 组件化混编之路(二)- 工程化

2023-11-13

 

作者丨张渝、郭金

来源丨百度App技术

前文《百度App Objective-C/Swift 组件化混编之路》已经介绍了百度App 引入 Swift 的影响面评估以及落地的实施步骤,本文主要以依赖管理工具为支撑,介绍百度App 如何实现组件内的 Objective-C/Swift 混编、单测、二进制发布和集成,以及组件间的依赖和引用。

百度App 自研的依赖管理工具 EasyBox 工具链已经把混编作为功能子集,如果你感兴趣,可以阅读百度App 技术公众号往期文章《百度App iOS工程化实践: EasyBox破冰之旅》。掌握 Xcode 编译、链接选项等相关知识点,有助于理解混编的实现过程。

一. 组件Target类型 和 Module化

为解决大规模并行开发问题,百度App 将工程进行了组件化拆分,并实现组件的二进制化,一个组件即为一个独立的功能单元和编译单元,具有两种形态,源码形态和二进制形态,开发过程中可以按需进行组件的源码/二进制切换。所以我们要解决这两种形态下的组件内混编和组件间混合调用问题。

在介绍混编之前,我们先来了解两个重要的概念:组件 Target 类型和 Module。

1.1 组件 Target 类型

EasyBox 工具链会为源码形态的组件生成一个 Xcode 子工程和对应的 Target,Target 可以是以下类型中的一种:

  • dynamic_library:动态库,Xcode 7 之前扩展名为 .dylib, Xcode 7 后是 .tbd ;目前官方环境并不允许为 iOS 平台添加这种类型。

  • static_library:静态库,扩展名 .a

  • static_framework:静态库,扩展名 .framework

  • dynamic_framework:动态库,扩展名 .framework

.a 与 .framework 的区别是:Framework 是分层目录,它将共享资源(例如动态共享库,nib 文件,图像文件,本地化字符串,头文件和参考文档)封装在一个程序包中。动态库与静态库的区别是:系统根据需要将动态库加载到内存中,可以被多个应用程序同时访问,并在所有可能的应用程序之间共享资源的一份副本。静态库则是链接到某个应用程序的二进制中。

这些 Target 可能还存在一个或多个伴生 Target :

  • bundle

  • octest_bundle

  • unit_test_bundle

  • ui_test_bundle

What's the Xcode target?

https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Targets.html

对于伴生 Target,与 Swift 混编相关的只有单测;而对于主 Target,按照 Target 的文件组织形式可以分两类:

  • Library(扩展名为 .a)

  • Framework(扩展名为 .framework)

当 Target 中只有 Objective-C 源码(.h、.m)时,无论哪种 Target,源文件之间都可以通过 import 头文件的方式进行引用,但 Swift 语言是强制以 module 形式 引用的,所以在 Swfit 中需要将 Target 的产物转换为一个独立的 module,供其他 module 依赖并引用。所以要实现 Swift 混编,每个组件对应的主 Target (源码或二进制)都必须以一个 module 的形式存在。下面介绍如何实现 Target 内的 module 混编、以及 Target 之间的 module 依赖。

1.2 Module 化

1.2.1 基本概念

  • module:是一个编译单元,或构建产物,对一个软件库的结构化替代封装,供链接器使用(更多介绍请查阅 Clang-Module:https://clang.llvm.org/docs/Modules.html#introduction)

  • umbrella header:module 对外公开的根头文件,包含了这个 module 中所有其他公开头文件的引用。以 Foundation 框架的根头文件 <Foundation/Foundation.h>为例:

对编译器来讲,每次编译过程一个 module 只会加载一次,避免多次引入并加载相同的头文件带来的编译耗时问题。所以 module 化后编译效率更高。

  • modulemap:描述 module 和 module header 间的关系,描述现有 header 如何映射到 module 的逻辑结构。modulemap 结构如下:

 
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 中不必添加 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 中的其他关键变量。

1.2.3 非 framework 的 module 处理

包含 Swift 源码的非 framework 的 module,建议在 buildphase 的 script 里处理编译后的两个事情:

  • 编译生成的 interface header,拷贝作为公开头文件,供其他 Target 访问编译生成的 Swiftmodule,配置追加到 modulemap 文件中

至此,我们已经了解了单个组件的 module 化过程。

二. 组件内混编

根据官方说明,Target 内支持 Objective-C 和 Swift 语言的混编,无外乎解决两个问题:

  • Objective-C 可以引用 Swift 的类和方法

  • Swift 可以引用 Objective-C 的类和方法

下面我们针对 Framework 和 Library(非 Framework 静态库)两种类型,分别介绍下组件内的混编实现。

2.1 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 文件夹内

  • Swift 引用 Objective-C 直接使用对应的类和方法

2.2 Library

针对 Library 类型的 Target 内混编,我们首先依然需要参照如何实现 Module 化改造。

  • Objective-C 引用 Swift 与 Framework 的引用方式一致,在头文件内添加引入 Swift 的 Interface 头文件即可,可以访问 Swift 中以 @objc 修饰的类和方法,或者 class 修饰为 @objcMembers

  • Swift 引用 Objective-C 有显式和隐式两种方式 1、通过显式配置桥接文件 BridingHeader,在桥接文件内 import 对 Swift 类公开的头文件,用于 Swift 访问 Objective-C 头文件 (Importing Objective-C into Swift:https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_objective-c_into_swift)

不足:无法开启跨 Swift 版本兼容的功能

  • OTHER_SWIFT_FLAGS 的标记:-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 的类和方法之前添加 @objc open,经测试发现,这样会造成 module 将近一秒延迟(即修改 Swift 的部分接口后 Interface 文件不立即变更)。

三. 组件间依赖

组件间依赖调用的核心依然是 Module 化,否则 Swift 无法调用其他组件,下面介绍组件间依赖调用相关的 Build Settings 参数。

单测也是组件间依赖的一种,单测的 Target 依赖其他需要测试的组件,并且该组件以源码形态集成

集成单测,除了配置组件间依赖的 Build Settings ,还需要注意两个要点

  • 第一,需要链接对应的静态库到目标 testbundle

  • 第二,如果当前单测是 Objective-C 源码,而依赖的库文件包含 Swift 相关的库或 Target,必须在单测的 Target 内添加空的 Swift 占位源文件(空文件真的可以,后缀为 .swift),否则链接时会报错。

3.1 依赖 Framework 组件

如果依赖组件的Target类型是Framework,So Easy,因为Framework已经是一个module了(包含umbrella header),直接配置BuildSettings:

  • FRAMEWORK_SEARCH_PATHS: 依赖的Framework搜索路径,在对应的路径下查找xxx.framework文件

  • OTHER_LDFLAGS:当依赖的组件是源码时,可以有效将依赖的组件顺序编译,根据Xcode 10.2的升级说明(https://developer.apple.com/documentation/xcode-release-notes/xcode-10_2-release-notes)

// 当依赖组件是二进制时,可以不用设置该项OTHER_LDFLAGS = $(inherited) -framework xxxA -framework xxxB ...

3.2 依赖Library组件

当依赖组件的Target类型是Library,配置稍微复杂一点:

3.2.1 当前组件包含Objective-C源码

  • OTHER_CFLAGS:配置当前Target依赖的其他Module

 
OTHER_CFLAGS = $(inherited) -fmodule-map-file="${path_dir}/xxxA/module.modulemap" -fmodule-map-file="${path_dir}/xxxB/module.modulemap" ...
  • OTHER_LDFLAGS:同 3.1 依赖 Framework 组件

 
OTHER_LDFLAGS = $(inherited) -l"xxxA" -l"xxxB" ...
  • HEADER_SEARCH_PATHS:配置当前 Target 的头文件搜索路径,包含依赖的其他 Module 内配置的头文件搜索路径

 
HEADER_SEARCH_PATHS = $(inherited) "${xxxA_public_header_dir}" "${xxxB_public_header_dir}" ...
3.2.2 当前组件包含 Swift 源码
  • OTHER_SWIFT_FLAGS;配置当前 Target 依赖的其他 Module

 
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,那么当前组件需要配置:

  • SWIFT_INCLUDE_PATHS:依赖组件 swiftmodule 的搜索路径,需要配置该路径,目录下包含 *.swiftmodule

 
SWIFT_INCLUDE_PATHS = $(inherited) "${xxxA_swift_module_dir}" "${xxxB_swift_module_dir}" ...

3.2.4 编译顺序控制

当依赖的组件是 Library,并且包含 Swift 的源码,需将当前 Target 的 Scheme 编译条件配置为非并行编译 uncheck Parallelize Build(如下图所示),达到控制编译顺序的目的,避免因为依赖组件还未生成的 *-Swift.h 文件(依赖组件编译成功后生成),造成当前组件源码的编译错误。

四. 混编组件二进制打包

为了提升产品线的编译速度,业界内很多产品线均做了组件二进制化,即将组件源码编译为多种架构的二进制,并合并架构后以二进制的方式引入工程,避免了大量源码的重复编译,提升编译效率,对于 Swift 的组件来说,如何做二进制化?

4.1 module 化

参考 1.2 Module 化要点

4.2 兼容性

虽然 ABI 稳定了,但是根据 Swift 的设计,各自 Swift 编译器打出的二进制并不能在其他版本使用,需要使用到跨 Swift 版本调用的 interface 文件(在编译产物 swiftmodule 文件夹中),设置 BUILD_LIBRARY_FOR_DISTRIBUTION = YES 即可生成,但该标记与bridging 冲突,即在混编的 Library 且使用 bridging header 的工程中不可用;如果真要使用 Library 又想 Swift 二进制跨 Swift 版本兼容,参考 2.2 介绍的 -import-underlying-module

4.3 SWIFT_OBJC_INTERFACE_HEADER 文件合并

对于 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 文件的合并方式:

  1. 以 #ifdef 架构 的方式进行(当各架构提供的接口没有区别的情况下,可直接使用模拟器架构)

  2. 合并为 XCFramework 的形式

4.4 swiftmodule文件合并

对于包含 Swift 源码的产物中将包含 swiftmodule 文件夹,直接合并两个 swiftmodule 目录即可,不同架构以不同的文件名呈现

对于开启 BUILD_LIBRARY_FOR_DISTRIBUTION 的 module 来说,swiftmodule 文件夹内包含 *.interface 即为跨 Swift 版本兼容文件

4.5 合并二进制

使用 lipo 命令进行二进制架构的常规合并,这里不做赘述

4.6 二进制包

如下图:模拟器架构 Framework 形态的 *.swiftmodule(.a的 *.swiftmodule与之类似),其中 x86_64-apple-ios-simulator.swiftinterface是跨 Swift 版本调用的 interface 文件 

4.7 小知识:swiftmodule 的传递依赖性

已知:有组件 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 的源码文件的组件

  • 存在 .swiftmodule 或 *-Swift.h 文件的二进制组件

  • 宿主工程的 Boxfile 中显式配置 module 化

  • 组件的 boxspec 描述中声明 modulemap 文件

对于混编组件的二进制打包,开发者们也不用去关心如何处理编译产物,诸如 *-Swift.h、二进制架构、*.swiftmodule*.interface等,EasyBox 工具链打包命令 box package 会全权处理,降低开发者们的配置难度和协同成本。

六. 常见问题

6.1 Swift 组件内调用 Objective-C,只能调用 Objective-C 的公开头文件,就不能调用私有头文件吗?

  • 如果组件以源码的方式被集成,是可行的

    • Framework 中将私有头文件声明为一个私有 module(modulemap内声明),由组件内的 Swift 源码 import 该私有 module 即可

    • Library 中使用 bridging header

  • 如果组件是以二进制方式被集成,则不可以

    • 集成 Framework 二进制,由于 Swiftmodule 的传递依赖的这个特性,这种调用方式将导致其他组件依赖这个组件的二进制时,无法找到对应的私有 module,导致编译失败

    • 集成 Library 二进制,由于编译二进制时无法同时开启 Bridging Header 和 BUILD_LIBRARY_FOR_DISTRIBUTION,开启 Bridging Header 后该二进制将无法在不同的 Swift 版本下被集成

6.2 到底使用 Framework 还是 Library?

建议直接全部使用 Framework ,因为 Framework 针对 Swift 混编支持非常简单

对于最低支持版本在 iOS8 及以下的 App,由于 Apple 限制 ipa 中二进制包大小为 80M,为了缩小二进制体积,一般都采用内置动态库,如果动态库也建议使用 Framework,而非动态库的 Library

6.3 App 链接一个 Swift 二进制时报错?

当一个组件或产物需要链接其他 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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

百度App Objective-C/Swift 组件化混编之路(二)- 工程化 的相关文章

  • numba 函数何时编译?

    我正在研究这个例子 http numba pydata org numba doc 0 15 1 examples html multi threading http numba pydata org numba doc 0 15 1 ex
  • 从数据框中按索引删除行

    我有一个数组wrong indexes train其中包含我想从数据框中删除的索引列表 0 63 151 469 1008 要删除这些索引 我正在尝试这样做 df train drop wrong indexes train 但是 代码失败
  • Python Popen 与 psexec 挂起 - 不良结果

    我对 subprocess Popen 和我认为是管道的问题有疑问 我有以下代码块 从 cli 运行时 100 都不会出现问题 p subprocess Popen psexec serverName get cmd c ver echo
  • django_openid_auth TypeError openid.yadis.manager.YadisServiceManager 对象不是 JSON 可序列化

    I used django openid auth在我的项目上 一段时间以来它运行得很好 但今天 我测试了该应用程序并遇到了这个异常 Environment Request Method GET Request URL http local
  • 如何在序列化器创建方法中获取 URL Id?

    我有以下网址 url r member P
  • Python逻辑运算符优先级[重复]

    这个问题在这里已经有答案了 哪个运算符优先4 gt 5 or 3 lt 4 and 9 gt 8 这会被评估为真还是假 我知道该声明3 gt 4 or 2 lt 3 and 9 gt 10 显然应该评估为 false 但我不太确定 pyth
  • 从 ffmpeg 获取实时输出以在进度条中使用(PyQt4,stdout)

    我已经查看了很多问题 但仍然无法完全弄清楚 我正在使用 PyQt 并且希望能够运行ffmpeg i file mp4 file avi并获取流式输出 以便我可以创建进度条 我看过这些问题 ffmpeg可以显示进度条吗 https stack
  • 在 Python distutils 中从 setup.py 查找脚本目录的正确方法?

    我正在分发一个具有以下结构的包 mymodule mymodule init py mymodule code py scripts script1 py scripts script2 py The mymodule的子目录mymodul
  • 将数据帧行转换为字典

    我有像下面的示例数据这样的数据帧 我正在尝试将数据帧中的一行转换为类似于下面所需输出的字典 但是当我使用 to dict 时 我得到了索引和列值 有谁知道如何将行转换为像所需输出那样的字典 任何提示都非常感激 Sample data pri
  • if 语句未命中中的 continue 断点

    在下面的代码中 两者a and b是生成器函数的输出 并且可以评估为None或者有一个值 def testBehaviour self a None b 5 while True if not a or not b continue pri
  • 忽略 Mercurial hook 中的某些 Mercurial 命令

    我有一个像这样的善变钩子 hooks pretxncommit myhook python path to file myhook 代码如下所示 def myhook ui repo kwargs do some stuff 但在我的例子中
  • Pandas 数据帧到 numpy 数组 [重复]

    这个问题在这里已经有答案了 我对 Python 很陌生 经验也很少 我已经设法通过复制 粘贴和替换我拥有的数据来使一些代码正常工作 但是我一直在寻找如何从数据框中选择数据 但无法理解这些示例并替换我自己的数据 总体目标 如果有人真的可以帮助
  • 在Python中调整图像大小

    我有一张尺寸为 288 352 的图像 我想将其大小调整为 160 240 我尝试了以下代码 im imread abc png img im resize 160 240 Image ANTIALIAS 但它给出了一个错误TypeErro
  • 按元组分隔符拆分列表

    我有清单 print L I WW am XX newbie YY ZZ You WW are XX cool YY ZZ 我想用分隔符将列表拆分为子列表 ZZ print new L I WW am XX newbie YY ZZ You
  • Seaborn Pairplot 图例不显示颜色

    我一直在学习如何在Python中使用seaborn和pairplot 这里的一切似乎都工作正常 但由于某种原因 图例不会显示相关的颜色 我无法找到解决方案 因此如果有人有任何建议 请告诉我 x sns pairplot stats2 hue
  • 将 2D NumPy 数组按元素相乘并求和

    我想知道是否有一种更快的方法 专用 NumPy 函数来执行 2D NumPy 数组的元素乘法 然后对所有元素求和 我目前使用np sum np multiply A B 其中 A B 是相同维度的 NumPy 数组m x n 您可以使用np
  • Python 将日志滚动到变量

    我有一个使用多线程并在服务器后台运行的应用程序 为了无需登录服务器即可监控应用程序 我决定包括Bottle http bottlepy org为了响应一些HTTP端点并报告状态 执行远程关闭等 我还想添加一种查阅日志文件的方法 我可以使用以
  • 无法在 osx-arm64 上安装 Python 3.7

    我正在尝试使用 Conda 创建一个带有 Python 3 7 的新环境 例如 conda create n qnn python 3 7 我收到以下错误 Collecting package metadata current repoda
  • 当鼠标悬停在上面时,intellisense vscode 不显示参数或文档

    我正在尝试将整个工作流程从 Eclipse 和 Jupyter Notebook 迁移到 VS Code 我安装了 python 扩展 它应该带有 Intellisense 但它只是部分更糟糕 我在输入句点后收到建议 但当将鼠标悬停在其上方
  • 检查字典键是否有空值

    我有以下字典 dict1 city name yass region zipcode phone address tehsil planet mars 我正在尝试创建一个基于 dict1 的新字典 但是 它不会包含带有空字符串的键 它不会包

随机推荐

  • 【华为OD机试真题】【python】 网上商城优惠活动(一)【2022 Q4

    华为OD机试 题目列表 2023Q1 点这里 2023华为OD机试 刷题指南 点这里 题目描述 某网上商场举办优惠活动 发布了满减 打折 无门槛3种 优惠券 分别为 1 每满100元优惠10元 无使用数限制 如100 199元可以使用1张减
  • Ubuntu18.04/16.04调整屏幕分辨率至1920*1080

    Ubuntu在设置 显示里面的分辨率选择没有1920 1080 我们可以手动增加该分辨率配置 然后进行设置 参考文字和图片 crtl alt t 打开终端窗口 获取添加分辨率的格式 输入 cvt 1920 1080 查看显示器信息及已经支持
  • 安卓HAL层 so库文件加载原理

    本文分析代码基于安卓6 0 上层app通过jni调用hal层的hw get module函数获取硬件模块 这个函数是上层与hal打交道的入口 这里我们就具体来看看hw get module的实现 文件路径 vim hardware libh
  • andorid 与滑动相关的一些知识---getscrollY onscrollchange() scrollby scrollto等

    Android系统手机屏幕的左上角为坐标系 同时y轴方向与笛卡尔坐标系的y轴方向想反 通过提供的api如getLeft getTop getBottom getRight可以获得控件在parent中的相对位置 同时 也可以获得控件在屏幕中的
  • python怎么升级python的pip

    报错提示 WARNING You are using pip version 19 1 1 however version 20 0 2 is available You should consider upgrading via the
  • ouldn‘t check the working tree for unmerged files because of an error. detected dubious ownership in

    IDEA的git的clone操作中如果出现问题 couldn t check the working tree for unmerged files because of an error detected dubious ownershi
  • LangChain 的聊天模型

    各位人工智能爱好者 大家好 今天 我们就来详细了解一下 LangChain 聊天模型 LangChain是一个很棒的工具 它提供了与各种语言模型交互的标准接口 包括基于文本的大型语言模型 LLM 和聊天模型 LangChain模型的概念 模
  • 【Linux命令详解

    文章标题 简介 一 参数列表 二 使用介绍 1 使用基本模式搜索 2 忽略大小写匹配 3 反向匹配 4 递归搜索目录 5 显示文件名 6 显示行号 7 显示上下文行 8 启用扩展正则表达式 9 将模式视为固定字符串 10 使用颜色高亮显示匹
  • Docker部署服务

    部署Nginx 寻找镜像 docker search nginx 默认最新版 官网查看不同的版本信息 下载镜像 docker pull nginx root iZwz9hv1phm24s3jicy8x1Z docker images REP
  • C语言打印输出斐波那契数前20项案例讲解

    我们先看什么是斐波那契数 斐波那契数列指的是这样一个数列 1 1 2 3 5 8 13 21 34 55 89 我们通过观察可以得出斐波那契数列的特点 1 第一项和第二项都是1 2 这个数列从第3项开始 每一项都等于前两项之和 思路分析 1
  • 2023年第二届全国大学生数据统计与分析竞赛题目B:电影评分的大数据分析第二问

    详细代码 企鹅2869955900 import pandas as pd import matplotlib pyplot as plt import numpy as np plt rcParams font sans serif Si
  • 【大数据】Hadoop 生态系统及其组件

    Hadoop 生态系统及其组件 1 Hadoop 生态系统的组成 2 Hadoop 生态系统简介 2 1 HDFS 2 2 MapReduce 2 3 YARN 2 4 Hive 2 5 Pig 2 6 HBase 2 7 HCatalog
  • python3 模块、import、from import

    模块 1 模块就是 py后缀的文件 2 py文件类似于一个类 包含以下部分 1 导入 一般的类都有导入 2 变量 对应类的属性 3 函数 对应类的方法 4 类 对应内部类 5 if name main 对应主函数 6 顶格写的代码段 对应构
  • OpenCV学习笔记(17)双目测距与三维重建的OpenCV实现问题集锦(二)双目定标与双目校正

    三 双目定标和双目校正 双目摄像头定标不仅要得出每个摄像头的内部参数 还需要通过标定来测量两个摄像头之间的相对位置 即右摄像头相对于左摄像头的三维平移 t 和旋转 R 参数 图6 要计算目标点在左右两个视图上形成的视差 首先要把该点在左右视
  • vue2+高德地图web端开发使用

    创建vue2项目 我们创建一个vue2项目 创建vue2项目就不用再多说了吧 使用 vue create 项目名 创建即可 注册高德地图 高德地图官网地址 https lbs amap com 如果是第一次使用 点击注册然后进入我们的控制台
  • idea快捷键最全最新最好

    持续更新 如果文档中没有的 麻烦在评论中添加 常用快捷键 返回最顶头 home 返回最末尾 end Alt Insert 可以新建类 文件 get或set方法 此快捷键又名创造一切 编辑区和文件区的跳转 alt 1 编辑区跳转至文件区 es
  • 272. 最长公共上升子序列(lcis,dp)

    首先是lis的状态划分图 然后是lcs 结合lis和lcs两种dp问题的分析方法 我们就可以得出lcis的状态分析图 1 首先上升子序列的分析方法 以某个数字为结尾 2 其次公共子序列的分析方法 有4种状态 00 01 10 11 双关键字
  • 128、函数接口类---Consumer

    一 概念 java util function Consumer
  • Git常用命令fetch和pull和push

    Git常用命令pull和push 1 fetch 从远程获取代码库 会将所有远程分支都拉到本地 并不会合并代码 git fetch 下载远程仓库的所有变动 git fetch remote git fetch origin 指定拉取远程re
  • 百度App Objective-C/Swift 组件化混编之路(二)- 工程化

    作者丨张渝 郭金 来源丨百度App技术 前文 百度App Objective C Swift 组件化混编之路 已经介绍了百度App 引入 Swift 的影响面评估以及落地的实施步骤 本文主要以依赖管理工具为支撑 介绍百度App 如何实现组件