Skip to content

第 4 篇:通读源码,规划移植路线

本系列第 4 篇。面对 28 个陌生的 Java 文件、近万行代码,一个新手怎么不慌、理出「先翻什么后翻什么」的顺序?这篇是方法论 + 实战规划。

一、面对陌生代码库,先别急着写

新手最容易犯的错:一打开代码就从第一个文件开始逐行翻译。结果翻到一半发现「这个类依赖那个还没翻的类」,卡死。

正确姿势是先俯瞰全局,理清依赖关系,再决定顺序。核心原则只有一句:

被别人依赖的,先翻;依赖别人的,后翻。

这样翻译任何一个文件时,它需要的东西都已经准备好了。

二、dd-plist 的整体架构

通读后,我发现这个库逻辑上分成两大块

① 数据模型层(NSObject 家族)—— 表示 plist 里的各种数据类型
② 读写层(Parser / Writer)  —— 把三种格式的文件 ⇄ 数据模型 互转

三种格式(XML、Binary、ASCII)各有一对 Parser + Writer。再加上一个统一入口 PropertyListParser 负责自动识别格式并分发。

三、依赖分层图(这就是翻译顺序)

我把 28 个文件按「谁依赖谁」分成了 5 层,从上往下翻,永远只依赖已经翻好的东西:

第 0 层|基础 / 无依赖(最先翻,最简单)
  ├─ PropertyListFormatException   自定义异常
  ├─ Base64                         Base64 编解码(★可用仓颉标准库替代,省 2000 行!)
  ├─ ByteOrderMarkReader            BOM 字节序识别
  ├─ ByteOrderMarkFilterInputStream 输入流 BOM 过滤
  └─ LocationInformation(抽象)+3个子类  报错位置信息(可最后做甚至先跳过)

第 1 层|数据模型核心基类
  └─ NSObject(抽象基类,722 行)   ★所有数据类型的父类

第 2 层|具体数据类型(都继承 NSObject)
  ├─ NSNumber / NSString / NSData / NSDate / NSNull
  ├─ NSArray / NSSet / NSDictionary
  └─ UID

第 3 层|辅助
  └─ ParsedObjectStack   解析时的对象栈(防循环 / 深度限制)

第 4 层|读写器(依赖上面所有数据类型)
  ├─ ASCII:  ASCIIPropertyListParser(1089) + ASCIIPropertyListWriter
  ├─ Binary: BinaryPropertyListParser(641) + BinaryPropertyListWriter(369)
  └─ XML:    XMLPropertyListParser(544)    + XMLPropertyListWriter + XMLLocationFilter

第 5 层|统一入口(依赖所有读写器)
  ├─ PropertyListParser     自动识别格式并分发
  └─ PropertyListConverter  格式互转

四、推荐的翻译顺序 + 难度评估

顺序模块行数难度说明
1PropertyListFormatException87🟢 极易先建异常体系,练手
2NSObject(骨架)722🟡 中先翻抽象方法+简单部分,反射相关最后处理(不能砍,见下)
3NSNumber / NSString / NSDate / NSNull~1300🟡 中核心数据类型,注意字符串编码
4Base642124🟢 易不用翻!用仓颉标准库,直接省 2000 行
5NSData219🟢 易依赖 Base64
6NSArray / NSSet / NSDictionary~1200🟡 中容器类型,对应仓颉 Array/HashMap/HashSet
7UID + ParsedObjectStack~260🟢 易小工具
8ASCII Parser + Writer~1256🟠 较难手写状态机解析器,纯算法好翻
9Binary Parser + Writer~1010🟠 较难字节操作多,注意大小端 / 无符号
10XML Parser + Writer~630🔴 最难⚠️ 最大难点(见下)
11ByteOrderMark 两个类~196🟢 易流处理
12PropertyListParser + Converter~620🟡 中最后拼装统一入口
LocationInformation 家族~260🟢 易报错行列信息,可最后做/先简化

五、三个提前预警的难点

1. XML 解析(最难)🔴

Java 版用了 javax.xml(系统自带的 DOM / SAX 库)来解析 XML。仓颉标准库大概率没有等价的 XML DOM 库。 两个选择:

  • 找仓颉生态里现成的 XML 库(如果有);
  • 自己写一个小型 XML 解析器——plist 的 XML 结构其实很简单,标签就那么几个(<dict> <array> <string> <integer> 等),手写并不难,而且可控性强。我倾向后者。

2. 反射(NSObject 里的对象 ⇄ plist 互转)🟡

Java 版用反射把「任意 Java 对象」和 plist 互转(fromJavaObject / toJavaObject)。仓颉的反射机制不一样。

⚠️ 重要修正:共建规则要求「与上游对外接口、功能定义全量一致」,所以这块不能直接砍掉。策略是——放到最后处理,能用仓颉原生反射实现就实现;实在实现不了的,推荐用 FFI(调用现成的 C 等实现)兜底,保证功能对齐,等仓颉后续能力完善再无痛替换。

开发节奏上仍然「最后做」,但目标是「做完」,不是「不做」。

3. 无符号数 / 字节序 🟠

Java 没有无符号类型,二进制解析里全是 & 0xFF 这种操作。仓颉有 UInt8 / UInt32 / UInt64,反而更清晰,但翻译时要一一对应准确。

六、我的落地路线

第一版先打通核心闭环,反射和位置信息放到最后补齐(但不省略,见上):

异常 → NSObject 骨架 → NSNumber/NSString/NSData/NSDate/NSNull
→ NSArray/NSSet/NSDictionary → ASCII 读写 → Binary 读写 → XML 读写
→ 统一入口 → 用 test-files/ 里的真实 plist 验证
→ 补齐反射(toJavaObject/fromJavaObject) + 位置信息 → 全量对齐上游

一个好消息:原库的 test-files/ 里有几十个真实的 .plist 测试文件(XML / 二进制 / ASCII 都有),src/test/ 里也有完整的 Java 测试用例。我们可以直接拿它们来验证翻译对不对——这是检验正确性最好的标尺。

七、这一步的收获(方法论)

这一篇其实教了一个通用技能:怎么啃一个陌生代码库。

  1. 先分层,别硬啃:找出「数据模型」和「功能逻辑」,理清依赖方向。
  2. 从叶子往根翻:先翻没有依赖的基础件,再翻依赖它们的。
  3. 识别难点,提前决策:哪些能用标准库替代(Base64)、哪些放到最后补齐(反射,用原生或 FFI)、哪些必须硬啃(XML)。
  4. 抓住现成的测试:原库的测试文件就是你的「标准答案」。

下一篇(待更新):用 cjpm 搭建 plist4cj 仓颉工程,并翻译第一个模块,写出第一段能跑的仓颉代码。