2 应用开发篇(下)

《带你领略iOS知识体系的全貌》,今天继续应用开发篇(下)。

在这一篇,我们会讲到 iOS 开发中的 JSON 解析、布局框架、富文本、TDD/BDD 和编码规范

26 | JSON 解析

背景:不同编程语言之间进行数据通信,通信数据规范该如何确定?所以需要一种通用,而且各个编程语言都支持的数据格式。


接下来就轮到 JSON 出场了。

Source: codebrainer

JSON 的全称是 JavaScript Object Notation,可见最初是被设计为 JavaScript 语言的一个子集,但最终因为和编程语言无关,所以 JSON 就成为了一种开放标准的常见数据格式。

目前很多编程语言都支持了 JSON 的生成和解析,所以 JSON 这样的数据格式就满足了背景里提到的需求。


JSON 基于两种结构

  • 键值对集合:具体实现有字典、Hash 表、对象、结构体等,示例:{"key1": "val1","key2": "val2"}

  • 有序值列表:具体实现有数组、向量、列表等,示例:[1,3,5]


JSON 的其它特点

  • 支持嵌套:上述值可以是字符串、数字、对象、数组、布尔值、空值中的任一种;

  • 不支持注释;

  • 水平制表符、换行符、回车符都会被当做空格。


大多数语言的逻辑可以转换成语法树结构,而语法树能够用 JSON 来描述。所以 JSON 的使用场景有很多:

  1. 描述业务数据,使得业务数据能够动态更新;

  2. 描述业务逻辑,以实现业务逻辑的动态化;

  3. 描述页面布局。


如何解析

苹果原生提供了 NSJSONSerialization (OC) / JSONSerialization (Swift) 类来解析 JSON,相关的第三方框架有 JSONModel、Mantle、MJExtension、YYModel,它们都是基于原生类的封装。

如果追求更高的解析性能,可以了解一下 simdjson(2019 年 2 月发布),号称每秒可解析千兆字节 JSON 文件。相比按字节自顶向下递归解析的传统 JSON 解析方式,其优化思路在于解析并行化,参考:

27 | 跟自动布局比,Flexbox 好在哪?

Flexbox 的“地位”:是知名布局库 React Native、Weex 和 Texture(AsyncDisplayKit)采用的布局思路,同时也是苹果官方类 UIStackView 采用的布局思路。

React Native 和 Weex 对 Flexbox 算法的实现,是一个叫作 Yoga 的 C++ 库。


如下图,Flexbox 算法的主要思想是,让 flex container(容器)能够改变其 flex item(项目)的宽高和顺序,如通过扩大 / 缩小项目来适配(填充 / 防止超出)可用空间。

Source: W3C

关于 Flexbox 更详细的讲解,可以参考:


那跟自动布局相比,Flexbox 好在哪呢

  1. 提供的布局方法更方便、更全面、更规范;

  2. 响应式,跨平台性好:目前所有浏览器都已支持,同时在 iOS 和 Android 中也支持。

所谓响应式,是指不直接操作目标,而是通过代理达到操作的目的。

28 | 怎么应对各种富文本表现需求?

富文本是什么?它是一段有属性的字符串。

  • 可以包含不同字体、不同字号、不同背景、不同颜色、不同字间距的文字;

  • 还可以设置段落、图文混排等等属性。


然后,如何展示富文本呢?这里分2种情况:

1)用 HTML 来描述的富文本

这种方式描述的富文本,可以直接使用 WKWebView 控件的 loadHTMLString:baseURL: 方法来展示。

此外,对于 HTML 里的图片资源,因为需要通过网络请求来获取,所以可以考虑缓存策略减少请求次数。

2)使用原生 iOS 代码描述的富文本

长列表场景 (一次性返回多条数据交由前端渲染)一般对性能的要求更高,所以会使用这种方式描述的富文本。

这种方式描述的富文本,可使用苹果官方的 TextKit 或者第三方的 YYText 来展示。

其中,YYText 有很多亮点

  1. 在异步文字布局和渲染上的性能非常好;

  2. 兼容 UILabel 和 UITextView;

  3. 自定义的 NSMutableAttributedString 分类,不光简化了基类,还增加了嵌入 UIView、CALayer 等功能。


小结

  • HTML 描述富文本更易读、更容易维护;

  • 原生代码描述富文本的性能更高。

所以如果想结合两者的优势,可以使用 HTML 描述富文本,然后在展示前先将 HTML 转成原生代码(可参考作者的 HTN 项目,实现了 HTML 代码转原生代码的能力)。

富文本 → HTML → 原生代码 → 展示

29 | 如何进行 TDD 和 BDD?

背景:编写影响范围比较大的代码时,需要检验的地方就非常多,相应地,人工检查的时间成本也会非常高。

那么,如何提高编写代码后的检验效率呢?

答案是开发、测试同步进行,尽早发现问题。


测试范围上来划分的话,测试可以分为:

  • 单元测试(开发者负责)

  • 集成测试(测试团队负责)

  • 系统测试(测试团队负责)

其中,单元测试也叫模块测试,这个单元可能是一个类的方法,也可能是一个模块的某个函数;同时,开发者要注意保证每个单元的职责清晰。


开发模式划分的话,开发方式可以分为:

  • TDD(Test-driven development,测试驱动开发)

  • BDD(Behavior-driven development,行为驱动开发)

Source: testlodge

TDD 的开发思路是:

  1. 先编写测试用例;

  2. 在不考虑代码优化的情况下快速编写功能实现代码;

  3. 等功能开发完成后,在测试用例的保障下,进行代码重构,以提高代码质量。

它的测试用例主要针对开发中的最小单元,适合单元测试。

在思想上,TDD 和拿到功能需求后直接开发功能的区别是:

  • 先考虑如何对功能进行测试,再考虑如何编写代码,这给优化代码提供了更多的时间和空间;

  • 即使几个版本过后再来优化,只要能够通过先前写好的测试用例,就能够保证代码质量。

PS:有点“不忘初心”的那味道~


BDD 是 TDD 的进化,它:

  • 基于行为进行功能测试,使用 DSL(Domain Specific Language,领域特定语言)来描述测试用例;

  • 测试用例看起来和文档一样,更易读、更好维护。

它的测试用例是对行为的描述,测试范围更大一些,适合集成测试和系统测试。

同时,得益于 BDD 使用的 DSL 语言(规范、标准、可读性高),不仅开发者可以使用 BDD 高效地发现问题,也方便测试团队参与编写。


BDD 的 OC 框架有 Kiwi、Specta、Expecta 等,Swift 框架有 Quick。

作者推荐 Kiwi 这个框架,因为它包含 Specta 的 DSL 模式,Expecta 框架的期望语法,以及 MocksStubs 能力,具体使用可以参考 Kiwi Wiki(Specs | Expectations | Mocks and Stubs | Asynchronous Testing)。

Mocks:模拟对象,如模拟 Null 对象、模拟类的实例、模拟协议的实例。

Stubs:存根,可以让选择器或消息模式返回固定的结果,支持真实对象和模拟对象。


小结

  • 无论是 TDD 还是 BDD,都是先写测试用例(需要考虑到各种异常条件以及输入输出的边界),再开发代码;

  • 好的模块化架构和 TDD 、BDD 是互相促进的。

作者建议:优先对基础能力的功能开发使用 TDD 和 BDD,保证了基础能力的稳定后,在时间允许的情况下,再考虑核心业务

30 | 如何制定一套合适的编码规范?

背景:一个团队有了统一的编码规范,才能更有效地避免团队成员相互认同感缺失的问题(代码风格不一致而导致的)。


那什么是好的代码规范,需要考虑哪些方面呢?

Source: quora

首先,可以参看一些优秀公司的代码规范


接下来,是一份简单的参考

  1. 常量:使用类型常量,而不是宏定义。

  2. 变量:明确体现出功能,最好加上类型做后缀;少用全局变量传递值,而是通过参数传值(减少功能模块间的耦合)。

  3. 属性:OC 里,尽量通过 get 方法来进行懒加载(避免无用的内存占用和多余的计算);Swift 里,如果属性是只读的,可以省掉其 get 子句。

  4. 条件语句:减少或不使用默认处理,特别是使用 Switch 处理枚举时(使用 Swift 编写 Switch 语句时,如果不加 default 分支的话,当枚举有新增值时,编译器会提醒你增加分支处理);减少嵌套处理(增加可读性),Swift 中可以充分利用 guard 语法。

  5. 循环语句:减少 continue 和 break 的使用(增加可读性),Swift 中可以统一使用 guard 来代替。

  6. 函数:函数名体现目的;每个函数处理最小单位的逻辑,满足单一职责原则;函数内尽量避免使用全局变量来传递数据(减少耦合,提高单元测试的准确性);注意检查函数的入参(提高健壮性),Swift 里的 guard 语法同样适用于检查入参。

  7. :在 OC 中,类的头文件尽可能少地引入其他类的头文件,而是通过 @class 来声明,然后在实现文件里再通过 #import 引入需要的头文件(使用 @class 保证代码编译通过,使用 #improt 保证代码运行通过,参考 OC 中 @class 和 #import 的区别——CSDN);对于继承和遵循协议的情况,无法避免引入其他类里的头文件,所以在代码设计时尽量减少继承(继承关系太多不利于代码的维护和修改,比如修改父类时还需要考虑对所有子类的影响)。

  8. 分类: 分类里增加的方法名尽量加上前缀,如果是系统自带类的分类的话,就一定要加上前缀(避免产生方法名重复的问题);把一个类里的公有方法分类到不同的分类里,便于管理维护(特别适合多人维护各自不同功能代码的场景)。

💡:

  • 代码逻辑清晰是高质量代码最基本、最必要的条件。如果代码不清晰的话,那么其他的扩展、重用、简洁优雅都免谈。

  • 写代码的首要任务是能让其他人看得懂,避免过度工程化(针对特定业务的工程设计)。

  • 减少使用过新的语言特性和黑魔法,如果需要使用,则多补充注释。


最后,如何将代码规范落地执行呢?

最好的方式就是 Code Review(代码审核)。通过 Code Review ,你可以:

  • 检查代码规范是否被团队成员执行;

  • 同时及时指导代码编写不规范的同学。

Code Review 可以分为两步走

  1. 先使用静态检查工具SwiftLintOCLint),对提交的代码进行一次全面检查。

  2. 然后,再进行人工检查(审核人可以点赞、评论),除了能达到将代码规范落地的效果外,还可以促进成员之间的沟通交流和相互学习。


好啦,应用开发篇马上就要告一段落(还差一篇介绍 iOS 开发学习资料的加餐),下次就要开始原理篇的内容了,涉及 iOS 系统内核 XNU、iOS 黑魔法背后的原理……(🤫留点想象空间)。

Bo2SS 会竭尽全力把它们讲明白,还请大家多多支持~咱们下次见!