- 移动开发
- UI组件
【免费下载链接】Texture
Smooth asynchronous user interfaces for iOS apps.
导读
本文基于 Texture 开源仓库的 CONTRIBUTING.md 撰写,系统梳理了这个 iOS 异步 UI 框架对外开源协作的完整规范:包括问题(Issue)的提问与上报流程、功能提案的 RFC 流程、Pull Request 的提交流程、语义化版本管理策略,以及一套可落地的 Objective-C 编码规范(缩进、命名、括号、锁与可空性标注)。无论你是想上报一个 bug、提出一个新功能,还是直接贡献代码,读完本文你都能按照项目官方流程高效参与,并让自己的代码风格与 Texture 核心团队保持一致。
提问与求助渠道:先到 Slack,别用 Issue
Texture 在文档开篇就明确了求助渠道的分工:
- 如果你在使用 Texture 时遇到困难或对用法有疑问,请到官方Slack 频道提问;
- 请不要通过提交 GitHub Issue 来寻求使用帮助,Issue 只用于 bug 跟踪与功能讨论。
同时,仓库中的 .github/ISSUE_TEMPLATE.md 也呼应了这一点:模板顶部同样提示"如果你在寻找帮助,请考虑加入我们的 slack 频道",并建议上报者尽可能包含:示例工程或截图、代码片段、Texture 版本号、崩溃时的 backtrace(> bt all)。也就是说,一个高质量的 Issue 应当自带可复现的最小工程和足够的诊断信息,而不是一句含糊的"它不工作了"。
Core Team:谁来审核与合并
Texture 有一个Core Team(核心团队),其职责包括:
- 评审并推动社区提交的 RFC Issue,作为这些 RFC 的批准人(approver);
- 推动 Texture 朝着一致的方向演进,目标是构建"通用的 iOS UI 框架";
- 团队成员拥有仓库的合并(merge)权限。
核心团队成员基于技术专长与对社区的贡献被任命。文档中列出的成员包括 Adlai Holler、Garrett Moon、Huy Nguyen、Michael Schneider、Scott Goodson 等,并说明随着时间推移,更多来自多元化背景的社区成员将根据其社区参与和贡献记录被任命。
Issues:如何上报 Bug 与新功能
已知问题在哪里
Texture 使用 GitHub Issues 进行所有 bug 跟踪,团队会密切跟踪并尽量明确标注"内部修复进行中"的状态。因此在提交新 Issue 之前,先确认你的问题是否已经存在。
上报新 Issue 的标准步骤
- 先更新到最新 master 版本再复现,团队可能已经修复了你的 bug;
- 搜索相似 Issue,很可能别人已经遇到过同一个问题;
- 提供一个精简的复现用例(reduced test case):这个 demo 除了你要演示的 bug 之外应当完全可运行,越精简越好。如果无法产出复现用例,请给出非常具体的复现步骤。如果团队无法复现、且没有其他证据,Issue 将被关闭;
- 你的 Issue 会被验证:提供的示例会被测试正确性,Texture 团队会与你协作直到问题得到验证;
- 跟进团队的反馈,Issue 如果长期无人跟进(stale)可能被关闭;
- 如果可能,提交一个带失败测试的 Pull Request;更进一步,如果能力允许,直接尝试修复 bug。
一句话总结:提供的信息越多,团队验证 bug 就越快,采取行动也就越快。
Issue 的 Triaging(分流)规则
- 如果团队需要你补充复现信息,Issue 会被标记为Needs More Info;发出通知后14 天内未获回应会再次提醒,首次提醒后两周仍无回应则可能关闭;
- 把功能请求直接提交为 Issue 的用户,会被引导遵循 RFC 流程,否则 Issue 将被关闭;
- 长期不活跃的 Issue 会被贴上相应标签以告知提交者"该关闭了",之后如恢复可操作性可重新打开;
- 可行时,Issue 会按Status: X(如 In Progress、On Hold)或Priority: X(P1/P2/P3,从高到低)前缀打标签,便于状态与优先级管理。
请求新功能:RFC Issue 流程
如果打算改变公开 API或对实现做非平凡改动,官方强烈建议先走 RFC Issue 流程,在投入大量精力实现之前先达成一致。如果只是修 bug,可以直接提交 Pull Request,但仍建议先提一个描述修复内容的 Issue(以防团队不接受该修复但想跟踪该问题)。
RFC Issue 流程如下:
- 在 Slack 频道收集反馈,或直接以 GitHub Issue 形式起草 RFC;
- Issue 标题必须以
[RFC]为前缀,例如:[RFC] Add new cool feature; - 清晰、详细地说明你想要的功能及其重要性。注意项目优先采纳对大多数用户有用的功能;如果只面向少数用户,请考虑以 add-on 库的形式扩展 Texture;
- 复杂功能请务必写 RFC Issue——即使你自己无法实现,RFC 也能为贡献者提供核心团队认可的实现规范文档。被接受(accepted)的 RFC 会标记Needs Volunteer;
- 讨论之后可以尝试提交 Pull Request。能写代码就尽快开始写,团队"永远有做不完的事",动手写代码能显著加速整个流程。
开发流程与 Pull Request
所有 Texture 开发都直接在 GitHub 上进行,核心团队成员与外部贡献者提交的 Pull Request 走同一套评审流程。
master 分支处于活跃开发状态
- 团队会尽力保持 master 分支良好状态、测试始终通过;
- 但为了快速迭代,master 上可能存在与你应用不兼容的API 变更;
- 团队会尽力沟通这些变更并合理管理版本号,方便你锁定特定版本。
提交 Pull Request 的完整检查清单
PR 应提交到 master 分支。稳定版本分支单独维护,但不接受直接对稳定分支的 PR;团队会从 master 把非破坏性变更 cherry-pick 到最新稳定大版本。
提交 PR 前请确认以下事项:
在 GitHub 上搜索已打开或已关闭的PR,避免重复劳动;
Fork 仓库并从 master 创建分支:
git checkout -b my-fix-branch master编写补丁,包含合适的测试用例,并遵循本文的编码规范;
确保每个提交信息(commit message)有意义,便于评审者理解意图;
确保你的 PR 通过GitHub 上的 CI 测试;
若尚未签署,先签署 CLA(见下文)。
新文件的版权头
在新建文件顶部粘贴以下版权声明:
// // ASDisplayNode.mm // Texture // // Copyright (c) Pinterest, Inc. All rights reserved. // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 //如果是修改已有文件,将文件头改为:
// // ASDisplayNode.mm // Texture // // Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. // Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 //这反映了 Texture 从 Facebook 旗下的 AsyncDisplayKit 迁移至 Pinterest 旗下 Texture 的历史:2017 年 4 月 13 日之后版权归属 Pinterest。
CI 如何把关
仓库的 .github/workflows/ci.yml 展示了 PR 与 push 触发的 CI 矩阵,覆盖 8 种模式:tests(构建并运行测试)、framework(构建动态 framework)、life-without-cocoapods(构建静态库)、carthage(验证 Carthage 可用)、以及examples-pt1到examples-pt4(分四批构建示例工程)。CI 运行在 macOS 上,先 checkout 再执行./build.sh <mode>。此外 CI/build.sh 中的入口脚本会执行./build.sh all。这意味着贡献者的 PR 会同时被测试、框架构建和示例工程构建三重验证。
语义化版本(Semantic Versioning)
Texture 遵循语义化版本控制:
- patch 版本:bug 修复;
- minor 版本:新功能(极少数情况下包含清晰且易修复的破坏性变更);
- major 版本:任何大的破坏性变更。
关键约定:在引入破坏性变更时,先在一个 minor 版本中给出 deprecation 警告,让用户提前了解变更并迁移代码。
每个 PR 都会被贴上标签,标明该变更应进入下一个 patch、minor 还是 major 版本。项目发布节奏约为每几周一次:不含重大新功能或破坏性 API 变更时发 patch/minor,反之发 major。
编码规范(Coding Guidelines)
这是文档中技术含量最高、最可落地执行的部分,逐条列举如下,并尽量与仓库源码相互印证。
缩进、行宽与空白
- 使用 2 空格缩进,绝不用 tab(在 Xcode 中设置该偏好);
- 行宽尽量控制在120 字符左右;
- 文件末尾以换行符结束;
- 不留尾部空白(trailing whitespace)。
@property 声明
@property声明后加一个空格,且属性修饰符需符合固定顺序:
@property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking; @property (nonatomic, readwrite, strong, nullable) NSAttributedString *attributedText;注意文档后面关于 nullability 的补充约定:声明属性时不要包含atomic、readwrite、strong、assign(这些是默认值或冗余信息),只标注NS_ASSUME_NONNULL未覆盖的可空性,顺序为 (nullability, atomicity, storage class, writability, custom getter, custom setter)。
方法签名
- 方法类型符号(
-/+)之后加一个空格; - 方法分段之间加空格(与 Apple 风格一致);
- 每个参数前必须有关键字,且关键字要能描述该参数:
@interface SomeClass - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; - (void)setExampleText:(NSString *)text image:(UIImage *)image; - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; - (id)viewWithTag:(NSInteger)tag; @end内部方法与括号风格
- 内部方法必须以
_前缀:
- (void)_internalMethodWithParameter:(id)param;- 方法与控制流的花括号:
if/else/switch/while等的左花括号与语句同行、右花括号独立成行:
if (foo == bar) { //.. } else { //.. }- 方法、
@interface、@implementation的花括号放在下一行:
@implementation SomeClass - (void)someMethod { // Implementation } @end- 函数的花括号与函数同行:
static void someFunction() { // Implementation }- 运算符与变量名同侧:
NSAttributedString *attributedText = self.textNode.attributedText;锁定(Locking)约定
Texture 是异步 UI 框架,线程安全是核心问题,因此锁定约定格外细致:
_locked_前缀约定:需要在持有锁时调用的方法,方法名前面加_locked_:
- (void)_locked_needsToBeCalledWithLock {}这一约定在仓库源码中大量真实存在,例如 Source/ASDisplayNode+Layout.mm 中有 15 处、Source/ASDisplayNode.mm 中有 45 处、Source/ASNetworkImageNode.mm 中有 28 处_locked_前缀方法,是团队实际执行的标准。
锁定安全规则:
_locked_方法调用其他_locked_方法是允许的;- 但禁止以下行为:
- 在
_locked_方法内调用普通的、未加锁的方法; - 在
_locked_方法内调用面向开发者、供子类覆写(override)的钩子方法;
- 在
- 面向用户覆写的子类钩子不得在持锁状态下调用;内部使用的钩子同样遵循上述约定。
获取锁的两种方式:
方式一:显式调用.lock()/.unlock():
- (void)setContentSpacing:(CGFloat)contentSpacing { __instanceLock__.lock(); BOOL needsUpdate = (contentSpacing != _contentSpacing); if (needsUpdate) { _contentSpacing = contentSpacing; } __instanceLock__.unlock(); if (needsUpdate) { [self setNeedsLayout]; } } - (CGFloat)contentSpacing { CGFloat contentSpacing = 0.0; __instanceLock__.lock(); contentSpacing = _contentSpacing; __instanceLock__.unlock(); return contentSpacing; }方式二:创建AS::MutexLocker(基于 RAII,作用域结束时自动解锁,可避免忘记 unlock):
- (void)setContentSpacing:(CGFloat)contentSpacing { { AS::MutexLocker l(__instanceLock__); if (contentSpacing == _contentSpacing) { return; } _contentSpacing = contentSpacing; } [self setNeedsLayout]; } - (CGFloat)contentSpacing { AS::MutexLocker l(__instanceLock__); return _contentSpacing; }从源码看,AS::MutexLocker定义在 Source/Details/ASThread.h,本质是typedef std::lock_guard<Mutex> MutexLocker;,即 C++ 标准库的std::lock_guard封装,遵循 RAII 语义。而 Source/ASLocking.h 进一步提供了ASLocking协议(在NSLocking基础上扩展-tryLock)以及ASLockSequence宏——它通过"反复尝试、失败则全部解锁并让出线程(sched_yield)"的方式同时获取多把锁以避免锁顺序导致的死锁,这也是_locked_体系之外的进阶锁定设施,可用于需要一次性持有多把锁的场景。
Nullability 标注
- 所有头文件采用
NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END包裹,然后只对可以为空的指针标注 nullability; - 除接口声明之外,基本没有使用 nullability 注解的意义。
完整示例(属性、方法、函数、typedef、block 参数等各场景):
// Properties // Never include: `atomic`, `readwrite`, `strong`, `assign`. // Only specify nullability if it isn't assumed from NS_ASSUME. // (nullability, atomicity, storage class, writability, custom getter, custom setter) @property (nullable, copy) NSNumber *status // Methods - (nullable NSNumber *)doSomethingWithString:(nullable NSString *)str; // Functions NSString * _Nullable ASStringWithQuotesIfMultiword(NSString * _Nullable string); // Typedefs typedef void (^RemoteCallback)(id _Nullable result, NSError * _Nullable error); // Block as parameter - (void)reloadDataWithCompletion:(void (^ _Nullable)())completion; // Block as parameter with parameter and return value - (void)convertObject:(id _Nonnull (^ _Nullable)(id _Nullable input))handler; // More complex pointer types - (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map;最后一个示例来自真实的 range controller 相关接口,其中ASScrollDirection、ASLayoutRangeMode、ASCollectionElement、ASElementMap等类型都定义在仓库 Source/Details 与 Source 目录下,可见这套 nullability 约定已贯穿 Texture 的公开 API。
CLA 与 License
- 签署贡献者许可协议(CLA):在发送 Pull Request 之前必须签署 CLA,任何代码变更要被接受,CLA 都必须已签署;
- License:通过向 Texture 贡献,你同意你的贡献将在其Apache 2.0许可下授权。
这一点同样体现在每个源文件的版权头中——例如 Source/ASLocking.h 头部即注明Copyright (c) Pinterest, Inc. All rights reserved.与Licensed under Apache 2.0,与贡献指南中的文件头模板完全一致。
结语
Texture 的贡献流程是一个"先沟通、再实现"的完整闭环:用法问题走 Slack,bug 与功能走 Issue + RFC,代码走 master 分支 PR + CI 验证 + CLA 签署;而编码规范中关于 2 空格缩进、_locked_前缀、AS::MutexLocker/ASLockSequence锁定约定、nullability 标注等细则,都可以在仓库源码中找到大量真实落地的实例。按照这套规范提交代码,不仅能显著提高 PR 被接受的概率,也能让你的实现与 Texture 核心团队保持一致的工程质量标准。
- 移动开发
- UI组件
【免费下载链接】Texture
Smooth asynchronous user interfaces for iOS apps.
相关推荐
pypdf 贡献指南:从提交 Issue 到合并 Pull Request 的完整协作规范
pypdf 贡献指南:从提交 Issue 到合并 Pull Request 的完整协作规范 pypdf 是一个纯 Python 实现的 PDF 处理库,可对 P
后端Exposed 贡献指南:从 Issue 提交到 Pull Request 合入的完整协作规范
Exposed 贡献指南:从 Issue 提交到 Pull Request 合入的完整协作规范 本指南以 Exposed(Kotlin SQL Framewor
ORM后端数据存储realm-swift 贡献指南:从 Issue 提交到 Pull Request 合入的完整协作规范
realm swift 贡献指南:从 Issue 提交到 Pull Request 合入的完整协作规范 导读 本文基于 realm swift 仓库根目录的 C
数据库移动开发嵌入式数据库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考