3D球场项目 (六):客户端工程化与优化

3D球场项目 (五) 把客户端的核心业务流 讲完了:Native 推送 → DataManager 队列 → ScenePlayer 章节调度 → GameController 状态机 → Handler → 渲染层。功能闭环跑通之后,真正的硬仗是 “工程化"和"性能”——前者保证 项目能稳定上线(编译跑通、退出不崩、Swift 调用顺手),后者保证在中低端机上画面流畅。 第一版上线时遇到了两类问题: 稳定性 / 工程化:modulemap 编译报错、lipo 架构冲突、A11 设备启动 crash、 Cocos 退出期 OpenAL 资源竞争 crash……这些都不是"画面卡",但每一个都能让用户体验 归零; 性能:中端 iPhone 在比赛回合渲染期间,GPU 和 CPU 都偶尔抖到 80%+,帧率掉到 30 fps 出头。 本篇按这两类拆成两部分,所有改动都遵循一条原则:不动玩法,只把工程问题和帧时间 解决掉。 架构改动一览 3D球场项目 (二) 介绍过弹幕引擎的四层 架构(腾讯视频 APP / MagicDanmakuiOS / MagicDanmaku / cocos-engine)。这一阶段 每一层都动了——3D 球场不是单点改造,而是一次自上而下的纵贯。下面这张图是把 PartTwo 那张原始架构图重画一遍,改动的节点用蓝色、新增的节点用绿色, 没动的节点保持白色: ...

May 31, 2026

编译优化-二进制化

二进制化是大型 iOS 工程编译加速的"银弹":把组件从源码编译改成预编译产物链接,在本地无需再跑 Swift/Clang 前端,直接链接已有的 .a / .framework / .xcframework。美团在 cocoapods-hmap-prebuilt 之外,还通过二进制化把整体编译速度提升 50%+。本文系统介绍二进制化的原理、实现方式与工具链。 为什么能加速 典型项目的编译时间分布(抖音量级): pie title 冷编译耗时占比 "业务代码编译" : 25 "第三方依赖编译" : 50 "链接" : 15 "其他" : 10 超过一半的时间都花在编译 “不会改” 的依赖上。把依赖预编译成二进制,本地只需要链接,这部分耗时直接归零。再配合远程缓存,第一次拉代码的同学也能命中他人产物。 二进制产物形态 静态库(.a) 最传统的形态: libSDWebImage.a SDWebImage/Headers/ ├── SDWebImage.h └── ... 特点: 最小体积,Mach-O 里没有 LC_SEGMENT_64 / LC_LOAD_DYLIB 负担 启动最快,无 dyld 成本 静态链接时整合到主可执行 不支持资源文件(需要单独管理 bundle) Framework(.framework) Apple 推荐的打包形态: SDWebImage.framework/ ├── SDWebImage # 二进制(静态或动态) ├── Headers/ # 公开头文件 ├── Modules/ # modulemap、swiftmodule │ ├── module.modulemap │ └── SDWebImage.swiftmodule/ │ ├── arm64-apple-ios.swiftmodule │ └── arm64-apple-ios.swiftinterface └── Info.plist 通过 Mach-O Type 设置决定内部是静态还是动态库: ...

May 2, 2026

代码规范工程化

规范化是前端工程化的一个重要部分。现在,有许多工具能够辅助我们实行代码的规范化,比如你一定知道的 ESLint 和 Prettier。 今天,来聊聊这些工具的工作原理和基本使用,了解它们是如何发挥作用的,以及如何更好地利用这些工具去规范项目的代码。 1. ESlint - 检查你的 JavaScript 代码 让我们先从知名度最高的 ESLint 开始。 1.1. ESLint 及其作用 Lint 是一类专门用于检查代码的工具软件, 也称 linter。ESLint ,即 JavaScript (ECMAScript)代码的检查工具。 正如官网的介绍 —— “Find and fix problems in your JavaScript code”,ESLint 能够辅助查找出你的 JavaScript 代码中的问题,包括: 代码风格问题(styles)。比如,运算符两边的空格、语句末尾的分号。 不好的写法。比如,使用 == 进行比较而不是 ===。 可能存在逻辑问题的代码模式。比如,定义了一个变量,但没有使用到它。 此外,ESLint 还能够帮你自动修复一些简单的问题。 我们将在下一小结学习如何使用 ESLint 检查我们的 JavaScript 代码,并修复其中的一些问题。 1.2. ESLint 快速上手 为了在项目中使用 ESLint,需要先安装它。 # 初始化一个 npm 项目 mkdir eslint-test cd eslint-test npm init -y # 安装 eslint npm init @eslint/config 回答一系列问题后,你可以看目录中的配置文件 .eslintrc.js,这个配置文件告诉 ESLint 如何去解析项目,这个项目采用了哪些规范和规则。 ...

August 7, 2025

编译优化-二进制化实现原理

本文结合 cocoapods-bin(社区最成熟的二进制化插件)拆解"Pod 二进制化"背后的工程机制:集成时如何透明替换 spec、打包时如何还原 Xcode 产物、调试时如何跳回源码。文末给出"自研一套二进制系统"的落地 checklist。 想先了解二进制化的整体背景、坑点和适用场景,可以先看 编译优化-二进制化;想了解 pod install 阶段本身的优化(HMap、并发下载等)见 编译优化-CocoaPods优化。 总体架构 一套完整的 Pod 二进制化系统由三层组成: flowchart TB subgraph CI[打包 CI] A1[源码 podspec] --> A2[壳工程 pod install] A2 --> A3[xcodebuild 双 SDK 编译] A3 --> A4[lipo / create-xcframework] A4 --> A5[上传 OSS] A5 --> A6[生成 binary podspec] A6 --> A7[push 到二进制仓] end subgraph Dev[开发机 pod install] B1[Podfile 声明 plugin] --> B2[Resolver/LazySpecification Hook] B2 --> B3{二进制仓是否有该版本?} B3 -- 是 --> B4[替换为 binary spec] B3 -- 否 --> B5[回退源码 spec] B4 --> B6[pod download zip] B5 --> B6 B6 --> B7[Xcode 链接] end subgraph Debug[调试] C1[dwarfdump 读取DW_AT_comp_dir] --> C2[下载源码] C2 --> C3[软链到 DWARF 路径] C3 --> C4[LLDB 自动跳转源码] end A7 --> B3 A3 -.DWARF 路径信息.-> C1 关键设计点: ...

May 8, 2026

版本控制

什么是版本控制 版本控制是一种系统性的方法,用于管理和跟踪软件开发项目中的代码、文件和资源。它的核心目标是在不同时间点的开发中保持代码的一致性、可追溯性和可维护性。 为什么要做版本控制 这个问题可以从定义中的关键词可以看出端倪:不同时间点下的代码一致性、可追溯性、可维护性。这里额外补充一点:当今的互联网开发早已步入敏捷迭代,各种动态化方案层出不穷都有着顺应时代潮流的意思,因此软件开发一定会越来越多地涉及到团队开发,团队之间的协作也一定需要这样一个机制去保障稳定性。 代码一致性 版本控制系统允许开发人员有效地管理和组织项目中的代码。它跟踪每个文件的历史记录,记录了每个版本的变更,包括谁做了什么修改、何时以及为什么。 可追溯性 版本控制系统允许为重要的里程碑或发布创建标签(tag),以便轻松识别和回滚到特定版本。 并且当某个版本的代码出现问题,版本控制系统允许开发人员轻松地回滚到之前的稳定版本,并在不破坏其余代码的情况下进行修复。 可维护性 开发人员可以创建分支(branch),这是一个独立的代码副本,用于开发新功能、修复错误或进行实验性工作。然后,这些分支可以与主代码库合并,以实现功能集成。 团队协作 在多人协作的环境中,不同开发者可能同时修改代码。版本控制系统确保不同的修改不会互相冲突,以及如何解决冲突。 版本控制系统促进了团队之间的协作,可以通过代码审查工具进行代码审查,以确保代码质量和一致性。 版本控制工具Git Git的安装 WIndows 访问 Git官网,选择对应的操作系统下载即可。 这里下载独立安装包即可 点install就行,会自动安装一个git GUI工具,但通常不会直接用它 下载完成安装即可,输入以下命令验证安装是否成功 git version 图形化工具 安装 这里推荐TortoiseGit这个工具,非常方便。但我觉得Git本身学习成本并不高,GUI工具更多是为了提升效率,基本的Git知识点还是需要掌握的。 下载本体 然后是语言包 安装时一路next即可,安装完本体后再安装语言包。 配置好Github的账户、邮箱、秘钥等,注意配置好了密钥也不会在下述部分显示,可以打开编辑全局进行查看 使用 在使用之前先提前补充一个点(后续会详细介绍),Git有本地仓库和远端仓库的概念,通常支持HTTPS和SSH两种协议进行关联,两种方式优缺点也十分明显: HTTPS:无需额外配置,但是在每次连接远端仓库时需要输入账号密码进行校验。 SSH:生成一组密钥对,需要本地进行配置,好处是配置好后每次连接远端仓库时会自动比较密钥对,无需额外校验。关于SSH详细可以参考这篇文章ssh-远程登录协议 为了一劳永逸,我们这里配置下SSH,首先打开Git bash # 输入下列命令,最后一个参数是您的邮箱 $ ssh-keygen -t rsa -C yourEmail.com 可以看到密钥对已经生成了 进入上述目录,注意.ssh文件夹默认是隐藏的,直接访问路径就行 以Github为例,配置公钥 创建一个Demo项目,平时学习的话就创建公有项目就行 复制一下我们的项目链接 选取一个你喜欢的目录作为本地工作区,然后按鼠标右键 ...

August 7, 2025

编译优化-头文件与HMap

对以 Objective-C 为主或混编的 iOS 大型工程,头文件查找是一个被严重低估的编译开销点。美团的统计显示,400+ Pod 组件的工程会产生近 5 万个头文件,导致海量的 IO 操作和编译参数膨胀。Header Map(HMap)技术能把头文件查找从 O(n) 的目录扫描退化为 O(1) 的哈希查表。 头文件查找的代价 Clang 的查找流程 当 Clang 遇到 #import <AFNetworking/AFNetworking.h> 时: flowchart TD A[遇到 #import] --> B{是否系统头?} B -- 是 --> C[SYSTEM_HEADER_SEARCH_PATHS] B -- 否 --> D[USER_HEADER_SEARCH_PATHS] C --> E[按顺序遍历 HEADER_SEARCH_PATHS] D --> E E --> F{路径下有吗?} F -- 否 --> G[下一个路径] G --> E F -- 是 --> H[stat + open] H --> I[解析头文件] 每一次查找都要对所有 HEADER_SEARCH_PATHS 执行 stat(2) 系统调用,当路径数量达到数千时,光 stat 就是显著开销。 ...

May 2, 2026

包管理

现如今,前端开发的同学已经离不开 npm 这个包管理工具,其优秀的包版本管理机制承载了整个繁荣发展的NodeJS社区,理解其内部机制非常有利于加深我们对模块开发的理解、各项前端工程化的配置以加快我们排查问题(相信不少同学收到过各种依赖问题的困扰)的速度。 本文从三个角度:package.json、版本管理、依赖安装结合具体实例对 npm 的包管理机制进行了详细分析。 一、剖析 package.json 在 Node.js 中,模块是一个库或框架,也是一个 Node.js 项目。Node.js 项目遵循模块化的架构,当我们创建了一个 Node.js 项目,意味着创建了一个模块,这个模块必须有一个描述文件,即 package.json。它是我们最常见的配置文件,但是它里面的配置你真的有详细了解过吗?配置一个合理的 package.json 文件直接决定着我们项目的质量,所以首先带大家分析下 package.json 的各项详细配置。 1.1 必备属性 package.json 中有非常多的属性,其中必须填写的只有两个:name 和 version ,这两个属性组成一个 npm 模块的唯一标识。 npm包命名规则 name 即模块名称,其命名时需要遵循官方的一些规范和建议: 包名会成为模块url、命令行中的一个参数或者一个文件夹名称,任何非url安全的字符在包名中都不能使用,可以使用 validate-npm-package-name 包来检测包名是否合法。 语义化包名,可以帮助开发者更快的找到需要的包,并且避免意外获取错误的包。 若包名称中存在一些符号,将符号去除后不得与现有包名重复 例如:由于react-native已经存在,react.native、reactnative都不可以再创建。 如果你的包名与现有的包名太相近导致你不能发布这个包,那么推荐将这个包发布到你的作用域下。 例如:用户名 conard,那么作用域为 @conard,发布的包可以是@conard/react。 查看包是否被占用 name 是一个包的唯一标识,不得和其他包名重复,我们可以执行 npm view packageName 查看包是否被占用,并可以查看它的一些基本信息: 若包名称从未被使用过,则会抛出 404 错误: 另外,你还可以去 https://www.npmjs.com/ 查询更多更详细的包信息。 1.2描述信息 基本描述 { "description": "An enterprise-class UI design language and React components implementation", "keywords": [ "ant", "component", "components", "design", "framework", "frontend", "react", "react-component", "ui" ] } description用于添加模块的的描述信息,方便别人了解你的模块。 ...

August 7, 2025

编译优化-编译缓存

“已经编译过的东西不再编译一遍”——这是编译优化的基础原理。Xcode 的增量编译、CocoaPods 的二进制缓存、Bazel 的 Action Cache 都是不同层次的编译缓存。本文聚焦 ccache、Clang/Swift module cache、远程缓存等通用方案的原理与 iOS 落地。 缓存分层 缓存按命中粒度可以分为三个层次: flowchart TD A[编译缓存] --> B[编译器内部缓存PCH/PCM/module cache] A --> C[Action 级缓存ccache/sccache] A --> D[产物级缓存framework/xcframework] B --> B1[进程内复用模块] C --> C1[按 .o 粒度缓存] D --> D1[按 Pod/module 缓存] 层次 粒度 代表 命中率 编译器内部 frontend 解析结果 Clang ModuleCache、Swift Module Cache 高(本地) Action 源文件 → 目标文件 ccache、sccache、Bazel 中(取决于参数稳定性) 产物 整个 Pod 或 module cocoapods-bin、Rugby 高(版本号稳定) Clang Module Cache 原理 Clang 的 @import / @_exported import 会把外部模块预编译成 .pcm,缓存到 ModuleCachePath: ...

May 8, 2026

持续集成/持续部署(CI/CD)

传统应用发布模式 开发人员:在开发环境完成代码编写,单元测试,测试通过后提交到代码仓库 运维人员:把项目部署到测试环境,供QA团队测试,测试通过后,部署生成环境 测试人员:进行测试,测试完成后通知运维部署生产环境 缺点 项目在早期就存在错误,但到最后集成的时候才发现 需要手动操作,易错率高 开发与运维需要及时沟通 有了以上缺点,那么就有了CI/CD CI/CD 持续集成(CI): 合并开发人员正在编写的所有代码 一天内进行多次合并和提交代码 从存储库或生产环境中进行构建和自动化测试,确保没有集成问题并及早发现任何问题 持续交付(CD): 可以通过将更改自动推送到发布系统来随时将软件发布到生产环境中 持续部署,并自动将更改推送到生产中 GitLab内置CI/CD 运行流水线任务 Job 在文件中可以定义一个或多个作业,每个作业具有唯一的名称,每个作业是独立执行的,每个作业至少包含一个script stages: 用于定义作业可以使用的阶段,并且是全局定义,同一个阶段的作业并行运行,不同阶段按顺序运行 only: 用分支策略来限制jobs构建 script: 项目中package中的脚本 environment: 定义此作业完成部署的环境名称 下面是每个jobs的详细变量名 Keyword Required Description script yes Runner执行的命令或脚本 image no 所使用的docker镜像,查阅使用docker镜像 services no 所使用的docker服务,查阅使用docker镜像 stage no 定义job stage(默认:test) type no stage的别名(已弃用) variables no 定义job级别的变量 only no 定义一列git分支,并为其创建job except no 定义一列git分支,不创建job tags no 定义一列tags,用来指定选择哪个Runner(同时Runner也要设置tags) allow_failure no 允许job失败。失败的job不影响commit状态 when no 定义何时开始job。可以是on_success,on_failure,always或者manual dependencies no 定义job依赖关系,这样他们就可以互相传递artifacts cache no 定义应在后续运行之间缓存的文件列表 before_script no 重写一组在作业前执行的命令 after_script no 重写一组在作业后执行的命令 environment no 定义此作业完成部署的环境名称 coverage no 定义给定作业的代码覆盖率设置 配置.gitlab-ci.yml stages: - deploy_dev - deploy_test - deploy_production deploy_dev: stage: deploy_dev environment: name: dev only: - dev script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:dev - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=/opt/devroot/" deploy_test: stage: deploy_test environment: name: test only: - master script: - rsync -av . ${WORKSPACE_PATH} && cd ${WORKSPACE_PATH} - npm run build:stage - ansible-playbook ansible-deploy.yml --extra-vars "hosts=cloud_ui_test projectDir=${WORKSPACE_DIST_PATH} projectName=${PROJECT_NAME} webRootPath=${WEB_ROOT_PATH}" deploy_production: stage: deploy_production only: - production when: manual script: - npm install - npm run build:prod - ansible-playbook ansible-deploy.yml --extra-vars "hosts=dvs-front-prod projectDir=${PROJECT_PATH} projectName=xianglin-cloud-ui/${PROJECT_NAME} webRootPath=/opt/" 上面这个.yml文件,我们首先定义了三个阶段,deploy_dev部署到dev环境,并且拉去的是dev分支代码; deploy_test部署到test环境,拉去的是master分支代码,deploy_production拉取production分支代码,执行完后需要手动操作 ...

August 7, 2025

编译优化-观测

“无法度量就无法优化”。在开始任何编译优化动作之前,必须先建立一套可重复、可对比的观测手段,否则改动的真实收益无从谈起。本文介绍 iOS 编译耗时观测的主要工具链和原理。 观测目标分层 不同层次的观测工具回答不同的问题: flowchart TD A[编译耗时观测] --> B[整体耗时] A --> C[阶段耗时] A --> D[任务级耗时] A --> E[函数/表达式级] B --> B1[xcodebuild 总耗时] C --> C1[Build Timing Summary] C --> C2[Pod Install 阶段计时] D --> D1[Build Timeline] D --> D2[XCLogParser] E --> E1[-debug-time-compilation] E --> E2[-warn-long-expression] 观测层次 典型问题 工具 整体 一次构建花了多久? time xcodebuild、MetricKit 阶段 哪个阶段最慢? -showBuildTimingSummary、Build Timing Summary 任务 哪个文件/目标最慢? Xcode Build Timeline、XCLogParser 函数级 哪个函数/表达式让前端卡住? -debug-time-function-bodies、-warn-long-expression-type-checking 整体耗时 xcodebuild 命令行计时 最简单也最稳定的方式是直接给 xcodebuild 加 time: ...

May 5, 2026