Xinbao Dong, An iOS Engineer

从GPA.ZJU谈iOS开发

2016.03.22

可能很少有人会记得GPA.ZJU了吧,这个应用于2013年10月24日正式在AppStore上架,经历了两年漫长的岁月存活至今。在此期间,迭代过两个大版本更新(最新的3.0版本正在上架审核当中),也经历过几次重大的崩溃事件。最长的一次崩溃延续至今,因为自己的个人原因一直没能更新,我感到非常抱歉。

GPA.ZJU起初最主要的功能目标是实现新成绩的推送通知。在每次考试周结束之后,总有一些人会每时每刻都在刷成绩(这无关于学霸与学渣或者学酥),这是一个非常痛苦的事情。作为一个坚信“能用代码解决的事情决不手工做”的开发者,我选择编写一个程序来缓解这一痛苦。因为当时正巧换了iPhone,于是我毅然决然踏上了iOS开发这一条不归路。

两个月之后,一个最初的,非常简陋的版本产生了。原本打算就自娱自乐,不过后来秉着助人为乐的精神自掏了开发者年费上架了AppStore。期间收获了很多鼓励,也有一些质疑。两年的开发积淀让我从一个歌楼上的少年变成了客舟中的壮年。虽坎坷,但也让我有了一些经验与感悟,我希望就这个机会分享给大家。本文内容较为基础,也没有较好的组织,想说什么就说什么。

在开始阅读本文之前,我希望你们能够下载一下iOS版本的GPA.ZJU,好知道我在说什么……

在接下来的文字中,我将从三个问题入手——

一、如何写一个GPA.ZJU
二、如何入门iOS开发
三、给你们讲个故事

一、如何写一个GPA.ZJU

GPA.ZJU分成两个部分,iOS端与服务器端。iOS端承载数据展示、用户交互;服务器端负责数据交换存储、与教务网通信。

1. 基本思路

首先,教务网一定不会提供接口供你调用,你所能做的只能是自己构造HTTP Post请求去请求教务网的数据,然后对教务网数据做解析即可。

这是一个非常简单的过程,你只要在登陆教务网的时候打开审查元素中的网络选项,点击登录你就可以看到如下图类似的Post请求。

Post请求

在代码中构造类似的请求,发送成功后就可以成功拿到cookie以及服务器响应的数据(HTML代码)。浙大教务网的验证码有个比较严重的Bug,当你在构造的请求中删去这个字段,就不再需要验证码就可以直接登录,这大大降低了开发难度。

登录成功后,采用同样的方式就可以获取到成绩、考试信息。采用正则或者网上现有的Parse开源库对数据进行解析即可生成所需要的数据。

这里需要注意的一点是 _VIEWSTATE 字段,教务网用这一参数跟踪页面切换时表单中各个值的变化。你需要在POST请求中把当前页面中 _VIEWSTATE 值解析出,然后放到新的表单数据(Form Data)中发送请求。关于 _VIEWSTATE 的官方阐述在这里可以找到。

对于Mac用户,有一个很好用的软件 Paw 推荐给你们。Paw 可以非常方便地构造请求,非常易于接口调试。

2. 框架结构设计

在三个大版本更新中,1.0、2.0我采用了图一的结构,3.0采用了图二的结构。

框架1

框架2

很明显,对于(经常有一些莫名其妙变动而且不规范的)教务网来说,图二的结构更加适合。如果发生了错误,开发者只要在服务器上修改代码重新部署即可,不再需要修改App代码。毕竟,App修改、提交审核、审核通过是一个非常漫长的过程,如果顺利的话大概需要一周多;申请被驳回的情况下时间会成倍增长。当然,还有一些其他手段能够在紧急情况下,利用Objective-C运行时的特性修改iOS app的代码以达到Hot Patch的目的,这一点在之后会有简单的介绍。

3. 技术选型

在3.0之后,我的目标是把GPA.ZJU打造成一个永久可用的产品(毕竟我已经是大四狗了)。就算有一天我不再花大量时间维护代码了,一旦有严重的Bug,我依然能够在很短的时间内修复。除此之外,因为这是一个非盈利的项目,服务器的选择也是非常重要。

在这里我要安利一个平台——LeanCloud。“LeanCloud是加速应用开发的一站式解决方案,专注于为应用开发者提供一流的工具、平台和服务。”它主要提供数据存储 、实时消息和推送、统计分析的服务。在功能上,LeanCloud和国外的Parse非常类似,不过近段时间推出的云引擎功能(支持Python、Node.js)可以满足大部分你对于服务器的基础需求,详见官网

也就是说,你可以免费或者以非常低廉的价格获取一个在沙盒中的服务器,如果你愿意的话,你不需要写任何服务器的代码就可以与服务器进行交互,或者甚至直接修改数据库内容(不建议)。鉴于LeanCloud这么多优秀的特性(我就不说是因为免费了),我选择了LeanCloud作为GPA.ZJU的后端服务器,用node.js配合express框架写了接口。

之所以上文中不建议直接修改数据库的原因是:如果这样做的话,App与LeanCloud之间的耦合度太高。如果今后LeanCloud倒闭了(Parse不就要关闭了嘛),你的App想要迁出LeanCloud,那么付出的成本将是巨大的。

App端只要封装一下接口调用即可,万一需要切换服务器了,只需要修改一下服务器地址即可。

4. iOS端方案

4.0 最新3.0版本的App设计理念是报纸风格,以黑、白两个类型的颜色填充所有内容。这个理念是我公司(Catch摄影)的设计总监Messizon提出的,里面的设计则主要是我完成的。毕竟我是一个程序员嘛,如果你有觉得不好看或者不习惯的地方,请多多容忍,或者到App中进行吐槽。

4.1 整个App遵循MVC架构。你不了解MVC?MVC的框架把应用分成Model、View、Controller三个部分。Model主要负责给ViewController提供数据,给ViewController存储数据提供接口;View主要负责响应与业务无关的事件(动画、点击反馈),界面元素的表达;Controller主要管理ViewContainer的生命周期、负责生成所有View实例并放入ViewContainer、监听来自View与业务相关的事件,与Model合作完成相对应的业务。

4.2 用CocoaPods管理第三方库。CocoaPods是一个非常流行的依赖管理工具,让你非常简单地集成、更新第三方库,详见官网

4.3 整个项目目录结构如下:

Macro放一些define类似的文件;Category放类的Category;Vender放一些CocoaPods中没有的第三方库;Helper放一些动画或者其他东文件;UI中放Storyboard;Layout放一些布局文件,比如UICollectionView要用到的layout;Support放图片和其他文件。

4.4 采用Storyboard结合代码的方式写UI。“采用Storyboard、Xib、代码这三种方式中的哪种写View”是一个永远在争论,永远得不到答案的问题。不得不说,Storyboard作为Apple推荐的方式,确实给我减轻了不少工作量。但是Storyboard有一个弊端,多人协作的时候,如果同时修改Storyboard容易产生冲突。我的建议是将应用的UI按照功能模块分成几个不同的Storyboard,每个Storyboard各司其职。如果几个Storyboard中有一些共用的View,可以考虑将其以xib的形式放在View目录中。除此之外,我个人建议轻量级的Storyboard,也就是在Storyboard中,ViewController之间的复杂切换在代码中实现,而不是在Storyboard中拖segue,这样能够让Storyboard变得更加清楚(不会出现漫天的线),同时也让页面切换时的判断更加简单。一些更加简单的View,我就直接在代码里实现了。

4.5 整个App以一个NavigationController贯穿始终,我修改了push、pop transition的动画,让他成为现在的效果。首页右上角的小方块放在了NavigationController的view上方,小方块的实现是利用了iOS 7新推出的UIKitDynamics,介绍可以看此博客

4.6 热部署方案

GPA.ZJU采用了JSPatch。“JSPatch 是一个开源项目,只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何Objective-C 的原生接口,替换任意 Objective-C 原生方法。”也就是说,万一线上的App出现了什么Bug,我可以注入一段代码来替换原生方法达到修复bug的目的。3.0版本是昨天上线的,因为上线测试没有充分,导致出现了通识数据少了一个类目的情况,我通过了JSPatch成功修复了这个Bug。

5. 服务器端方案

我用了Node.js的Express框架写了GPA.ZJU的后端,并部署在了LeanCloud的云引擎中。对于一些小型的应用来说,LeanCloud确实给了我很多便捷(此处应该给我发广告费)。从此以后,服务器维护变得非常简单。

服务器实现了以下几个接口:

/auth: 登录认证

/exam: 获取考试信息

/grade: 获取成绩信息

/mix: 获取考试信息与成绩信息混合之后的信息(主要是给App用)

/push: 查看、修改推送状态

获取登录、考试成绩信息原理在最开始的介绍中已经提及,所以不再赘述。服务器在获取完信息时,对数据进行解析,生成一个JSON格式的字符串返回给App。App根据需要将其转化为模块进行调用即可。

二、如何入门iOS开发

我个人推荐的步骤是:

  1. 阅读Objective-C(或Swift)的文档,了解基本语法。
  2. 了解相关的Cocoa框架的知识,推荐一本书《精通iOS开发(第7版)》,一套视频《斯坦福大学公开课:iOS 7应用开发》(网易公开课)。
  3. 选择一个自己想要做的项目,从项目入手着手开始开发。Google会告诉你所有问题的答案。我的第一个项目是GPA.ZJU,当初花了一个暑假的时间做出了它,经常出现bug(写得跟shi一样),不过这种从0到1的过程还是让我感觉非常有成就感的。
  4. 阅读一个完整的项目的实现。Github上有很多完整的开源项目,你可以按照Star数从上到下挑一个自己喜欢的。

我觉得有一些我觉得重要的Tips:

  1. 别重复造轮子。或者说,造轮子之前你必须先浏览一些相同功能的开源库的代码,了解他们的实现过程。否则,有可能你写出来的东西,根本没有使用价值。
  2. 读一些iOS相关的博客,他们对一个问题的认识是非常深入的,可以学到很多。
  3. 关注一些开发者的微博账号,他们每天会转好玩的、有用的、实在的东西。
  4. 当你有了一定的水平之后,不要接太多的项目或者外包。这会占用你大量的时间,以至于让你没有心思研究一些新技术,阻碍你水平的进步。要记住,你是一个程序员,而不是一个熟练工。

一些资源:

Github合集:https://github.com/Aufree/trip-to-iOS

一些微博:@iOS程序犭袁@hangcom2010 @我就叫Sunny怎么了@移动开发小冉 @叶孤城_ @唐巧_boy @onevcat @周楷雯Kevin@雷纯锋2011@汤圣罡 @KITTEN-YANG @程序媛念茜

一些博客:唐巧OneV's Dencasatwybeyondvincent

对了,GPA.ZJU的iOS代码已经开源在了我的Github中,我删去了LeanCloud的一些信息。如果有什么问题,请给我发邮件或者提个issue。

三、给你们讲个故事

曾经有一个少年,在很小的时候就非常喜欢电脑。
他干过很多事情。比如说初中电脑课上,为了防止别人跟他抢网速,他把其他人的电脑弄蓝屏。当然,利用的漏洞也是非常古老、简单的。
他开着挖掘机挖过网站的漏洞,但是由于技术不精没拿到开门的钥匙。
他还给喜欢的女孩子写过一个VB的程序,加了一个密码,发给了那个女孩子。
女孩子非常感动,最后,没有选择他。

就这样折腾着,2012年的夏天,他进入了大学,很迷茫。
由于高中搞的是物理竞赛,在算法这部分他拼不过搞OI的同学。在高中的时候被称为大神的他并没有掌握一两种研究比较深入的技术,他很苦恼。
日常的状态是全寝室开黑打LOL,水水代码,水水作业,考前通宵。
曾体验过没复习去考微积分,也曾体验过一周时间自学完整本线代书。
嗯,结果当然是惨跪。不至于挂科,但是成绩很难看。
就这么糟糕地,他如愿以偿进入了CS相关专业,开始了码农之旅。
码农之旅还算顺利,也许是因为有一点点聪明。

大一暑假那年,他因为一个需求开始学起了iOS开发。他觉得,能让自己写的东西在手机上跑是一件非常幸福的事情。
事实证明,真的很幸福。
如果说人生有诸多后悔,那么学习iOS开发始终不属于这个集合。
在那之后,他做了很多项目,接过一些外包,参加过一些比赛,获得过一些荣誉与奖励。
他加入了一家创业公司,跟着公司一起成长,目前公司已经到了A轮。
创始人很靠谱,在交流的过程中,他的贪玩个性有一些改善,他也明白什么叫责任。

小的时候,他像所有人一样,总会问自己“将来是要去清华还是北大?”
不过对于一件事情来说,他始终没有改变——“出国”。

朋友问:
“你毕业之后准备干啥?”
“出国。”
“考了托福吗?”
“……”
“GRE呢?”
“……”
“绩点咋样?”
“就你话多!”
这样的对话一直延续到15年9月份……
那时候,他终于领悟到英语的难度。

如果说大学四年最让他崩溃的时段是哪个?当之无愧是准备英语的这三个月。
每天早上起来背英语、晚上睡觉背英语,一天十几个小时都跟英语相关。
不知道谁还送给了他"出不了国"的压力。
于是,他很痛苦,比看一个1000行的没有注释的函数还要痛苦,比找不到bug还要痛苦。
他考了好多好多次Toefl,好多好多次GRE,每一次考前失眠到凌晨。
也算是上帝仁慈,他在12月份,GRE终于突破了320,Toefl也终于突破了100,然而口语18……
他很开心,开始申请,准备文书,填写表格,递交成绩。

他有一个一般般的绩点,有一个大于100的托福成绩,有一个大于320的GRE成绩,有一段水水的小科研,有过一些比赛的奖励,有一段创业经历,有一技之长。
他以为他要走向人生巅峰了……
然而,现实还是很残酷的,他到现在(2016年3月5日)都还没收到Admission。
嗯,他还处于失学状态。
他开始反思自己的申请,反思自己的成绩,反思自己的文书。
为什么,GPA不能再高一些呢!
为什么,之前不多练习下口语呢!
为什么,文书里这么自信的想要转方向呢!
为什么,之前不做一些科研呢!

嗯,他已经想好了,如果失学了,就给自己放个假,出去看看这个世界。
也许有一天,这个世界的好些角落都有他的脚印吧……


主要是为了告诉大家,如果想要申到好学校,早日准备英语(口语至少22),成绩一定要好,有一些科研,有一些特长。(按先后顺序排序)
当你做到了,Offer自然来了。

Comments
Write a Comment
  • evelynn reply

    我很怀念你的app,在求是潮mobile还没有做到现在这么好的时候,你的zju.gpa几乎是那时候最棒的查看绩点的app。

    在第一次改版之前,清新的ui设计和风格很抓眼。我还记得有一次推送崩溃,你还特意推了一条道歉的banner。那时候觉得,这个开发者好暖心啊。

    后来的改版,可以说都是顶着很多压力了。尤其是有了很多公众号也开使可以查询成绩甚至老师评价的时候,求是潮开始不断完善他们的app的时候。

    我看到那次更新加入了统计分类通识的总学分。那个更新真是太棒了QAQ!非毕业学生当时都没法在教务网查看学分统计,那个更新我要吹一辈子!

    总之特喜欢你的这个作品。我其实当时默默看你的个人主页很久。虽然我也不知道为什么。

    哈,如果有个打赏通道就好了,我一定会打赏的!

    但愿你现在已经是有书可以读的状态了。祝你未来更好。

    来自一个曾经非常喜欢你的app作品的曾经的zjuer//貌似知道这个app的人都已经毕业了吧哈哈哈哈哈

    另:托福口语keep talking很关键

    • Xinbao Dong reply

      @evelynn

      在多年后的现在还能收到这样的回复,真的是一件非常暖心的事情。

      也许事情本事都已经在岁月中磨损,但是一些记忆却散落在世界各地,这是我始料未及的惊喜。

      不用担心,我已经有书读了,并且已经读过啦!

      打赏就不必啦,也祝你未来更好!

      最后,套用下『头号玩家』中的格式:

      And thanks. Thanks for using my app.

      P.S. 我的博客域名应该改了好几个,你竟然还可以找到… 2333

      过几天应该会再改个域名,找到了一个更加适合自己的,然后就不会再改了。

      看来以后要多花一些时间来写博客了,尽管工作忙碌,也得给自己留一块小空间。