《带你领略iOS知识体系的全貌》,今天继续应用开发篇(下)。
在这一篇,我们会讲到 iOS 开发中的 JSON 解析、布局框架、富文本、TDD/BDD 和编码规范。
26 | JSON 解析
背景:不同编程语言之间进行数据通信,通信数据规范该如何确定?所以需要一种通用,而且各个编程语言都支持的数据格式。
接下来就轮到 JSON 出场了。
JSON 的全称是 JavaScript Object Notation,可见最初是被设计为 JavaScript 语言的一个子集,但最终因为和编程语言无关,所以 JSON 就成为了一种开放标准的常见数据格式。
目前很多编程语言都支持了 JSON 的生成和解析,所以 JSON 这样的数据格式就满足了背景里提到的需求。
JSON 基于两种结构:
-
键值对集合:具体实现有字典、Hash 表、对象、结构体等,示例:
{"key1": "val1","key2": "val2"}
。 -
有序值列表:具体实现有数组、向量、列表等,示例:
[1,3,5]
。
JSON 的其它特点:
-
支持嵌套:上述值可以是字符串、数字、对象、数组、布尔值、空值中的任一种;
-
不支持注释;
-
水平制表符、换行符、回车符都会被当做空格。
大多数语言的逻辑可以转换成语法树结构,而语法树能够用 JSON 来描述。所以 JSON 的使用场景有很多:
-
描述业务数据,使得业务数据能够动态更新;
-
描述业务逻辑,以实现业务逻辑的动态化;
-
描述页面布局。
如何解析?
苹果原生提供了 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(项目)的宽高和顺序,如通过扩大 / 缩小项目来适配(填充 / 防止超出)可用空间。
关于 Flexbox 更详细的讲解,可以参考:
-
Flex 布局教程:语法篇——阮一峰
-
Flex 布局教程:实例篇——阮一峰
-
Flex 布局示例——Blog
那跟自动布局相比,Flexbox 好在哪呢?
-
提供的布局方法更方便、更全面、更规范;
-
响应式,跨平台性好:目前所有浏览器都已支持,同时在 iOS 和 Android 中也支持。
所谓响应式,是指不直接操作目标,而是通过代理达到操作的目的。
28 | 怎么应对各种富文本表现需求?
富文本是什么?它是一段有属性的字符串。
-
可以包含不同字体、不同字号、不同背景、不同颜色、不同字间距的文字;
-
还可以设置段落、图文混排等等属性。
然后,如何展示富文本呢?这里分2种情况:
1)用 HTML 来描述的富文本
这种方式描述的富文本,可以直接使用 WKWebView 控件的 loadHTMLString:baseURL:
方法来展示。
此外,对于 HTML 里的图片资源,因为需要通过网络请求来获取,所以可以考虑缓存策略减少请求次数。
2)使用原生 iOS 代码描述的富文本
长列表场景 (一次性返回多条数据交由前端渲染)一般对性能的要求更高,所以会使用这种方式描述的富文本。
这种方式描述的富文本,可使用苹果官方的 TextKit 或者第三方的 YYText 来展示。
其中,YYText 有很多亮点:
-
在异步文字布局和渲染上的性能非常好;
-
兼容 UILabel 和 UITextView;
-
自定义的 NSMutableAttributedString 分类,不光简化了基类,还增加了嵌入 UIView、CALayer 等功能。
小结:
-
HTML 描述富文本更易读、更容易维护;
-
原生代码描述富文本的性能更高。
所以如果想结合两者的优势,可以使用 HTML 描述富文本,然后在展示前先将 HTML 转成原生代码(可参考作者的 HTN 项目,实现了 HTML 代码转原生代码的能力)。
即富文本 → HTML → 原生代码 → 展示。
29 | 如何进行 TDD 和 BDD?
背景:编写影响范围比较大的代码时,需要检验的地方就非常多,相应地,人工检查的时间成本也会非常高。
那么,如何提高编写代码后的检验效率呢?
答案是开发、测试同步进行,尽早发现问题。
从测试范围上来划分的话,测试可以分为:
-
单元测试(开发者负责)
-
集成测试(测试团队负责)
-
系统测试(测试团队负责)
其中,单元测试也叫模块测试,这个单元可能是一个类的方法,也可能是一个模块的某个函数;同时,开发者要注意保证每个单元的职责清晰。
从开发模式划分的话,开发方式可以分为:
-
TDD(Test-driven development,测试驱动开发)
-
BDD(Behavior-driven development,行为驱动开发)
TDD 的开发思路是:
-
先编写测试用例;
-
在不考虑代码优化的情况下快速编写功能实现代码;
-
等功能开发完成后,在测试用例的保障下,进行代码重构,以提高代码质量。
它的测试用例主要针对开发中的最小单元,适合单元测试。
在思想上,TDD 和拿到功能需求后直接开发功能的区别是:
-
先考虑如何对功能进行测试,再考虑如何编写代码,这给优化代码提供了更多的时间和空间;
-
即使几个版本过后再来优化,只要能够通过先前写好的测试用例,就能够保证代码质量。
PS:有点“不忘初心”的那味道~
BDD 是 TDD 的进化,它:
-
基于行为进行功能测试,使用 DSL(Domain Specific Language,领域特定语言)来描述测试用例;
-
测试用例看起来和文档一样,更易读、更好维护。
它的测试用例是对行为的描述,测试范围更大一些,适合集成测试和系统测试。
同时,得益于 BDD 使用的 DSL 语言(规范、标准、可读性高),不仅开发者可以使用 BDD 高效地发现问题,也方便测试团队参与编写。
BDD 的 OC 框架有 Kiwi、Specta、Expecta 等,Swift 框架有 Quick。
作者推荐 Kiwi 这个框架,因为它包含 Specta 的 DSL 模式,Expecta 框架的期望语法,以及 Mocks、Stubs 能力,具体使用可以参考 Kiwi Wiki(Specs | Expectations | Mocks and Stubs | Asynchronous Testing)。
Mocks:模拟对象,如模拟 Null 对象、模拟类的实例、模拟协议的实例。
Stubs:存根,可以让选择器或消息模式返回固定的结果,支持真实对象和模拟对象。
小结:
-
无论是 TDD 还是 BDD,都是先写测试用例(需要考虑到各种异常条件以及输入输出的边界),再开发代码;
-
好的模块化架构和 TDD 、BDD 是互相促进的。
作者建议:优先对基础能力的功能开发使用 TDD 和 BDD,保证了基础能力的稳定后,在时间允许的情况下,再考虑核心业务。
30 | 如何制定一套合适的编码规范?
背景:一个团队有了统一的编码规范,才能更有效地避免团队成员相互认同感缺失的问题(代码风格不一致而导致的)。
那什么是好的代码规范,需要考虑哪些方面呢?
首先,可以参看一些优秀公司的代码规范:
-
Apple: Apple Developer Documentation | Technologies(接口设计)、Coding Guidelines for Cocoa
-
Google: Google Objective-C Style Guide 中文版、Google Swift Style Guide 中文版
-
Others: Spotify Objective-C Coding Style、NYTimes Objective-C Style Guide、Raywenderlich Objective-C Style Guide、Raywenderlich Swift Style Guide。
接下来,是一份简单的参考:
-
常量:使用类型常量,而不是宏定义。
-
变量:明确体现出功能,最好加上类型做后缀;少用全局变量传递值,而是通过参数传值(减少功能模块间的耦合)。
-
属性:OC 里,尽量通过 get 方法来进行懒加载(避免无用的内存占用和多余的计算);Swift 里,如果属性是只读的,可以省掉其 get 子句。
-
条件语句:减少或不使用默认处理,特别是使用 Switch 处理枚举时(使用 Swift 编写 Switch 语句时,如果不加 default 分支的话,当枚举有新增值时,编译器会提醒你增加分支处理);减少嵌套处理(增加可读性),Swift 中可以充分利用 guard 语法。
-
循环语句:减少 continue 和 break 的使用(增加可读性),Swift 中可以统一使用 guard 来代替。
-
函数:函数名体现目的;每个函数处理最小单位的逻辑,满足单一职责原则;函数内尽量避免使用全局变量来传递数据(减少耦合,提高单元测试的准确性);注意检查函数的入参(提高健壮性),Swift 里的 guard 语法同样适用于检查入参。
-
类:在 OC 中,类的头文件尽可能少地引入其他类的头文件,而是通过
@class
来声明,然后在实现文件里再通过#import
引入需要的头文件(使用@class
保证代码编译通过,使用#improt
保证代码运行通过,参考 OC 中 @class 和 #import 的区别——CSDN);对于继承和遵循协议的情况,无法避免引入其他类里的头文件,所以在代码设计时尽量减少继承(继承关系太多不利于代码的维护和修改,比如修改父类时还需要考虑对所有子类的影响)。 -
分类: 分类里增加的方法名尽量加上前缀,如果是系统自带类的分类的话,就一定要加上前缀(避免产生方法名重复的问题);把一个类里的公有方法分类到不同的分类里,便于管理维护(特别适合多人维护各自不同功能代码的场景)。
💡:
-
代码逻辑清晰是高质量代码最基本、最必要的条件。如果代码不清晰的话,那么其他的扩展、重用、简洁优雅都免谈。
-
写代码的首要任务是能让其他人看得懂,避免过度工程化(针对特定业务的工程设计)。
-
减少使用过新的语言特性和黑魔法,如果需要使用,则多补充注释。
最后,如何将代码规范落地执行呢?
最好的方式就是 Code Review(代码审核)。通过 Code Review ,你可以:
-
检查代码规范是否被团队成员执行;
-
同时及时指导代码编写不规范的同学。
Code Review 可以分为两步走:
-
然后,再进行人工检查(审核人可以点赞、评论),除了能达到将代码规范落地的效果外,还可以促进成员之间的沟通交流和相互学习。
好啦,应用开发篇马上就要告一段落(还差一篇介绍 iOS 开发学习资料的加餐),下次就要开始原理篇的内容了,涉及 iOS 系统内核 XNU、iOS 黑魔法背后的原理……(🤫留点想象空间)。
Bo2SS 会竭尽全力把它们讲明白,还请大家多多支持~咱们下次见!