CocoaPods使用指南
前言
对于大多数软件开发团队来说,依赖管理工具必不可少,它能针对开源和私有依赖进行安装与管理,从而提升开发效率,降低维护成本。针对不同的语言与平台,其依赖管理工具也各有不同,例如 npm 管理 Javascript、Gradle 、Maven 管理 Jar 包、pip 管理 Python 包,Bundler、RubyGems 等等。本文聚焦于 iOS 方面,对 CocoaPods 的使用和部分原理进行阐述。
简单易用的 CocoaPods
对于 iOSer 来说,CocoaPods 并不陌生,几乎所有的 iOS 工程都会有它的身影。CocoaPods 采用 Ruby 构建,它是 Swift 和 Objective-C Cocoa 项目的依赖管理工具。在 MacOS 上,推荐使用默认的 Ruby 进行安装 (以下操作均在 CocoaPods 1.10.1、Ruby 2.7.2 进行):
sudo gem install cocoapods
如果安装成功,便可以使用 pod 的相关命令了。针对一个简单的项目来说,只需三步便可引入其他的依赖:
创建 Podfile 文件( CocoaPods 提供了 pod init 命令创建)
对 Podfile 文件进行编写,添加依赖的库,版本等信息。
在命令行执行 pod install
命令
顺利的话,这时在项目目录下会出现以下文件:
- .xcworkspace:CocoaPods 将项目分为了主工程与依赖工程(Pods)。与 .xcodeproj 相比 .xcworkspace 对于管理多个项目的能力更强,你也可以将复杂的大型应用转换为以 .xcworkspace 构建的多个兄弟项目,从而更轻松的维护和共享功能。
- Podfile.lock:记录并跟踪依赖库版本,将依赖库锁定于某个版本。
- Pods 文件夹:存放依赖库代码。
- Pods/Manifest.lock:每次
pod install
时创建的 Podfile.lock 的副本,用于比较这两个文件。一般来说,Podfile.lock 会纳入版本控制管理,而 Pods 文件夹则不会纳入版本控制变更;这意味着 Podfile.lock 表示项目应该依赖的库版本信息,而 Manifest.lock 则代表本地 Pods 的依赖库版本信息。在 pod install 后会将脚本插入到 Build Phases,名为[CP] Check Pods Manifest.lock
,从而保证开发者在运行 app 之前能够更新 Pods,以确保代码是最新的。
pod install vs. pod update
pod install
:在每一次编辑 Podfile 以添加、更新或删除 pod 时使用。它会下载并安装新的 Pod,并将其版本信息写入 Podfile.lock 中。pod outdated
:列出所有比 Podfile.lock 中当前记录的版本 newer 版本的 pod。pod update [PODNAME]
:CocoaPods 会查找 newer 版本的 PODNAME,同时将 pod 更新到可能的最新版本(须符合 Podfile 限制)。若没有 PODNAME,则会将每一个 pod 更新到可能的最新版本。
一般来说,每次编辑 Podfile 时使用 pod install
,仅在需要更新某个 pod 版本(所有版本)时才使用 pod update。同时,需提交 Podfile.lock 文件而不是 Pods 文件夹来达到同步所有 pod 版本的目的。
ps: newer 代表更加新的,若采用中文理解起来比较别扭。
Podfile 语法规范
Podfile 描述了一个或多个 Xcode 项目的 target 依赖关系,它是一种 DSL,了解它对我们使用好 CocoaPods 是一个必不可少的步骤。下面列出其相关的语法规范:
Root Options
install!:指定 CocoaPods 安装 Podfile 时使用的安装方法和选项。如:
install! 'cocoapods',:deterministic_uuids => false,:integrate_targets => false
:clean
:根据 podspec 和项目支持平台的指定,清理所有不被 pod 使用的文件,默认为 true。:deduplicate_targets
:是否对 pod target 进行重复数据删除,默认为 true。:deterministic_uuids
:创建 pod project 是否产生确定性 UUID,默认为 true。:integrate_targets
:是否继承到用户项目中,为 false 会将 Pod 下载并安装到到 project_path/Pods 目录下,默认为 true。:lock_pos_sources
:是否锁定 pod 的源文件,当 Xcode 尝试修改时会提示解锁文件,默认为 true。:warn_for_multiple_pod_sources
:当多个 source 包含同名同版本 pod 时是否发出警告,默认为 true。:warn_for_unused_master_specs_repo
:如果没有明确指出 master specs repo 的 git 是否发出警告,默认为 true。:share_schemes_for_development_pods
:是否为开发中的 pod 分享 schemes,默认为 false。:disable_input_output_paths
:是否禁用 CocoaPods 脚本阶段的输入输出路径(Copy Frameworks 和 Copy Resources),默认为 false。:preserve_pod_file_structure
:是否保留所有 pod 的文件结构,默认为 false。:generate_multiple_pod_projects
:是否为每一个 pod target 生成 一个 project,生成与 Pods/Pods 文件夹中,默认为 false。:incremental_installation
:仅对自上次安装的 target 与其关联的 project 的变更部分进行重新生成,默认为 false。:skip_pods_project_generation
:是否跳过生成 Pods.xcodeproj 并仅进行依赖项解析与下载,默认为 false。 ensure_bundler!:当 bundler 版本不匹配时发出警告。
ensure_bundler! '~> 2.0.0'
Dependencies
pod:指定项目的依赖项
- 依赖版本控制:=、>、>=、<、<= 为字面意思;~> 0.1.2 表示 0.1.2 <= currVersion < 0.2 之间的符合要求的最新版本版本。
- Build configurations:默认依赖安装在所有的构建配置中,但也可仅在指定构建配置中启用。
- Modular Headers:用于将 pod 转换为 module 以支持模块,这时在 Swift 中可以不用借助 bridging-header 桥接就可以直接导入,简化了 Swift 引用 Objective-C 的方式;也可以采用 use_modular_headers! 进行全局的变更。
- Source:指定具有依赖项的源,同时会忽略全局源。
- Subspecs:默认会安装所有的 subspecs,但可制定安装某些 subspecs。
- Test Specs:默认不会安装 test specs,但可选择性安装 test specs。
- Local path:将开发的 pod 与其客户端一起使用,可采用 path。
- 指定某个特殊或者更为先进的 pod 版本
# 依赖版本控制
pod 'Objection', '~> 0.9'
# Build configurations
pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
# Modular Headers
pod 'SSZipArchive', :modular_headers => true
# Source
pod 'PonyDebugger', :source => 'https://github.com/CocoaPods/Specs.git'
# Subspecs
pod 'QueryKit', :subspecs => ['Attribute', 'QuerySet']
# Test Specs
pod 'AFNetworking', :testspecs => ['UnitTests', 'SomeOtherTests']
# Local path
pod 'AFNetworking', :path => '~/Documents/AFNetworking'
# 指定某个特殊或者更为先进的 Pod 版本
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'
# 指定某个 podspec
pod 'JSONKit', :podspec => 'https://example.com/JSONKit.podspec'
inherit:设置当前 target 的继承模式。
:complete
继承父级 target 的所有行为,:none
不继承父级 target 的任何行为,:search_paths
仅继承父级的搜索路径。
target 'App' dotarget 'AppTests' doinherit! :search_pathsend
end
target:与 Xcode 中的 target 相对应,block 中是 target 的依赖项。
默认情况下,target 包含在父级 target 定义的依赖项,也即 inherit!
为 :complete
。关于 :complete
和 :search_paths
,:complete
会拷贝父级 target 的 pod 副本,而 :search_paths
则只进行 FRAMEWORK_SEARCH_PATHS
和 HEADER_SEARCH_PATHS
的相关拷贝,具体可通过比对 Pods/Target Support Files 的相关文件得以验证,一般在 UnitTests
中使用,以减少多余的 install_framework
过程。
target 'ShowsApp' dopod 'ShowsKit'# 拥有 ShowsKit 和 ShowTVAuth 的拷贝target 'ShowsTV' dopod 'ShowTVAuth'end# 拥有 Specta 和 Expecta 的拷贝# 并且能够通过 ShowsApp 进行访问 ShowsKit, 相当于 ShowsApp 是 ShowsTests 的宿主APPtarget 'ShowsTests' doinherit! :search_pathspod 'Specta'pod 'Expecta'end
end
abstract_target:定义 abstract_target
,方便 target 进行依赖继承,在 CocoaPods 1.0 版本之前为 link_with
。
abstract_target 'Networking' dopod 'AlamoFire'target 'Networking App 1'target 'Networking App 2'
end
abstract:表示当前 target 是抽象的,不会链接到 Xcode 的 target 中。
script_phase:添加脚本阶段。
在执行完 pod install 之后 CocoaPods 会将脚本添加到对应的 target build phases。
target 'App' do
script_phase {
:name => 'scriptName' # 脚本名称,:script => 'echo "nihao"' # 脚本内容,:execution_position => :before_compile / :after_compile:shell_path => '/usr/bin/ruby' # 脚本路径:input_files => ['/input/filePath'], # 输入文件:output_files => ['/outpput/filePath'] # 输出文件
}
end
Target configuration
platform:指定其构建平台。
默认值为 iOS 4.3、OSX 10.6、tvOS 9.0 和 watchOS 2.0。CocoaPods 1.0 之前的版本为 xcodeproj
platform :ios, '4.0'
project:指定包含 target 的 Xcode project。这一般在 workspace 存在多个 xcode project 中使用:
# 在 FastGPS Project 中可以找到一个名为 MyGPSApp 的 target
target 'MyGPSApp' doproject 'FastGPS'...
end
inhibit_all_warnings!:禁止所有警告。如果针对单个 Pod,则可以采用:
pod 'SSZipArchive', :inhibit_warnings => true
pod 'SSZipArchive', :inhibit_warnings => true
user_modular_headers!:将所有 Pod 模块化。如果针对单个 Pod,则可以采用:
pod 'SSZipArchive', :modular_headers => true
pod 'SSZipArchive', :modular_headers => false
user_frameworks!:采用 framework 而不是 .a 文件的静态库。
可以通过 :linkage 指定使用静态库还是动态库:
use_frameworks!:linkage => :dynamic / :static
supports_swift_versions:指定 target definition 支持的 swift 版本要求
supports_swift_versions '>= 3.0', '< 4.0'
Workspace
workspace:指定包含所有项目的 Xcode workspace。
workspace 'MyWorkspace'
Sources
sources:Podfile 从指定的源列表中进行检索。sources 默认存储在 ~/.cocoapods/repos 中,是全局的而非按 target definition 存储。当有多个相同的 Pod 时,优先采用检索到的 Pod 的第一个源,因此当指定另一个来源时,则需显示指定 CocoaPods 的源。
source 'https://github.com/artsy/Specs.git'
source 'https://github.com/CocoaPods/Specs.git'
Hooks
plugin:指定在安装期间使用的插件。
plugin 'cocoapods-keys', :keyring => 'Eidolon'
plugin 'slather'
pre_install:在下载后和在安装 Pod 前进行更改。
pre_install do |installer|# Do something fancy!
end
pre_integrate:在 project 写入磁盘前进行更改。
pre_integrate do |installer|# perform some changes on dependencies
end
post_install:对生成 project 写入磁盘前进行最后的修改。
post_install do |installer|installer.pods_project.targets.each do |target|target.build_configurations.each do |config|config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'endend
end
post_integrate:在 project 写入磁盘后进行最后更改。
post_integrate do |installer|# some change after project write to disk
end
podspec 语法规范
podspec = pod Specification,意为 pod 规范,它是一个 Ruby 文件。包含了 Pod 的库版本详细信息,例如应从何处获取源、使用哪些文件、要应用构建设置等信息;也可以看作该文件是整个仓库的索引文件,了解它对我们知道 Pod 库是如何组织、运作的提供了很大帮助。podspec 的 DSL 提供了极大的灵活性,文件可通过 pod spec create
创建。
Root
名称 | 用途 | 必需 |
---|---|---|
name | pod 名称 | required |
version | pod 版本,遵循语义化版本控制 | required |
swift_version | 支持的 Swift 版本 | |
cocoapods_version | 支持的 CocoaPods 版本 | |
authors | pod 维护者的姓名和电子邮件,用“, ”进行分割 | required |
license | pod 的许可证 | required |
homepage | pod 主页的 URL | required |
source | 源地址,即源文件的存放地址,支持多种形式源 | required |
summary | pod 的简短描述 | required |
prepare_command | 下载 pod 后执行的 bash 脚本 | |
static_framework | 是否采用静态 framework 分发 | |
deprecated | 该库是否已被弃用 | |
deprecated_in_favor_of | 该库名称已被弃用,取而代之 |
Pod::Spec.new do |s|s.name = 'CustomPod's.version = '0.1.0's.summary = 'A short description of CustomPod.'s.swift_versions = ['3.0', '4.0', '4.2']s.cocoapods_version = '>= 0.36's.author = { 'nihao' => 'XXXX@qq.com' }s.license = { :type => 'MIT', :file => 'LICENSE' }s.homepage = 'https://github.com/XXX/CustomPod'# Supported Key
# :git=> :tag, :branch, :commit,:submodules
# :svn=> :folder, :tag,:revision
# :hg=>:revision
# :http=> :flatten, :type, :sha256, :sha1,:headerss.source = { :git => 'https://github.com/XX/CustomPod.git', :tag => s.version.to_s }s.prepare_command = 'ruby build_files.rb's.static_framework = trues.deprecated = trues.deprecated_in_favor_of = 'NewMoreAwesomePod'
end
Platform
platform:pod 支持的平台,留空意味着 pod 支持所有平台。当支持多平台时应该用 deployment_target
代替。
spec.platform = :osx, '10.8'
deployment_target:允许指定支持此 pod 的多个平台,为每个平台指定不同的部署目标。
spec.ios.deployment_target = '6.0'
spec.osx.deployment_target = '10.8'
Build settings
dependency:基于其他 pods 或子规范的依赖
spec.dependency 'AFNetworking', '~> 1.0', :configurations => ['Debug']
info_plist:加入到生成的 Info.plist 的键值对,会对 CocoaPods 生成的默认值进行覆盖。仅对使用 framework 的框架有影响,对静态库无效。对于应用规范,这些值将合并到应用程序主机的 Info.plist
;对于测试规范,这些值将合并到测试包的 Info.plist。
spec.info_plist = {'CFBundleIdentifier' => 'com.myorg.MyLib','MY_VAR' => 'SOME_VALUE'
}
requires_arc:允许指定哪些 source_files 采用 ARC,不使用 ARC 的文件将具有 -fno-objc-arc
编译器标志
spec.requires_arc = false
spec.requires_arc = 'Classes/Arc'
spec.requires_arc = ['Classes/*ARC.m', 'Classes/ARC.mm']
frameworks:使用者 target 需要链接的系统框架列表
spec.ios.framework = 'CFNetwork'
spec.frameworks = 'QuartzCore', 'CoreData'
weak_frameworks:使用者 target 需要弱链接的框架列表
spec.weak_framework = 'Twitter'
spec.weak_frameworks = 'Twitter', 'SafariServices'
libraries:使用者 target 需要链接的系统库列表
spec.ios.library = 'xml2'
spec.libraries = 'xml2', 'z'
compiler_flags:应传递给编译器的 flags
spec.compiler_flags = '-DOS_OBJECT_USE_OBJC=0', '-Wno-format'
pod_target_xcconfig:将指定 flag 添加到最终 pod 的 xcconfig 文件
spec.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-lObjC' }
user_target_xcconfig:🙅 将指定 flag 添加到最终聚合的 target 的 xcconfig,不推荐使用此属性,因为会污染用户的构建设置,可能会导致冲突。
spec.user_target_xcconfig = { 'MY_SUBSPEC' => 'YES' }
prefix_header_contents:🙅 在 Pod 中注入的预编译内容,不推荐使用此属性,因为其会污染用户或者其他库的预编译头。
spec.prefix_header_contents = '#import <UIKit/UIKit.h>', '#import <Foundation/Foundation.h>'
prefix_header_file:预编译头文件,false 表示不生成默认的 CocoaPods 的与编译头文件。🙅 不推荐使用路径形式,因为其会污染用户或者其他库的预编译头。
spec.prefix_header_file = 'iphone/include/prefix.pch'
spec.prefix_header_file = false
module_name:生成的 framrwork / clang module 使用的名称,而非默认名称。
spec.module_name = 'Three20'
header_dir:存储头文件的目录,这样它们就不会被破坏。
spec.header_dir = 'Three20Core'
header_mappings_dir:用于保留头文件文件夹的目录。如未提供,头文件将被碾平。
spec.header_mappings_dir = 'src/include'
script_phases:该属性允许定义脚本在 pod 编译时执行,其作为 xcode build
命令的一部分执行,还可以利用编译期间所设置的环境变量。
spec.script_phases = [{ :name => 'Hello World', :script => 'echo "Hello World"' },{ :name => 'Hello Ruby World', :script => 'puts "Hello World"', :shell_path => '/usr/bin/ruby' },]
File patterns
文件模式指定了库的所有文件管理方式,如源代码、头文件、framework、libaries、以及各种资源。其文件模式通配符形式可参考 LINK。
source_files:指定源文件
spec.source_files = 'Classes/**/*.{h,m}', 'More_Classes/**/*.{h,m}'
public_header_files:指定公共头文件,这些头文件与源文件匹配,并生成文档向用户提供。如果未指定,则将 source_files 中的所有头文件都包含生成。
spec.public_header_files = 'Headers/Public/*.h'
project_header_files:指定项目头文件,与公共头文件相对应,以排除不应向用户项目公开且不应用于生成文档的标头,且不会出现在构建目录中。
spec.project_header_files = 'Headers/Project/*.h'
private_header_files:私有头文件,与公共头文件对应,以排除不应向用户项目公开且不应用于生成文档的标头,这些头文件会出现在产物中的 PrivateHeader 文件夹中。
spec.private_header_files = 'Headers/Private/*.h'
vendered_frameworks:pod 附加的 framework 路径
spec.ios.vendored_frameworks = 'Frameworks/MyFramework.framework'
spec.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.xcframework'
vendered_libraries:pod 附加的 libraries 路径
spec.ios.vendored_library = 'Libraries/libProj4.a'
spec.vendored_libraries = 'libProj4.a', 'libJavaScriptCore.a'
on_demand_resources:根据 Introducing On demand Resources 按需加载资源,不推荐与主工程共享标签,默认类别为 category => :download_on_demand
s.on_demand_resources = {'Tag1' => { :paths => ['file1.png', 'file2.png'], :category => :download_on_demand }
}
s.on_demand_resources = {'Tag1' => { :paths => ['file1.png', 'file2.png'], :category => :initial_install }
}
resources:为 pod 构建的 bundle 的名称和资源文件,其中 key 为 bundle 名称,值代表它们应用的文件模式。
spec.resource_bundles = {
'MapBox' => ['MapView/Map/Resources/*.png'],'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']
}
exclude_files:排除的文件模式列表
spec.ios.exclude_files = 'Classes/osx'
spec.exclude_files = 'Classes/**/unused.{h,m}'
preserve_paths:下载后不应删除的文件。默认情况下,CocoaPods 会删除与其他文件模式不匹配的所有文件
spec.preserve_path = 'IMPORTANT.txt'
spec.preserve_paths = 'Frameworks/*.framework'
module_map:pod 继承为 framework 时使用的模块映射文件,默认为 true,CocoaPods 根据 公共头文件创建 module_map 文件。
spec.module_map = 'source/module.modulemap'
spec.module_map = false
Subspecs
subspec:子模块的规范;实行双重继承:specs 自动继承所有 subspec 作为依赖项(除非指定默认 spec);subspec 继承了父级的属性;
# 采用不同源文件的 Specs, CocoaPods 自动处理重复引用问题
subspec 'Twitter' do |sp|sp.source_files = 'Classes/Twitter'
endsubspec 'Pinboard' do |sp|sp.source_files = 'Classes/Pinboard'
end# 引用其他子规范
s.subspec "Core" do |ss|ss.source_files = "Sources/Moya/", "Sources/Moya/Plugins/"ss.dependency "Alamofire", "~> 5.0"ss.framework = "Foundation"ends.subspec "ReactiveSwift" do |ss|ss.source_files = "Sources/ReactiveMoya/"ss.dependency "Moya/Core"ss.dependency "ReactiveSwift", "~> 6.0"ends.subspec "RxSwift" do |ss|ss.source_files = "Sources/RxMoya/"ss.dependency "Moya/Core"ss.dependency "RxSwift", "~> 5.0"end
end# 嵌套子规范
Pod::Spec.new do |s|s.name = 'Root's.subspec 'Level_1' do |sp|sp.subspec 'Level_2' do |ssp|endend
end
default_subspecs:默认子规范数组名称,不指定将全部子规范作为默认子规范,:none
表示不需要任何子规范。
spec.default_subspec = 'Core'
spec.default_subspecs = 'Core', 'UI'
spec.default_subspecs = :none
scheme:用以给指定 scheme configuration 添加拓展
spec.scheme = { :launch_arguments => ['Arg1'] }
spec.scheme = { :launch_arguments => ['Arg1', 'Arg2'], :environment_variables => { 'Key1' => 'Val1'} }
test_spec:测试规范,在 1.8 版本支持。可参考:CocoaPods 1.8 Beta
requires_app_host:是否需要宿主 APP 运行测试,仅适用于测试规范。
app_host_name:必要时作用于应用程序的应用程序规范名称
app_spec:宿主 APP 规范
Pod::Spec.new do |s|s.name = 'CannonPodder's.version = '1.0.0'# ...rest of attributes heres.app_spec 'DemoApp' do |app_spec|app_spec.source_files = 'DemoApp/**/*.swift'# Dependency used only by this app spec.app_spec.dependency 'Alamofire'ends.test_spec 'Tests' do |test_spec|test_spec.requires_app_host = true# Use 'DemoApp' as the app host.test_spec.app_host_name = 'CannonPodder/DemoApp'# ...rest of attributes here# This is required since 'DemoApp' is specified as the app host.test_spec.dependency 'CannonPodder/DemoApp'end
end
Multi-Platform support
存储特定于某一个平台的值,分别为 ios、osx、macOS、tvos、watchos:
spec.resources = 'Resources/**/*.png'
spec.ios.resources = 'Resources_ios/**/*.png'
Pod 的开发流程
了解完 Podfile 和 podspec 的相关的规范之后,那么开发自己的 pod 应该是一件驾轻就熟的事。
Spec Repo
Spec Repo 是 podspec 的仓库,即是存储相关的 podspec 文件的地方。本地源存储于 ~/.cocoapods/repos中,它从 git 上拉取并完全保留目录结构。可以发现, Master Specs Repo 的现在目录结构有些特殊;以往版本的 Master Spec Repo 是完全在同一目录下的,但若大量文件在同一目录中会导致了 Github 下载慢 的问题。为解决这个问题,采用散列表形式处理。具体方式为对名称进行 MD5 计算得到散列值,取前三位作为目录前缀,以对文件分散化。初次之外,CocoaPods 后续还采用 CDN 以及 trunk 进一步加快下载速度,有兴趣可以参考 CocoaPods Source 管理机制。
如:md5("CJFoundation") => 044d913fdd5a52b303222c357521f744
;CJFoundation
则在 /Specs/0/4/4 目录中
Create
只需利用 pod lib create [PodName]
命令便可以快速创建一个自己的 pod 。填写好使用平台、使用语言、是否包含 Demo、测试框架等信息,CocoaPods 会从默认的 Git 地址中拉取一份 pod 模版,同时也可以通过 --template-url=URL
指定模版地址。在执行完后,整个文件结构如下:
tree CustomPod -L 2
CustomPod
├── CustomPod
│ ├── Assets // 存放资源文件
│ └── Classes
│ └── RemoveMe.[swift/m] // 单一文件以确保最初编译工作
├── CustomPod.podspec // Pod 的 spec 文件, 是一个 Pod 依赖的索引以及规范信息
├── Example // 用作演示/测试的示例项目
│ ├── CustomPod
│ ├── CustomPod.xcodeproj
│ ├── CustomPod.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ └── Tests
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj // 指向 Pods 项目的以获得 Carthage 支持
├── LICENSE // 许可证
└── README.md // 自述文件
Development
将源文件和资源分别放入 Classes / Assets 文件夹中,或者按你喜欢的方式组织文件,并在 podspec 文件中编辑相应项。如果你有任何想使用的配置项,可参考前面的podsepc 语法规范 。
一般来说,开发 Pod 一般都是作为本地 Pod 被其他 Project 所依赖进行开发,无论是使用 example 文件夹的 project 或者其他的 Project。
pod 'Name', :path => '~/CustomPod/'
Testing
通过 pod lib lint
以验证 Pod 仓库的使用是否正常。
Release
前面提到过 podspec 可以看作是整个仓库的索引文件,有了这个文件也就能组织起一个 Pod。因此官方的源以及私有源都只需要 podspec 即可,而其他文件则应推送到 podspec 中 source 中指定仓库,这个仓库应该是你自创建的。
在准备发布推送源代码时,需要更新版本号以及在 git 上打上 tag,这是为了进行版本号匹配,因为默认情况下的 podspec 文件中:
s.source = { :git => 'https://github.com/XXX/CustomPod.git', :tag => s.version.to_s }
可能你的工作流操作如下:
$ cd ~/code/Pods/NAME
$ edit NAME.podspec
# set the new version to 0.0.1
# set the new tag to 0.0.1
$ pod lib lint
$ git add -A && git commit -m "Release 0.0.1."
$ git tag '0.0.1'
$ git push --tags
存有几种方式推送 podspec 文件:
- 推送到公共仓库,需要用到的 trunk 子命令,更多可以参考 Getting setup with Trunk:
# 通过电子邮箱进行注册
pod trunk register orta@cocoapods.org 'Orta Therox' --description='macbook air'
# 将指定podspec文件推送到公共仓库中
pod trunk push [NAME.podspec]
# 添加其他人作为协作者
pod trunk add-owner ARAnalytics kyle@cocoapods.org
- 推送到私有源,例如 Artsy/Specs,需要用到 repo 子命令,更多可以参考 Private Pods:
# 将私有源地址添加到本地
pod repo add REPO_NAME SOURCE_URL
# 检查私有源是否安装成功并准备就绪
cd ~/.cocoapods/repos/REPO_NAME
pod repo lint .
# 将Pod的podspec添加到指定REPO_NAME中
pod repo push REPO_NAME SPEC_NAME.podspec
- 不推送到任何源中,若能存在以 URL 方式检索到 podspec文件,则可用该 URL,一般采用仓库地址,例如:
pod 'AFNetworking', :git => 'https://github.com/XXX/CustomPod.git'
Semantic Versioning
语义化版本控制顾名思义是一种语义上的版本控制,它不要求强制遵循,只是希望开发者能够尽量遵守。如果库之间依赖关系过高,可能面临版本控制被锁死的风险(可能需要对每一个依赖库改版才能完成某次升级);如果库之间依赖关系过于松散,又将无法避免版本的混乱(可能库兼容性不再能支持以往版本),语义化版本控制正是作为这个问题的解决方案之一。无论在 CocoaPods 中,还是 Swift Packager Manager 上,官方都希望库开发者的的版本号能遵循这一原则:
例如,给定版本号 MAJOR.MINOR.PATCH
:
MAJOR
:进行不兼容的 API 更改时进行修改MINOR
:向后兼容的方式添加新功能时进行修改PATCH
:进行向后兼容的错误修复时进行修改
先行版本号以及版本编译信息可以添加到 MAJOR.MINOR.PATCH
后面以作为延伸。
CocoaPods 原理浅析
CococaPods 核心组件
CocoaPods 被 Ruby 管理,其核心部分也被分为一个一个组件。下载源码,可以看到 Gemfile 文件如下,其依赖了若干个 gem,有意思的是 cp_gem
函数,通过 SKIP_UNRELEASED_VERSIONS
与 path
来控制是否采用本地的 gem 路径,实现了 DEVELOPMENT 与 RELEASE 环境的切换。
SKIP_UNRELEASED_VERSIONS = false
# Declares a dependency to the git repo of CocoaPods gem. This declaration is
# compatible with the local git repos feature of Bundler.
def cp_gem(name, repo_name, branch = 'master', path: false)return gem name if SKIP_UNRELEASED_VERSIONSopts = if path{ :path => "../#{repo_name}" }elseurl = "https://github.com/CocoaPods/#{repo_name}.git"{ :git => url, :branch => branch }endgem name, opts
endsource 'https://rubygems.org'gemspecgroup :development docp_gem 'claide', 'CLAide'cp_gem 'cocoapods-core', 'Core'cp_gem 'cocoapods-deintegrate', 'cocoapods-deintegrate'cp_gem 'cocoapods-downloader', 'cocoapods-downloader'cp_gem 'cocoapods-plugins', 'cocoapods-plugins'cp_gem 'cocoapods-search', 'cocoapods-search'cp_gem 'cocoapods-trunk', 'cocoapods-trunk'cp_gem 'cocoapods-try', 'cocoapods-try'cp_gem 'molinillo', 'Molinillo'cp_gem 'nanaimo', 'Nanaimo'cp_gem 'xcodeproj', 'Xcodeproj'gem 'cocoapods-dependencies', '~> 1.0.beta.1'...
end
这些组件相对独立,被分成一个一个 Gem 包,在 Core Components 中,可以找到对这些组件的简要描述。同时也可以到 CocoaPods 的 Github 中去看详细文档。
- CocoaPods:命令行支持与安装程序,也会处理 CocoaPods 的所有用户交互。
- cocoapods-core:对模版文件的解析,如 Podfile、.podspec 等文件。
- CLAide:一个简单的命令解析器,它提供了一个快速创建功能齐全的命令行界面的 API。
- cocoapods-downloader:用于下载源码,为各种类型的源代码控制器(HTTP/SVN/Git/Mercurial) 提供下载器。它提供 tags、commites、revisions、branches 以及 zips 文件的下载与解压缩操作。
- Monlinillo:CocoaPods:对于依赖仲裁算法的封装,它是一个具有前项检察的回溯算法。不仅在 pods 中,Bundler 和 RubyGems 也是使用这一套仲裁算法。
- Xcodeproj:通过 Ruby 来对 Xcode projects 进行创建于修改。如:脚本管理、libraries 构建、Xcode workspece 和配置文件的管理。
- cocoapods-plugins:插件管理,其中有 pod plugins 命令帮助你获取的可用插件列表以及开发一个新插件等功能,具体可用
pod plugins --help
了解。
pod install 做了什么
执行 pod install --verbose
,会显示 pod install 过程中的更多 debugging 信息。下文主要参考:整体把握 CocoaPods 核心组件
经过消息转发与 CLAide 命令解析,最终调用了 CocoaPods/lib/cocoapods/installer.rb 的 install! 函数,主要流程图如下:
def install!prepareresolve_dependenciesdownload_dependenciesvalidate_targetsclean_sandboxif installation_options.skip_pods_project_generation?show_skip_pods_project_generation_messagerun_podfile_post_install_hookselseintegrateendwrite_lockfilesperform_post_install_actions
end
1. Install 环境准备(prepare)
def prepare# 如果检测出当前目录是 Pods,直接 raise 终止if Dir.pwd.start_with?(sandbox.root.to_path)message = 'Command should be run from a directory outside Pods directory.'message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"raise Informative, messageendUI.message 'Preparing' do# 如果 lock 文件的 CocoaPods 主版本和当前版本不同,将以新版本的配置对 xcodeproj 工程文件进行更新deintegrate_if_different_major_version# 对 sandbox(Pods) 目录建立子目录结构sandbox.prepare# 检测 PluginManager 是否有 pre-install 的 pluginensure_plugins_are_installed!# 执行插件中 pre-install 的所有 hooks 方法run_plugins_pre_install_hooksend
end
在 prepare 阶段会完成 pod install
的环境准备,包括目录结构、版本一致性以及 pre_install
的 hook。
2. 解决依赖冲突(resolve dependencies)
def resolve_dependencies# 获取 Sourcesplugin_sources = run_source_provider_hooks# 创建一个 Analyzeranalyzer = create_analyzer(plugin_sources)# 如果带有 repo_update 标记UI.section 'Updating local specs repositories' do# 执行 Analyzer 的更新 Repo 操作analyzer.update_repositoriesend if repo_update?UI.section 'Analyzing dependencies' do# 从 analyzer 取出最新的分析结果,@analysis_result,@aggregate_targets,@pod_targetsanalyze(analyzer)# 拼写错误降级识别,白名单过滤validate_build_configurationsend# 如果 deployment? 为 true,会验证 podfile & lockfile 是否需要更新UI.section 'Verifying no changes' doverify_no_podfile_changes!verify_no_lockfile_changes!end if deployment?analyzer
end
通过 Podfile、Podfile.lock 以及 manifest.lock 等生成 Analyzer 对象,其内部会使用个 Molinillo 算法解析得到一张依赖关系表,进行一系列的分析与依赖冲突解决。
3. 下载依赖文件(download dependencies)
def download_dependenciesUI.section 'Downloading dependencies' do# 构造 Pod Source Installerinstall_pod_sources# 执行 podfile 定义的 pre install 的 hooksrun_podfile_pre_install_hooks# 根据配置清理 pod sources 信息,主要是清理无用 platform 相关内容clean_pod_sourcesend
end
经过前面分析与解决依赖冲突后,这是会进行依赖下载。会根据依赖信息是否被新添加或者修改等信息进行下载,同时下载后也会在本地留有一份缓存,其目录在 ~/Library/Caches/CocoaPods 。
4. 验证 targets(validate targets)
def validate_targetsvalidator = Xcode::TargetValidator.new(aggregate_targets, pod_targets, installation_options)validator.validate!
enddef validate!verify_no_duplicate_framework_and_library_namesverify_no_static_framework_transitive_dependenciesverify_swift_pods_swift_versionverify_swift_pods_have_module_dependenciesverify_no_multiple_project_names if installation_options.generate_multiple_pod_projects?
end
- verify_no_duplicate_framework_and_library_names:验证是否有重名的 framework / library
- verify_no_static_framework_transitive_dependencies:验证动态库是否有静态链接库依赖。个人认为,这个验证是不必要的,起码不必要 error。
- verify_swift_pods_swift_version:验证 Swift pod 的 Swift 版本配置且相互兼容
- verify_swift_pods_have_module_dependencies:验证 Swift pod 是否支持 module
- verify_no_multiple_project_names:验证没有重名的 project 名称
5. 生成工程(Integrate)
def integrategenerate_pods_projectif installation_options.integrate_targets?# 集成用户配置,读取依赖项,使用 xcconfig 来配置integrate_user_projectelseUI.section 'Skipping User Project Integration'end
enddef generate_pods_project# 创建 stage sanbox 用于保存安装前的沙盒状态,以支持增量编译的对比stage_sandbox(sandbox, pod_targets)# 检查是否支持增量编译,如果支持将返回 cache resultcache_analysis_result = analyze_project_cache# 需要重新生成的 targetpod_targets_to_generate = cache_analysis_result.pod_targets_to_generate# 需要重新生成的 aggregate targetaggregate_targets_to_generate = cache_analysis_result.aggregate_targets_to_generate# 清理需要重新生成 target 的 header 和 pod foldersclean_sandbox(pod_targets_to_generate)# 生成 Pod Project,组装 sandbox 中所有 Pod 的 path、build setting、源文件引用、静态库文件、资源文件等create_and_save_projects(pod_targets_to_generate, aggregate_targets_to_generate,cache_analysis_result.build_configurations, cache_analysis_result.project_object_version)# SandboxDirCleaner 用于清理增量 pod 安装中的无用 headers、target support files 目录SandboxDirCleaner.new(sandbox, pod_targets, aggregate_targets).clean!# 更新安装后的 cache 结果到目录 `Pods/.project_cache` 下update_project_cache(cache_analysis_result, target_installation_results)
end
将之前版本仲裁的所有组件通过 project 文件的形式组织起来,并对 project 中做一些用户指定的配置。
6. 写入依赖(write lockfiles)
def write_lockfiles@lockfile = generate_lockfileUI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do# No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the# contents of the file are the same.@lockfile.write_to_disk(config.lockfile_path)endUI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do# No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the# contents of the file are the same.@lockfile.write_to_disk(sandbox.manifest_path)end
end
将依赖更新写入 Podfile.lock 与 Manifest.lock
7. 结束回调(perform post install action)
def perform_post_install_actions# 调用 HooksManager 执行每个插件的 post_install 方法 run_plugins_post_install_hooks# 打印过期 pod target 警告warn_for_deprecations# 如果 pod 配置了 script phases 脚本,会主动输出一条提示消息warn_for_installed_script_phases# 警告移除的 master specs repo 的 specswarn_for_removing_git_master_specs_repo# 输出结束信息 `Pod installation complete!`print_post_install_message
end
最后的收尾工作,进行 post install action
的 hook 执行以及一些 warning 打印。
CocoaPods + Plugins
早在 2013 年,CocoaPods 就添加了对插件的支持,以添加不符合依赖管理和生态系统增长为主要目标的功能。CocoaPods Plugins 可以:在 install 前后添加 hook、添加新命令到 pod、以及利用 Ruby 动态性做任何事。下面介绍一下常见的插件:
- cocoapods-binary:一个比较早期的二进制插件库,是诸多二进制方案的灵感来源
- cocoapods-repo-update:自动化 pod repo update
- cocoapods-integrate-flutter:将 flutter 与现有 iOS 应用程序集成
- cocoapods-uploader:上传文件/目录到远程仓库
ps:许多插件可能许久未维护,读者使用需自行斟酌。
不太常见概念
CocoaPods 的配置内容几乎包含了 Xcode Build 的方方面面,因此存在许多不太常见的概念,在此做一个链接聚合以供参考。
- Clang Module / module_map / umbrella header:Clang Module 是 Clang 16.0.0 中引入的概念,用以解决 #include / #import 头文件引入导致的相关问题;module_map 是用以描述 clang module 与 header 的关系;umbrella header 则是 module_map 中的语法规范,表示指定目录中的头文件都应包含在模块中。
Modules
Clang Module
LLVM 中的 Module
- Hmap / Xcode Header / CocoaPods Headers
Header Map 是一组头文件信息映射表,用 .hmap 后缀表示,整体结构以 Key-Value 形式存储;Key为头文件名称、Value 为 头文件物理地址。
Xcode Phases - Header 在构建配置中分为 public、private 与 project ,用以与 target 关联;其中 public 、private 就复制到最终产物的 header 和 PrivateHeaders 中,而 project 头文件不对外使用,则不会放到最终产物。
一款可以让大型iOS工程编译速度提升50%的工具
What are build phases?
- Xcconfig:
一种配置文件,用以对构建设置进行声明与管理,比如区分不同的开发环境等。
Xcode Build Configuration Files
- On demand resource:WWDC 2015 引入的概念,对资源文件的按需加载。
Introducing On Demand Resources
🔗:
[1] Cocoapods.org
[2] Xcode Workspace with multiple projects
[3] 深入理解 CocoaPods
[4] 系统理解 iOS 库与框架
[5] Cocoapods script phases
[6] CocoaPods Podfile 解析原理
[7] Semantic Versioning 2.0.0
[8] 一款可以让大型iOS工程编译速度提升50%的工具
[9] CocoaPods Source 管理机制
[10] 版本管理工具及 Ruby 工具链环境
[11] 整体把握 CocoaPods 核心组件
[12] 工程效率优化:CocoaPods优化
作者:nihao1011
链接:https://juejin.cn/post/7179231344147300412
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章:
CocoaPods使用指南
前言 对于大多数软件开发团队来说,依赖管理工具必不可少,它能针对开源和私有依赖进行安装与管理,从而提升开发效率,降低维护成本。针对不同的语言与平台,其依赖管理工具也各有不同,例如 npm 管理 Javascri…...

Kafka 消息队列
目录主流的消息队列消息队列的应用场景缓存/肖锋解耦异步处理KafkaKafka的定义Kafka的底层基础架构Kafka分区如何保证Leader选举Kafka分区如何保证Leader和Follower数据的一致性Kafka 中消费者的消费方式Kafka 高效读写数据的原因(高性能吞吐的原因)&…...
华为OD机试 - 挑选字符串(Python)| 真题+思路+考点+代码+岗位
挑选字符串 题目 给定a-z,26 个英文字母小写字符串组成的字符串A和B, 其中A可能存在重复字母,B不会存在重复字母, 现从字符串A中按规则挑选一些字母可以组成字符串B 挑选规则如下: 同一个位置的字母只能挑选一次, 被挑选字母的相对先后顺序不能被改变, 求最多可以同时…...

对比Hashtable、HashMap、TreeMap有什么不同?
第9讲 | 对比Hashtable、HashMap、TreeMap有什么不同? Map 是广义 Java 集合框架中的另外一部分,HashMap 作为框架中使用频率最高的类型之一,它本身以及相关类型自然也是面试考察的热点。 今天我要问你的问题是,对比 Hashtable、…...

测试新版Android Studio的手机镜像效果
学更好的别人, 做更好的自己。 ——《微卡智享》 本文长度为669字,预计阅读2分钟 前言 春节刚上班,就开始了疯狂出差的节奏,期间发现Android Studio发布新的版本2022.1.1(Electric Eel),里面两个更新的内容蓝牙模拟器和…...

女生可以参加IT培训吗?
2023年了,就不要把性别当作选择专业的前提条件了。虽然这句话说过很多次了,作为IT行业来说,是非常欢迎女生的加入;尤其是整天都是面对一大堆男攻城狮,工作氛围一点都不活跃,反而显得压抑和杂乱,…...

刷题25-重排链表
重排链表 解题思路:通过观察链表可以发现,把链表一分为二,后半段链表反转,然后两个链表穿插连接,当链表的节点总数是奇数时,要保证链表的前半段比后半段多一个节点。 关于把链表一分为二,可以…...

VHDL-延迟模型-惯性延迟与传输延迟
目录 1,惯性延时 2,传输延时 信号通过元件都会有延迟,延迟时间的计算是逻辑仿真的重要功能。考虑延迟信息得到的仿真输出波形可以更精确地反映实际电路的情况。针对元件的延时,人们根据需要建立了一些用的延时模型,这…...

2023年美赛(MCM/ICM)简介
2023年美赛将要如期开赛,这里为了 让大家对今年的美赛有一个直接 客观的了解。对2023年美赛(MCM/ICM)进行一下简要的介绍。相关资料大家可以查看另一篇文章一、竞赛时间February 16-20, 2023开赛时间 北京时间 17号(本周五) 6:00结束时间 北京时间 21号(…...

5min完成linux环境Jenkins的安装
5min搞定linux环境Jenkins的安装安装Jenkinsstep1: 使用wget 命令下载Jenkinsstep2、创建Jenkins日志目录并运行jekinsstep3、访问jenkins并解锁jenkins,安装插件以及创建管理员用户step4、到此,就完成了Finish、以上步骤中遇到的问题1、 jenkins启动不了…...
华为OD机试 - 字母计数(Python)| 真题+思路+考点+代码+岗位
字母计数 题目 给出一个只包含字母的字符串, 不包含空格,统计字符串中各个子字母(区分大小写)出现的次数, 并按照字母出现次数从大到小的顺序输出各个字母及其出现次数 如果次数相同,按照自然顺序排序,且小写字母在大写字母之前 输入 输入一行仅包含字母的字符串 输出 按…...

DENSE 数据集 - STF 数据集(CVPR 2020)
DENSE 数据集 - STF 数据集 - Seeing Through Fog Without Seeing Fog: Deep Multimodal Sensor Fusion in Unseen Adverse Weather(CVPR 2020)摘要1. 引言2. 相关工作3. 多模式恶劣天气数据集3.1 多模态传感器设置3.2 记录4. 自适应深度融合4.1 自适应多…...
华为OD机试 - 静态扫描最优成本(Python)| 真题+思路+考点+代码+岗位
静态扫描最优成本 题目 静态扫描快速识别源代码的缺陷,静态扫描的结果以扫描报告作为输出: 文件扫描的成本和文件大小相关,如果文件大小为 N ,则扫描成本为 N 个金币扫描报告的缓存成本和文件大小无关,每缓存一个报告需要 M 个金币扫描报告缓存后,后继再碰到该文件则不…...

【ns-3】零基础安装教程
文章目录前言1. 安装虚拟机及Ubuntu2. 安装依赖库3. 下载ns-34. 构建ns-3前言 近期因工作需要开始接触ns-3。作者零基础,从零开始顺利完成了ns-3的安装。本篇为ns-3安装过程记录贴或针对小白的零基础教程。 本篇内容所使用到的软件版本信息如下:VMware…...
华为OD机试 - 新学校选址(Python)| 真题+思路+考点+代码+岗位
新学校选址 题目 为了解新学期学生暴涨的问题,小乐村要建立所新学校 考虑到学生上学安全问题,需要所有学生家到学校的距离最短. 假设学校和所有学生家都走在一条直线之上,请问学校建立在什么位置, 能使得到学校到各个学生家的距离和最短 输入 第一行: 整数 n 取值范围 [1,1…...
华为OD机试 - 最长合法表达式(Python)| 真题+思路+考点+代码+岗位
最长合法表达式 题目 提取字符串中的最长合法简单数学表达式, 字符串长度最长的,并计算表达式的值。 如果没有返回0. 简单数学表达式只能包含以下内容: 0-9数字,符号+-* 说明: 所有数字,计算结果都不超过long如果有多个长度一样的,请返回第一个表达式的结果数学表达式…...

夭寿啦!我的网站被攻击了了735200次还没崩
记得有一个看到鱼皮的网站被攻击,那时候我只是一个小小号,还在调侃,没想到我居然也有那么一天! 突袭 一个风和日丽中午,我正在和同事吃饭,一个内存oom,我的小破站崩溃了。 虽然天天被攻击吧&a…...

Java 反射深入浅出
Java 反射深入浅出📈 反射的概述:📑 Java Reflection(反射) 被视为动态语言的关键,Java并不是动态语言,但因为反射Java可以被称为准动态语言 反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息&a…...

Windows系统,安装RabbitMQ
官网地址:https://rabbitmq.com 版本:RabbitMQ 3.10.7 (1)查看支持的Erlang版本:https://rabbitmq.com/which-erlang.html (2)下载支持的的erlang版本:https://github.com/erlang/…...
代码随想录第十二天(232)
文章目录232. 用栈实现队列补充知识——Deque232. 用栈实现队列 答案思路: 在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来࿰…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...

算术操作符与类型转换:从基础到精通
目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...

DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...