0%

绪论

我从这周开始在Course上开始了对Machine Learning的学习,注册了吴恩达教授的机器学习课程。所以我准备在自己的博客上整理课程的学习笔记,并记录自己的思考。

什么是机器学习

机器学习:致力于研究如何通过计算的手段,利用经验来来改善系统自身的性能。我们把经验数据提供给学习算法,它就能根据这些数据产生模型;在面对新的情况时,模型就可以给我们提供相应的判断。如果说计算机科学是研究关于“算法”的学问,那么我们可以说机器学习是研究“学习算法”的学问。

机器学习也有更学术化的定义:

假设用 P 来评估计算机程序在某任务上的性能,若一个程序通过利用经验 E 在 T 中任务上获得了性能呢改善,则我们就说关于 T 和 P,该程序对 E 进行了学习。 — Mitchell

注意,机器学习的目标是使学习得到的模型能很好得适用于新样本

监督学习(Supervised Learning)和非监督学习(Unsupervised Learning)

根据训练数据是否有标记信息,机器学习的任务和分为两大类:监督学习(Supervised Learning)和非监督学习(Unsupervised Learning)。对于监督学习任务,我们明确得知道数据具有某些特点或者事先已经知道了样本将会属于什么类别,例如将西瓜分为“好瓜”与“坏瓜”,根据历史房价来预测未来房价;而对非监督学习任务,我们事先也不知道算法所产生的结果所具有的特点,例如对西瓜做聚类(Clusting)后,可能将西瓜分为“浅色瓜”和“深色瓜”等我们事先不知道的概念。监督学习有两种典型的任务:分类(Classification)和回归(Regression)。如果我们希望预测的是离散值,如“好瓜”或“坏瓜”、“盈利”或“亏损”等,那么该任务为分类任务;如果我们希望预测的是连续值,如在未来某个时间节点的房价、股票价格,那么该任务为回归任务。

线性回归(Linear Regression)

线性模型是一种简单、易于建模的模型,我们可以根据已有数据学习得到线性函数中的参数。得到参数的过程叫做线性回归(Linear Regression)。
现在假设我们得到了房产面积和房价的数据,而希望预测某个面积房产的房价。首先,我们需要根据已有的数据估计出模型的函数表达式。为了直观显示房产面积和房价的关系,我们可以将房价的数据绘制出来,观察数据间的关系。

points
根据住房面积和房价之间的散点图,我们可以认为面积和房价成线性关系,即可以假设房价 y 和面积 x 的函数关系为:

$$ h_{\theta}(x) = \theta_0 + \theta_1x $$

其中 $x$ 模型的属性。
之后,我们就需要通过“学习”已有的数据,得到函数用得系数值。

成本函数(Cost Function)

在实现“回归”之前,我们将 Cost Function 定义为:

$$ J( \theta_0 , \theta_1 ) = \frac{1}{2m}\sum_{i=1}^{m}(h_{\theta}(x_i) - y_i)^2 $$

这个函数定义了使用现有模型得出的 m 个预测值和实际值的均方误差(square loss)的平均值,而前面的 $\frac{1}{2}$ 则是为了之后的处理方便而添加的。

所以,我们的“回归”过程的目的,就是通过对“学习数据”的学习,得到一个模型,是得 Cost Function 的值最小。

梯度下降(Gradient Descent)

在机器学习中,常用梯度下降(Gradient Descent)的方法,通过不断迭代,学习得到使得 $J(\theta_0, \theta_1)$ 最小的 $\theta_0$ 和 $\theta_1$ 的值。

梯度下降方法的基本过程可以用伪代码来表示:

repeat until converage {

$$\theta_0 = \theta_0 - \alpha\sum_{i=1}^{m}(h_\theta(x_i) - y_i)$$

$$\theta_1 = \theta_1 - \alpha\sum_{i=1}^{m}((h_\theta(x_i) - y_i)x_i)$$

}

要注意的是,$\theta_0$和$\theta_1$必须要被同步更新,否则会导致算法出错。

我们可以研究下,为什么通过梯度下降的方式可以最终迭代到最优点。以一个简单的二次函数为例:

group

假设我们从 A 点开始使用梯度下降的方法迭代,该函数在 A 点的导数小于零,所以在梯度下降的过程中,$\theta = \theta - \alpha\frac{\mathrm d}{\mathrm d x}J(x)$ 会使得 $\theta$ 不断向右逼近,从而接近最低点。在 B 点同理可得,在梯度下降的过程中,$\theta$ 会不断向左逼近,靠近最低点。

在梯度下降的迭代过程中,我们通过选择不同的 $\alpha$ 值来控制 $\theta$ 变化的幅度或大小。当 $\theta$ 越小时,算法迭代的过程就以越小的步伐(更高的精度)来逼近最优点。所以,$\alpha$ 被称为 Learning Rate。

当 $\alpha$ 足够小的时候,梯度下降算法一定会最终抵达最优点。而且在迭代过程中不需要改变 $\alpha$ 的值,算法最终一定会收敛。这在数学上已经得到了证明。

但是要注意,梯度下降算法最终找到的是局部最优点,而不一定是全局最优点。

多元线性回归(Linear Regression with Multiple Variables)

在单变量线性回归问题中,我们假设模型只有一个属性。而在多元线性回归问题中,我们认为模型有多个属性。例如,在之前提到的预测房价的问题中,房价可能不仅仅有房产的面积所决定,还会被房产的年代、地段等因素影响。

在队员线性回归问题中,我们用变量 n 来代表模型属性的个数,$x^{(i)}$ 是一个行向量表示第 i 个训练样本的属性集,$x_j^{(i)}$ 表示第 i 个训练样本的第 j 个属性的值。

我们令 $x_0=1$ ,那么我们的模型和定义为:

$$h_{\theta}(x) = \theta_0 + {\theta_1}x_1 + {\theta_2}x_2 + \cdots + {\theta_n}x_n$$

即:

$$ h_{\theta}(x) = {\theta}^{T}x $$

其中:

$ {\theta^{T}} = [\theta_0, \theta_1, \cdots, \theta_n], x^{T} = [1, x_1, x_2, \cdots, x_n] $

现在我们在多元线性回归问题中使用梯度下降方法学习得到模型参数的过程可以写为:

repeat until converage {

$$ \theta_{j} = \theta_{j} - \alpha\frac{1}{m}\sum_{i=1}^{m}(h_{\theta}(x^{(i)} - y^{(i)})x_j^{(i)}) $$

$$j = 1 \cdots n$$

}

注意,各参数必须同步更新。

加速收敛方法

在课程中,吴恩达教授还介绍了几种可以加速梯度下降过程的方法,分别是:Feature Scaling, Mean Normalization 和 选择 Learning Rate 的方法。

Feature Scaling

Feature Scaling 的主要原理是各个模型在数值上处于同一个“范围”,如都在$[0, 1]$这个范围。在预测房价的例子中,我们假设房产有属性:房产面积 $x_1 \in [0, 2000]$, 卧室数目 $x_2 \in [0, 5]$ 。这两个属性不在同一个数量级,那么在梯度下降的迭代过程中,可能出现算法收敛过慢,收敛路径复杂等问题。

为了解决这个问题,我们使用 Feature Scaling 方法,使得:

$$x_1 = \frac{x_1}{2000}$$ $$x_2 = \frac{x_2}{5}$$

或者我们使用 Mean Normalization 方法。令 $\mu_i = mean(x_i)$ , 有:

$$x_i = \frac{x_i - \mu_i}{max(x_i) - min(x_i)}$$

通过这两种方法,我们可以将所有属性在数值上属于同一范围,从而可以显著梯度下降的迭代速度。

选择合理的 Learning Rate

另外一种保证算法收敛的方式就是要选择一个大小合适的 Learning Rate。因为如果 $\alpha$ 过小,那么算法会收敛过慢;如果 $aplha$ 过大,可能导致算法无法保证在每次迭代使得参数减小。

我们可以将使用不同 Learning Rate 的迭代过程用可视化的方式表示出来,通过这种更形象的方式选择合适的 Learning Rate。

多项式回归(Polynomial Regression)

我们可以将多个属性组合成一个新的属性用于对模型参数的预测,例如将房产的地基长度x1和地基宽度x2组合为房产面积x3。

当我们的训练数据不能很好得用线性函数描述时,我们可以考虑使用二次或三次函数。

Normal Equation

在线性回归的问题中,我们的目的是通过学习训练数据,得到新的参数 $\theta$ 使得 $J(\theta)$ 最小。体用微积分的知识,我们可以对 $J(\theta)$ 中的 $\theta_0$ , $\theta_1$ 等分别求偏导数,并同时令偏导数等于0,即可算出使得 $J(\theta)$ 最小的参数值。

结论: 当 $\theta = (X^{T}X)^{-1}X^{T}y$时,$J(\theta)$ 最小。

我们对比梯度下降和 Normal Equation 方法:

| 梯度下降 | Normal Equation |
| 需要选择合适的 Learning Rate | 不需要选择Learning Rate |
| 需要多次迭代 | 不需要迭代 |
| 时间复杂度 O(kn^2) | 时间复杂度 O(n^3) |
| 在样本数量很大时,很有效 | 样本数量很大时,效率很低 |

参考文献

  1. 机器学习 周志华
  2. Machine Learning Stanford

guard 关键字从 Swift 2.0 开始被引入,但是因为这一个关键字同时可以发挥四个作用,所以我们需要全面地理解它。

第一个作用显而易见:guard 用来实现提前退出。这意味着一个函数在一些前提条件不满足的情况下可以直接返回。例如,我们写一个简单的函数,实现为一个歌手颁奖:

1
2
3
4
5
6
7
8
9
func giveAward(to name: String) {
guard name == "ChuanleiGuo" else {
print("No way!")
return
}

print("Congratulations, \(name)!")
}
giveAward(to: "ChuanleiGuo")

giveAward(to:) 方法中使用 guard 关键字使得只有 ChuanleiGuo 同学才能获奖😋。虽然这个函数没什么实际作用,但是方法能够继续运行的前提条件非常清晰:只有在获奖人是 ChunaleiGuo 同学的时候,方法才能继续进行。

这个例子看起来和直接使用 if 语句做判断几乎是一样的,但是使用 guard 有一个巨大的优势:使你的意图更加清晰,不仅仅对于人,对于编译器也是一样。这是一个提前退出语句,意味着当条件不满足的时候,方法应该退出。使用 guard 说明:这个条件并不是方法的一部分,它存在的意义就是要保证后面的代码安全地执行。这对编译器也更加清晰,如果我们删除了上面的 return 语句,代码就无法编译了。因为编译器发现这是一个提前退出语句,而我们却没有退出。

第二个作用可以说是意外的收获:使用 guard 可以减少我们代码的缩进量。一些开发者朋友仍未永远都不应该使用提前退出,相反,每一个函数仅仅应该在一个地方返回。这就要求在函数的主要部分增加额外的缩进量:

1
2
3
4
5
6
7
8
9
10
11
12
13
func giveAward(to name: String) -> String {
let message: String

if name == "ChuanleiGuo" {
message = "Congratulations, \(name)!"
} else {
message = "No way!"
}

return message
}

giveAward(to: "ChuanleiGuo")

使用 guard 后,我们立刻处理的函数运行的前提条件,并且消除了额外的缩进量,代码也变得更经凑了呢!

第三个作用是使得代码的成功路径更加清晰。成功路径,代表着在没有错误和异常发生的情况下,你的代码的运行路径。有了 guard 之后,所有的错误都被立刻消除了。现在代码里仅仅剩下了成功路径。

以上的三个作用都比较简单,但是 guard 的还有很重的的一个作用,这也是它和 if 语句的重要区别:在我们使用 guard 判断并解包一个可选值之后,可选值会继续存在于作用域中。为了演示,我们重写 giveAward(to:):

1
2
3
4
5
6
7
8
func giveAward(to name: String?) {
guard let winner = name else {
print("No one won the award")
return
}

print("Congratulations, \(winner)!")
}

如果使用 if - let 语句的话,winner 仅仅会存在于被属于 if - let 大括号包围的作用域中。然而,guard 使得解包后的可选值继续存在于函数的作用域中,所以我们可以使用 print() 直接输出它。这段代码可以这样理解:“试着将解包后的可选值复制给winner,来让我们继续使用它,但是如果可选值为空,那么打印信息并退出”。

最后一个作用,并不新鲜。相反,它仅仅是对于已知知识点的新运用:如果条件不满足的话,guard可以让我们跳出任何作用域,不仅仅是函数的方法。也就是说,你可以跳出 switch语句,也可以跳出 whilefor 循环。

下面的例子,用两种方法实现从1遍历到100,输出所有可以被8整除的数:

1
2
3
4
5
6
7
8
for i in 1...1000 {
guard i % 8 == 0 else { continue }
print(i)
}

for i in 1...1000 where i % 8 == 0 {
print(i)
}

我们可以在不同的情况下选择使用不同的方法。

开学后我就是研究生了,进入了新的求学的阶段。

为什么要读研呢?因为我明白自己的实力、资历还不能够胜任好公司的岗位,所以决定继续在学校学习。这也就意味着,接下来的三年我的长远计划就是要持续学习、科研和实习,最终顺利拿到心仪的Offer。

我从大一进入计算机学院开始学习编程,从C语言起,依次学习了C++、Java、Python、Objective-C、Swift,JavaScript,研究了Web开发的各种框架,可是最终都仅仅停留在了明白如何使用,至于它们的实现原理,核心思想则一概不知。在本科四年的时间里,虽然编写了Web后台、前端、iOS App等大大小小的所谓“项目”,但是现在回过头来看看,翻翻躺在我硬盘里的项目代码,真是觉得拿不出手。滥用单例、没有设计模式、命名不规范、逻辑混乱、模块无法复用、完全不做测试等等问题,让我意识到自己的代码力还远远不够。

在本科期间,我做过的最正确的决定就是跟着Bia哥去参加ACM训练和竞赛。在学校培训期间,我认识了来自各个学院的编程好手:徐翔、赞赞、++7、dyx,最重要的是在有幸跟随校队参加的几场ACM比赛中还抱大腿拿了奖,嘿嘿。。。在封闭训练和比赛的日子里,我观察大神们的学习技巧、编码技巧,感受他们对技术的热情。我从他们对技术的理解里学到了很多东西,打开了自己的眼界,也要求自己写出高质量的代码,受益匪浅。我的队友们在毕业的时候也顺利进入了自己心仪的公司,做着自己喜欢的工作,前路一片光明。这也让我相信,美好的人总是会被世界善待的。

被录取后,我也开始了自己的⎡研究生自我培养计划⎦。鉴于20年来的计划失败经历,这次在自我培养计划中,我没有定太高或太宏伟的目标,只是从一些小目标开始,一步步进步。那么研一上学期我的计划只有两项:

  1. 坚持学英语,每天学习⎡懂你英语⎦课程的两个Lesson,听写一篇英语文章。
  2. 坚持每天课外编程至少3小时。

在英语学习上,我的目标是要实现一次美国自由行。我从7月份开始购买了英语流利说的⎡懂你英语⎦课程,用来改善自己哑巴英语的问题。在7、8两个月我连续打卡,从Level3升到了Level5。所以在研究生入学后继续坚持英语学习,不断做自我小测验,最终在11月份参加雅思考试来检验自己的水平。

在编程学习上,必须完成的任务是每天至少完成一道LeetCode题目来练习算法和数据结构,到目前为止已经完成了249道题目;每天必须给自己项目提交代码,现在手头正在进行的是使用Swift仿写⎡网易新闻⎦客户端,完成后会放到Github接受群众检验。项目完成后,我也不能仅仅停留于实现功能,而要继续不断重构,提高代码质量;不能仅仅使用开源库,也会分析和阅读优秀项目的源代码,目前我暂时完成了一篇代码阅读心得:(SwiftyJSON源代码学习笔记)[] 。以后也会不断更新。

希望自己的研究生生活不要再像本科那样蹉跎,从实现一个个小目标开始,踏实前行。

加油吧。

《富爸爸 穷爸爸》是我读的第一本财务类,或者说财商类的书。

作为一个学生党,对未来有无数不切实际的,宏大的设想,实现财务自由也是一个刚刚有了微薄的收入,深感钱不够花的人的美好的愿望。我希望了解一些财务类的知识,但又担心太晦涩、太专业的著作会让我这个“财盲”望而却步,所以我在豆瓣检索、比较后选择了《富爸爸 穷爸爸》这本书。

image

这是一本优秀的财务启蒙书。之前,我对财务的理解就是省钱 -> 努力工作 -> 存钱。这本书让我明白了其实投资和理财并不是富人的专属活动,普通人也可以通过学习和努力,培养财商,逐步踏上财务自由之路。理财和投资是一个生活态度,财商可以让人获利。

作者有两个爸爸,一个富,一个穷。一个爸爸受过良好的教育,拥有博士学位,但是一生都在财务问题中挣扎;另一个爸爸低学历,但却是当地最富有的人之一。两个爸爸都曾给予他人生和理财的建议,比如:一个爸爸说“贪财乃万恶之源”,另一个爸爸说“贫穷才是万恶之源”。作者通过对比和独立的思考最终选择了自己的道路,并实现了财务自由。作者希望每个人都接受财商教育,获得驾驭钱的力量,并在本书中教授了六门课程。

富人不为钱而工作

生活是最好的老师,它永远在推着人们前进,也在不断给予人们机会。一些人在被生活推着转的同时,抓住了生活赐予的机会,学会了新的东西;而另一些人则与生活抗争,对机会视而不见,最终对生活屈服。

改变自己比改变比人更容易

我们在被生活推着前进的同时,抱怨、愤怒没有任何作用,要不断学习,让自己进步,因为改变自己永远比改变别人更容易。

“如果你认为这是我的问题,你就会想要改变我;如果你认为问题在你那儿,你就会改变自己,学习一些东西让自己变得更聪明。大多数人认为世界上除了自己外,其他人都需要改变。其实,改变自己比改变别人更容易。”

穷人和中产阶级为钱工作

学习需要精力、激情和愿望,激情是愤怒和热爱的集合体。而关于钱,穷人没有激情,只有恐惧,他们相信工作就是为了钱。它们的生活被恐惧和贪婪所控制。

大多数人的学习在大学毕业后也就到头了,而我们却应该用一生来研究钱和钱的规律。缺乏财商教育才是解决生活问题的关键。

我们要学习在深思后作出思考,而不是用感情。

永远别忘记,会有两种感情——恐惧和欲望,使你落入一生中最大的陷讲。

富人不为钱而工作。它们忘记自己的工资,不求回报地工作,并最终看见别人看不见的的东西。它们会在一生中不断地发现机会,从而避开生活中最大的陷阱。

为什么要教授财务知识

只有知识才能解决问题并创造财富,那些不是靠财务知识挣来的钱也不会长久。

从长远来看,重要的不是你挣了多少钱,而是你能留下多少钱,以及能留住多久。

所以,如果你想发财,就要学习财务知识。

要分辨资产和负债的区别。

富人获得资产,而穷人和中产阶级获得负债,只不过他们以为那些负债就是资产。
资产,就是能把钱放到你口袋里的东西。

看不懂关于财务的文字或者读不懂数字的含义,是产生财务问题的根本原因。

聪明的人要雇佣比他更聪明的人。

资产项产生的收入足以弥补支出,还可以用剩余的收入对资产项进行再投资。资产项不断增长,相应的收入也会越来越多。其结果就是:富人越来越富!

财富是将资产项产生的现金与支出项流出的现金进行比较而定的。

富人买入资产,穷人只有支出,中产阶级购买自以为是资产的负债。

关注自己的事业

从事你所学的专业的可怕后果在于,它会让你忘记关注自己的事业。人们耗尽一生去关注别人的事业并使他人致富。
为了财务安全,人们需要关注自己的事业。

只有你把额外的收入用来购买可产生收入的资产,你才能获得真正的财务安全。

对于成年人而言,把支出保持在低水平,减少借款并勤劳工作会帮你打下一个稳固的资产基础。

真正的资产可分为:

  1. 不需要自己到场就可以正常运作的业务。
  2. 股票
  3. 债券
  4. 共同基金
  5. 能产生收入的房地产
  6. 票据
  7. 版税,如:音乐、手稿、专利
  8. 其它任何有价值或有增值潜力的东西

关注自己的事业,也就是要建立自己牢固的资产。

税收的历史和公司的力量

政府对钱的胃口越来越大,且税收的范围正向穷人拓展。

资本家用财务知识逃脱了税收,并借助公司的保护来避税。

公司并不一定是一个真正是实体,公司可以是一些装着法律文件的文件夹,被放在办公室里。

“精于计算,你就不会被别人牵着走。”

“我把工资投资于资产项,再用资产项为我生产出来的钱购买我想要的东西。”

财商是由四个方面的专门知识构成的:

  1. 会计,也就是财务知识。
  2. 投资,即钱生钱的科学。
  3. 了解市场,是供给于需求的科学。
  4. 法律。如:减税优惠,公司可以做许多个人无法完成的事情;在诉讼中获得保护。

富人的投资

我们都拥有巨大的潜能。然而,我们都或多或少地存在着某种自我怀疑的心理,从而阻碍进步的步伐。这种障碍很少是缺乏某种技术性的东西,更多的是缺乏自信。

陈旧的思想是人最大的债务。

提高自己的理财能力,就可以成为能够自己创造机遇的人,能够坦然地接受发生的任何事情,并努力使事情变得更好。

“金钱不是真正的资产。”

投资者分为两类:

  1. 进行一揽子投资的人。
  2. 自己创造投资机会的投资者。

要成为自己创造投资机会的投资者,还需要具备三种技能:

  1. 寻找其他人都忽略的机会。
  2. 如何增加资金。
  3. 把精明的人组织起来。

风险无处不在,要学会驾驭风险,而别总想着回避风险。

学会不为钱而工作

大部分人需要学习和掌握不止一项技能,只有这样他们的收入才能获得显著增长。

不断学习才是一切。

从长远来看,教育比金钱更有价值。

许多才华横溢的人之所以贫穷,就是因为他们只是专心做好产品,而对商业体系知之甚少。

成功所需要的管理技能包括:

  1. 对现金流的管理。
  2. 对系统的管理。
  3. 对人员的管理。

销售技能是个人成功的基本技能,它涉及与其他人的交往,包括与顾客、雇员、老板、配偶和孩子。而沟通能力,如书面表达、口头表达以及谈判能力等对于一个人来说更是至关重要。

客服困难

人们在学会财务知识后,在通往财务自由的路上还面临着许多障碍:

  1. 对可能亏钱的恐惧心理。
  2. 愤世嫉俗的心理。
  3. 懒惰。忙碌的人常常是最懒惰的。懒惰的一种最普遍的表现形式:通过忙碌来掩饰懒惰。
    “我可付不起”这句话禁锢了你的思想,使你无法进一步思考。“我怎样才能付得起”这句话开启了你的头脑,迫使你去思考并寻求答案。
  4. 习惯。
  5. 傲慢。傲慢是自大和无知的集合体。
    如果你知道自己在某一问题上有所欠缺,你就应该找一位本领域的专家或者一本相关的书,马上开始教育自己。

开始行动

用10个步骤来开发自己的才能:

  1. 找到自己的精神力量。
  2. 每天作出自己的选择---选择的力量。
  3. 慎重地选择朋友---关系的力量。
  4. 掌握一种模式,然后在学习一种新的模式---快速学习的力量。
  5. 首先支付自己---自信的力量。如果你控制不了自己,就别想着致富。
    能否自律是将富人、穷人和中产阶级区分开来的首要因素。
  6. 给你的经纪人以优厚的报酬---好建议的力量。
  7. 做一个无私的给予者---无私的力量。
  8. 用资产来购买奢侈品---专注的力量。
  9. 对英雄的崇拜---神话的力量。
  10. 先予后取---给予的力量。

SwiftyJSON 是使用Swift编写的 JSON 解析库。Swift 是一门强类型的语言,并且引入了 Optional 类型。这使得解析 JSON 数据变得异常麻烦,我们往往需要不断地判断 Optional 中是否包含了有效值:

1
2
3
4
5
6
7
8
9
10
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(dataFromTwitter, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let statusesArray = jsonObject as? NSArray{
if let aStatus = statusesArray[0] as? NSDictionary{
if let user = aStatus["user"] as? NSDictionary{
if let userName = user["name"] as? NSDictionary{

}
}
}
}

使用可选链的来访问 JSON 中的数据也依然非常麻烦。

SwiftyJSON 很好的解决了上面的问题,使我们可以用简洁的语法访问数据,而不用担心程序崩溃。正是因为安全、高效以及极易上手的特点,它在Github获得了超过10000 Star。

“优秀的源代码使最好的老师”。我们来研究一下 SwiftyJSON 是怎样实现的。

数据保存

SwiftJSON 使用一个 Struct 来抽象表示 JSON:

1
2
3
public struct JSON {

}

我们在json.org看到,JSON 只可以存储几种简单的数据:

  1. 键值对,即字典
  2. 数组,可以保存一系列的值
  3. 具体值,它可以是字符串,数字,json对象,数组,bool值,还可能是null

SwiftyJSON 定义了一个枚举来抽象表示这些数据类型:

1
2
3
4
5
6
7
8
9
public enum Type :Int{
case Number
case String
case Bool
case Array
case Dictionary
case Null
case Unknown // 代表未知类型,或无法解析
}

SwiftyJSON 使用一个私有变量_type来确定数据的类型,并使用一系列私有的变量来保存相应类型的元数据(Raw Value)。

1
2
3
4
5
6
7
8
9
10
private var _type: Type = .Null  // 初始为空类型

private var rawArray: [AnyObject] = []
private var rawDictionary: [String : AnyObject] = [:]
private var rawString: String = ""
private var rawNumber: NSNumber = 0
private var rawNull: NSNull = NSNull()

// 使用私有的 _error 来存储错误
private var _error: NSError? = nil

现在,可以使用一个抽象的公有变量object作为接口,根据Type枚举来设置或取得JSON相应类型的数据:

1
2
3
4
5
6
7
8
public var object: AnyObject {
get {
// 根据 _type 来取得相应的数据
}
set {
// 根据 _type 来设置相应的数据
}
}

我们以objectset方法为例来演示如何设置JSON的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public var object: AnyObject {
get {

}
set {
_error = nil
switch newValue {
case let number as NSNumber:
if number.isBool {
_type = .Bool
} else {
_type = .Number
}
self.rawNumber = number
case let string as String:
_type = .String
self.rawString = string
case _ as NSNull:
_type = .Null
case let array as [AnyObject]:
_type = .Array
self.rawArray = array
case let dictionary as [String : AnyObject]:
_type = .Dictionary
self.rawDictionary = dictionary
default:
_type = .Unknown
_error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType,
userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"])
}
}
}

objectget方法也是同样使用switch/case语句来根据_type得到相应的数据。

初始化

SwiftyJSON 提供了丰富的初始化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 NSData 来创建 JSON 
public init(data: NSData, options opt: NSJSONReadingOptions, error: NSErrorPointer)

// 使用拥有字典、数组等数据类型属性的对象来创建 JSON
public init(_ object: AnyObject)

// 使用 [JSON] 来初始化 JSON
public init(_ jsonArray: [JSON])

// 使用 [String: JSON] 来初始化 JSON
public init(_ jsonDictionary: [String: JSON])

// 使用 JSON 字符串来初始化 JSON
public static func parse(string: String) -> JSON

第一个初始化方法使用NSJSONSerialization创建包含相应属性的 JSON 对象:

1
2
3
4
5
6
7
8
9
10
11
public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) {
do {
let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt)
self.init(object)
} catch let aError as NSError {
if error != nil {
error.memory = aError
}
self.init(NSNull())
}
}

通过调用这个初始化方法,可以将 JSON 字符串解析为 JSON 对象。如果字符串是合法的 JSON 字符串,那么就返回相应的 JSON 对象,负责返回一个空 JSON 对象:

1
2
3
4
public static func parse(string: String) -> JSON {
return string.dataUsingEncoding(NSUTF8StringEncoding)
.flatMap({JSON(data: $0)}) ?? JSON(NSNull())
}

剩下的三个初始化方法均是直接设置 struct JSON 中的 object 对象来完成初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public init(_ object: AnyObject) {
self.object = object
}

public init(_ jsonArray:[JSON]) {
self.init(jsonArray.map { $0.object })
}

public init(_ jsonDictionary:[String: JSON]) {
var dictionary = [String: AnyObject](minimumCapacity: jsonDictionary.count)
for (key, json) in jsonDictionary {
dictionary[key] = json.object
}
self.init(dictionary)
}

用字面量初始化 JSON

SwiftyJSON 的另外一项功能就是可以使用字面量初始化 struct JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// StringLiteralConvertible
let json: JSON = "I'm a json"

// IntegerLiteralConvertible
let json: JSON = 12345

// FloatLiteralConvertible
let json: JSON = 2.8765

// With subscript in array
var json: JSON = [1,2,3]

// With subscript in dictionary
var json: JSON = ["name": "Jack", "age": 25]

// Array & Dictionary
var json: JSON = ["name": "Jack", "age": 25, "list": ["a", "b", "c", ["what": "this"]]]

这个功能的实现很直接,就是根据Swift Literal Convertibles遵循一系列协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
extension JSON: StringLiteralConvertible { ... }

extension JSON: IntegerLiteralConvertible { ... }

extension JSON: BooleanLiteralConvertible { ... }

extension JSON: FloatLiteralConvertible { ... }

extension JSON: DictionaryLiteralConvertible { ... }

extension JSON: ArrayLiteralConvertible { ... }

extension JSON: NilLiteralConvertible { ... }

综上,SwiftyJSON 在初始化以后得到的是一个多个保存不同类型数据的 JSON 对象相互嵌套的模型:

image.001

解析 JSON

下标

在 SwiftyJSON 中的struct JSON保存的可能是字典,数组,或者是具体的值,如:字符串,数字。当保存的是字典或数组时,我们需要使用分别使用字符串和数字作为下标来访问其中的数据。为了可以同时使用字符串和数字作为下标,SwiftyJSON 定义了一个协议 JSONSubscriptType 来代表 struct JSON 的下标,并定义枚举类型 JSONKey 来区分字符串和数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum JSONKey {
case Index(Int)
case Key(String)
}

public protocol JSONSubscriptType {
var jsonKey:JSONKey { get }
}

extension Int: JSONSubscriptType {
public var jsonKey:JSONKey {
return JSONKey.Index(self)
}
}

extension String: JSONSubscriptType {
public var jsonKey:JSONKey {
return JSONKey.Key(self)
}
}

现在使得字符串和整数均遵循 JSONSubscriptType 协议后,我们就可以同时使用这两种类型作为下标来访问数据了。SwiftyJSON 也在拓展中定义了两个下标方法,它们分别接受整数和字符串作为参数来访问数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
extension JSON {
private subscript(index index: Int) -> JSON {
get {
if self.type != .Array {
var r = JSON.null
r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"])
return r
} else if index >= 0 && index < self.rawArray.count {
return JSON(self.rawArray[index])
} else {
var r = JSON.null
r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"])
return r
}
}
set {
if self.type == .Array {
if self.rawArray.count > index && newValue.error == nil {
self.rawArray[index] = newValue.object
}
}
}
}

private subscript(key key: String) -> JSON {
get {
var r = JSON.null
if self.type == .Dictionary {
if let o = self.rawDictionary[key] {
r = JSON(o)
} else {
r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"])
}
} else {
r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"])
}
return r
}
set {
if self.type == .Dictionary && newValue.error == nil {
self.rawDictionary[key] = newValue.object
}
}
}
}

这两个私有方法分别使用整数和字符串作为参数来访问数据。在函数内部的访问逻辑中,它们分别判断了类型错误,整数下标是否越界,字符串下标是否存在等错误,完善得处理了在获得 JSON 数据过程中可能出现的错误。

SwiftyJSON 还定义了一个私有的下标方法来优雅地简化了对上述两个方法的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private subscript(sub sub: JSONSubscriptType) -> JSON {
get {
switch sub.jsonKey {
case .Index(let index): return self[index: index]
case .Key(let key): return self[key: key]
}
}
set {
switch sub.jsonKey {
case .Index(let index): self[index: index] = newValue
case .Key(let key): self[key: key] = newValue
}
}
}

这个私有的下标方法给提供的一个简单的内部接口,它判断下标的类型,从而在内部调用的时候可以不用关心下标的具体类型。

在以上三个私有方法的基础上,SwiftyJSON 定义了两个接口,使得我们可以用非常简单的语法来安全地访问数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public subscript(path: [JSONSubscriptType]) -> JSON {
get {
return path.reduce(self) { $0[sub: $1] }
}
set {
switch path.count {
case 0:
return
case 1:
self[sub:path[0]].object = newValue.object
default:
var aPath = path; aPath.removeAtIndex(0)
var nextJSON = self[sub: path[0]]
nextJSON[aPath] = newValue
self[sub: path[0]] = nextJSON
}
}
}

public subscript(path: JSONSubscriptType...) -> JSON {
get {
return self[path]
}
set {
self[path] = newValue
}
}

这两个函数巧妙得使用了函数式编程的技巧,如path.reduce(self) { $0[sub: $1] },使代码简洁易读,这也正是使用 Swift 写出的代码应该具备的特点:安全、简洁、高效。

使用这两个方法,我们可以这样得到 JSON 对象中的数据:

1
2
3
let name = json[9]["list"]["person"]["name"]

let sameName = json[9,"list","person","name"]

这比起文章开头的例子,是不是简单、易读了太多呢?

安全地访问数据

SwiftyJSON 使用两种不同命名的只读计算变量来使我们可以根据自己的需要安全得访问数据。只读计算变量的命名遵循以下规则:

  1. type —— 返回包含该类型的可选值,当数据错误时返回nil
  2. typeValue —— 返回具体的值,当数据错误时返回具体类型的默认值,如[], 0等。

所以要得到 JSON 数组或字典,我们可以使用以下接口:

1
2
3
4
5
6
7
8
9
10
11
// MARK: - Array
extension JSON {
public var array: [JSON]? { get } // 返回 Optional([JSON])
public var arrayValue: [JSON] { get } // 返回 [JSON]
}

// MARK: - Dictionary
extension JSON {
public var dictionary: [String: JSON]? { get }
public var dictionaryValue: [String: JSON] { get }
}

要得到数组和字典中的元素,SwiftyJSON 也定义了相应的接口,它们均是计算属性,通过访问内部的数据结构来获取或设置数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// MARK: - Array
extension JSON {
//Optional [AnyObject]
public var arrayObject: [AnyObject]? {
get {
switch self.type {
case .Array:
return self.rawArray
default:
return nil
}
}
set {
if let array = newValue {
self.object = array
} else {
self.object = NSNull()
}
}
}
}

// MARK: - Dictionary
extension JSON {
//Optional [String : AnyObject]
public var dictionaryObject: [String : AnyObject]? {
get {
switch self.type {
case .Dictionary:
return self.rawDictionary
default:
return nil
}
}
set {
if let v = newValue {
self.object = v
} else {
self.object = NSNull()
}
}
}
}

SwiftyJSON 中 struct JSON 内部使用NSNumber类型的rawNumber来保存bool和数字,所以SwiftyJSON 定义了返回NSNumber的接口,也可以通过这个接口来得到相应类型的数字,如:Int, Double, Float, Int8, Int16, Int32, Int64等。

特别注意一点,因为 JSON 本质上是一个文本文件,它的内容均是字符串。所以在我们要获得数字的时候,要将用字符串表示的数字,如“123”,转化为NSNumber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// MARK: - Number
extension JSON {

//Optional number
public var number: NSNumber? {
get {
switch self.type {
case .Number, .Bool:
return self.rawNumber
default:
return nil
}
}
set {
self.object = newValue ?? NSNull()
}
}

//Non-optional number
public var numberValue: NSNumber {
get {
switch self.type {
case .String:
let decimal = NSDecimalNumber(string: self.object as? String)
if decimal == NSDecimalNumber.notANumber() { // indicates parse error
return NSDecimalNumber.zero()
}
return decimal
case .Number, .Bool:
return self.object as? NSNumber ?? NSNumber(int: 0)
default:
return NSNumber(double: 0.0)
}
}
set {
self.object = newValue
}
}
}

之后,SwiftJSON 使用public var numberpublic var numberValue这两个接口在得到各种数字类型,以Double为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension JSON {
public var double: Double? {
get {
return self.number?.double
}
set {
if let newValue = newValue {
self.object = NSNumber(double: newValue)
} else {
self.object = NSNull()
}
}
}

public var doubleValue: Double {
get {
return self.numberValue.doubleValue
}
set {
self.object = NSNumber(double: newValue)
}
}
}

获得其它类型数字的接口的实现方法和这段代码类似。

此外,SwiftyJSON 也定义了我们非常常用的返回URL的接口,它返回一个Optional:

1
2
3
4
// MARK: - URL
extension JSON {
public var URL: NSURL? { get }
}

使用 Raw Value

除了使用解析后的具体的值以外,SwiftyJSON 通过遵循RawRepresentable协议,来允许我们直接使用元数据(Raw Value)。我们可以选择直接使用 JSON 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
extension JSON: RawRepresentable {
public init?(rawValue: AnyObject) {
if JSON(rawValue).type == .Unknown {
return nil
} else {
self.init(rawValue)
}
}

public var rawValue: AnyObject {
return self.object
}
}

可以直接将 JSON 对象转化为NSData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension JSON {
public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData {
guard NSJSONSerialization.isValidJSONObject(self.object) else {
throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"])
}

return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt)
}
}

// 我们可以简单地将 JSON 对象转化为 NSData
if let data = json.rawData() {
//Do something you want
}

也可以把 JSON 对象转化为字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
extension JSON {
public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? {
switch self.type {
case .Array, .Dictionary:
do {
let data = try self.rawData(options: opt)
return NSString(data: data, encoding: encoding) as? String
} catch _ {
return nil
}
case .String:
return self.rawString
case .Number:
return self.rawNumber.stringValue
case .Bool:
return self.rawNumber.boolValue.description
case .Null:
return "null"
default:
return nil
}
}
}

// 我们可以简单地将 JSON 对象转化为 String
if let string = json.rawString() {
//Do something you want
}

SwiftyJSON 也让 struct JSON 遵循了PrintableDebugPrintable协议,使我们可以直接以字符串的形式输出JSON:

1
extension JSON: Printable, DebugPrintable { ... }

遍历 JSON

在 SwiftyJSON 的介绍中,描写了对 JSON 的遍历功能:

如果 JSON 是 Dictionary

1
2
3
for (key: subJson): (String: JSON) in JSON {
// 其中 key 就是字典中的键
}

如果 JSON 是 Array

1
2
3
for (index, json): (String: JSON) in JSON {
// 其中 key 就是字符串类型的 o..<json.count 的值
}

要使得 struct JSON 可以在for…in循环中被多次遍历,就需要让 struct JSON 遵从CollectionType协议。

遵循 CollectionType 协议

首先,SwiftyJSON 定义了遵循CollectionType协议所需要的两个关联类型,它们分别代表了集合中的元素的生成器、访问集合元素时所要使用的下标类型,以及遍历时生成下一个元素的生成器:

1
2
3
4
5
6
7
8
9
extension JSON: CollectionType {
public typealias Generator = JSONGenerator

public typealias Index = JSONIndex

public func generate() -> JSON.Generator {
return JSON.Generator(self)
}
}

之后,实现了返回起始和结尾索引、根据索引返回相应元素的接口,并根据需要重写了默认的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
extension JSON: CollectionType {
public var startIndex: JSON.Index {
switch self.type {
case .Array:
return JSONIndex(arrayIndex: self.rawArray.startIndex)
case .Dictionary:
return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex)
default:
return JSONIndex()
}
}

public var endIndex: JSON.Index {
switch self.type {
case .Array:
return JSONIndex(arrayIndex: self.rawArray.endIndex)
case .Dictionary:
return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex)
default:
return JSONIndex()
}
}

public subscript (position: JSON.Index) -> JSON.Generator.Element {
switch self.type {
case .Array:
return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!]))
case .Dictionary:
let (key, value) = self.rawDictionary[position.dictionaryIndex!]
return (key, JSON(value))
default:
return ("", JSON.null)
}
}

public var isEmpty: Bool {
get {
switch self.type {
case .Array:
return self.rawArray.isEmpty
case .Dictionary:
return self.rawDictionary.isEmpty
default:
return true
}
}
}

public var count: Int {
switch self.type {
case .Array:
return self.rawArray.count
case .Dictionary:
return self.rawDictionary.count
default:
return 0
}
}

public func underestimateCount() -> Int {
switch self.type {
case .Array:
return self.rawArray.underestimateCount()
case .Dictionary:
return self.rawDictionary.underestimateCount()
default:
return 0
}
}
}

当 JSON 中保存的对象是数组或字典是,要分别使用整数和字符串作为索引来访问相应的值。所以在接口的实现中,根据索引的类型来访问相应的数据结构。

因为 Swift 是一门多范式的“面向协议”的编程语言。在 Protocol Extension 的帮助下,我们不需要实现协议的所有方法。仅仅需要覆盖少数方法即可。其它的方法可以直接使用默认实现。

实现 JSONGenerator

在使 struct JSON 遵循CollectionType*的时候,需要使用一个元素生成器。SwiftyJSON 定义了一个 *JSONGenerator 类型的生成器。它的元素类型为(String, JSON),即一个tuple。这个tuple的第一个值用字符串表示元素在集合中的索引;第二个值则是struct JSON,这也使我们可以在遍历过程中方便地访问数据。

因为在 JSON 中可以遍历数组或字典,所以JSONGenerator中使用了标准库中的IndexingGenerator, DictionaryGenerator两个生成器来分别生成数组或字典元素,并使用一个私有变量type来对生成的元素的类型加以区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public struct JSONGenerator: GeneratorType {
// 生成元素的类型为(String, JSON)
public typealias Element = (String, JSON)

private let type: Type
private var dictionayGenerate: DictionaryGenerator<String, AnyObject>?
private var arrayGenerate: IndexingGenerator<[AnyObject]>?
private var arrayIndex: Int = 0 // 当生成器生成数组元素时,保存数组索引

init(_ json: JSON) {
self.type = json.type
if type == .Array {
self.arrayGenerate = json.rawArray.generate()
}else {
self.dictionayGenerate = json.rawDictionary.generate()
}
}
}

要实现一个生成器非常简单,仅仅需要实现一个next方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public mutating func next() -> JSONGenerator.Element? {
switch self.type {
case .Array:
if let o = self.arrayGenerate?.next() {
let i = self.arrayIndex
self.arrayIndex += 1
return (String(i), JSON(o))
} else {
return nil
}
case .Dictionary:
if let (k, v): (String, AnyObject) = self.dictionayGenerate?.next() {
return (k, JSON(v))
} else {
return nil
}
default:
return nil
}
}

这个方法会根据type来分别调用相应的生成器来生成数组或字典元素。

实现 JSONIndex

在遍历 struct JSON 的过程中,我们需要使用索引来访问相应的元素。因为我们所遍历的可能时数组或字典,所以在JSONIndex中,分别声明了两个变量arrayIndexdictionaryIndex来作为索引,并使用一个私有变量 type 来区分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public struct JSONIndex {
let arrayIndex: Int?
let dictionaryIndex: DictionaryIndex<String, AnyObject>?

let type: Type

init(){
self.arrayIndex = nil
self.dictionaryIndex = nil
self.type = .Unknown
}

init(arrayIndex: Int) {
self.arrayIndex = arrayIndex
self.dictionaryIndex = nil
self.type = .Array
}

init(dictionaryIndex: DictionaryIndex<String, AnyObject>) {
self.arrayIndex = nil
self.dictionaryIndex = dictionaryIndex
self.type = .Dictionary
}
}

要使得JSONIndex可以在集合中作为索引使用,它必须遵循ForwardIndexType协议,并实现协议中的successor方法:

1
2
3
4
5
6
7
8
9
10
11
12
extension JSONIndex: ForwardIndexType {
public func successor() -> JSONIndex {
switch self.type {
case .Array:
return JSONIndex(arrayIndex: self.arrayIndex!.successor())
case .Dictionary:
return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor())
default:
return JSONIndex()
}
}
}

在这个实现中,根据遍历数据结构类型的不同,而返回不同类型的索引。

我们需要知道两个JSONIndex是否相等,或得到两个索引的大小关系,所以JSONIndex遵循了EquatableComparable协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
extension JSONIndex: Equatable, Comparable {}

public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
switch (lhs.type, rhs.type) {
case (.Array, .Array):
return lhs.arrayIndex == rhs.arrayIndex
case (.Dictionary, .Dictionary):
return lhs.dictionaryIndex == rhs.dictionaryIndex
default:
return false
}
}

public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
switch (lhs.type, rhs.type) {
case (.Array, .Array):
return lhs.arrayIndex < rhs.arrayIndex
case (.Dictionary, .Dictionary):
return lhs.dictionaryIndex < rhs.dictionaryIndex
default:
return false
}
}

public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
switch (lhs.type, rhs.type) {
case (.Array, .Array):
return lhs.arrayIndex <= rhs.arrayIndex
case (.Dictionary, .Dictionary):
return lhs.dictionaryIndex <= rhs.dictionaryIndex
default:
return false
}
}

public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
switch (lhs.type, rhs.type) {
case (.Array, .Array):
return lhs.arrayIndex >= rhs.arrayIndex
case (.Dictionary, .Dictionary):
return lhs.dictionaryIndex >= rhs.dictionaryIndex
default:
return false
}
}

public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool {
switch (lhs.type, rhs.type) {
case (.Array, .Array):
return lhs.arrayIndex > rhs.arrayIndex
case (.Dictionary, .Dictionary):
return lhs.dictionaryIndex > rhs.dictionaryIndex
default:
return false
}
}

源码学习体会

我从2014年6月,Apple在WWDC上公布Swift起就开始关注这门现代的编程语言,关注它的每一个变化,尝试用Swift来解决问题,开发App,并享受编程了乐趣。

可惜由于编程能力欠佳,我对这门语言的理解依然有限,使用Swift编写出的代码仍然浓浓的Objective-C的味道。可以说自己是在使用Swift的语法来编写Objective-C。

通过对 SwiftyJSON 这样的优秀的开源代码的学习,我学到了很多。

  1. 我学会了要在在恰当地使用函数式编程,这样写出的代码简洁、高效并且有良好的可读性,SwiftyJSON 中实现DictionaryLiteralConvertible、实现let name = json[9]["list"]["person"]["name"]访问数据等都是出色的实践。
  2. 对SwiftyJSON 代码的分析也加深了我对 Swift “面向协议编程”、“从协议出发编程”,值语义等概念的理解;

在今后的实践中,我也要有意识得运用这些编程技巧,写出更加Swifty的代码,更合理、高效地解决编程中遇到的问题。