iOS开发的数据层架构思考,粗稿

简单App的开发通常不需要考虑数据层的问题,或者说考虑得可以比较粗糙。比如一个展示产品信息的页面,通常我们认为只需要在网络正常的情况使用,可以认为是一个非常接近于B-S模式的C-S模式。
而随着App功能的逐渐迭代进化,以及产品方对于产品的思考逐渐深入,数据维护需求迟早会提上议事日程。与其一开始草草地写一个App交差,还不如一开始就考虑清楚如何设计好一个扩展能力强,又不至于功能过剩的架构。
A. 网络数据
网络数据是App最重要的数据来源。现在App开发者似乎已经可以不动脑筋地一上来就拖进AFNetworking之类的功能库,因为这类功能库俨然成为业界的新标准。其实使用这种库我是百分百得赞成,因为前人已经造好了轮子,除非你真有更多闲余时间,或者你老板觉得研究功能模块也会给你发工资,那你可以尝试全部采用自写模块。
首先思考使用AFNetworking给你带来了什么益处。首先你不需要再关注HTTP的更多细节,包括数据缓存,https认证,摘要认证,业务上你不用考虑请求的调度问题,额外的你可能还能获得数据序列化的便利性。这些可能是你当下没有想到,但未来App中一定会需要用到的功能。
然后我们来思考我们需要注意什么。

  1. 你的App和服务器的通讯数据。
    典型的用JSON。如果你的App用了XML等目前非主流的传输协议,而且短期内没有精力改,那也没关系。但是劝你一定要遵守RESTful的API的设计规范。
    很多程序员以为数据传输这种事不就是从服务器拉一个东西来,怎么传都行,并没什么太大区别。假如你的App只准备维护一年,那是没太大区别。这里举个简单例子来说明乱定义API的危害:
    例子1: 我们App要获得一个首页数据,最初程序员随便取了个接口名叫
    http://api.myapp.com/getData
    ,然后第二个页面出现了,是个人信息页,后台程序员说,那还取这个名字吧,传递的参数换下就行。更妙的是,参数还是用POST方式传输的
http://api.myapp.com/getData
POST data: apiName=myProfile

如果产品哪一天说你的接口数据太大,要做接口缓存,而好死不死的你的参数在POST里面,那很遗憾,HTTP的缓存就不要想了。你需要自己实现一套类似HTTP的ETag或者Modified-Since逻辑,还不能保证比AFNetworking写的好,这不是给自己找事吗?

例子2: 很多开发人员写JSON极其随意,常见如:

{"data": {
  "imgurl":  "http://api.myapp.com/link",
  "title": "My List",
}}

请问这个data是什么,对于服务器和客户端来说,对应任何一个实际的数据结构吗?哪怕想把几个不相干的数据结构打包成一个新节点,也请取一个确定的名字,data这种名字到最后就是客户端根本不能预知到内部的数据类型,也许还得加一个type来区别,那写个data只是为了给节点凑个名字吗?
更常见的是一物多名,比如一个用户信息,一会叫userProfile。一会叫userInfo。这种设计最后的结果必然导致开发人员不看文档根本不能了解接口的含义,而遗憾的是没有文档又是很多开发组的日常状态。
B 数据模型
客户端网络数据通常会被转换成数据结构,供各业务层使用。设计数据模型很多人觉得是个简单体力活,其实也不尽然。我觉得最理想的数据模型,应当由客户端和服务器开发人员协商后确定,切勿由一端主导。
还是以用户信息为例:产品初期我们可能设想的用户信息也就没几样:

@interface UserModel : NSObject
@property (nonatomic, strong) NSString *userId;
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *desc;
@property (nonatomic, strong) NSString *iconUrl;
@end

先还是想想userId的数据类型吧。不要服务器定了个长整型结果客户端是个字符,后患无穷!
随着业务的复杂化,UserModel东西越来越多了!也许该包含子对象了。

@interface UserModel : NSObject
@property (nonatomic, strong) NSString *userId;
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, strong) NSString *desc;
@property (nonatomic, strong) NSString *iconUrl;
@property (nonatomic, strong) UserLevel *userLevel;
@property (nonatomic, strong) UserProfile *userProfile;
@property (nonatomic, strong) Relation *userRelation;
@end

什么时候可以增加子对象了?其实并不难确定:

  1. 服务端数据表发现用一张表存储用户信息已经不合适,需要分表时;或者数据来源并不相同时
  2. 子对象有独立使用的应用场景时
    第一点其实很好判断。Relation这个结构假如包含的是这个用户和接口中“我”的关系信息,对客户端来说绝不可能是和其他这个用户固有属性是在一个表内,或者说产生数据逻辑差异很大。这样还是建议客户端将这块数据独立命名。当然,在网络传输时也应当独立开来。
    tips:给每一个节点增加一种“未获得”状态。
    这个技巧的好处是,哪怕来自服务器的JSON数据是不完整的,你也能轻易的知道究竟是数据缺失,还是数据本身是个空。对于普通的页面也许并无明显价值,可如果信息是来自多个接口呢?比如从接口1获得了UserModel的一部分数据,从接口2又补充了一部分,最终形成了完整的UserModel,对于有缓存需求的App这是一种非常理想的节约流量的模式。

数据模型的建立,比较理想的是采用JSON->Model的工具,如Mantle,JsonModel等,这类工具其实并不难写,但还是建议用一些较成熟的工具。
B. 表现层模型
这个非常好理解,MVVM开发模型中View Model这一层。我还是强烈建议表现层还是要尽量接近数据层模型,最好是它的超集。切记不要另外定义一个类似UserModel2,最糟糕的一种是里面还都是Dictionary,看似一个万能结构,后人维护的时候会有多恐怖!

C. 客户端持久化
除了上面说到的网络层缓存,还有一种App常用的是和业务相关的持久化。比如,启动的时候想快速展示之前关闭的页面;把音乐,书籍,图片等保存到客户端等。这些业务相关的持久化数据需要非常谨慎的设计。
我们通常对App的持久化问几个问题,来推断其类型:

  1. 保存的数据量多大?k级别还是m级别
  2. 是否需要本地索引
  3. 数据类型,二进制还是文本?
  4. 读写的频率多大

第一个问题涉及到存储的方式,显然m级别的数据不适合直接在数据库存储。第二个问题是是否使用数据库存储的关键,而且提出这个问题不能基于当下,要为未来预留空间。第三个问题在已经确定使用数据库存储时,需要考虑是直接存入数据库,还是采用文件+数据库索引的模式。第四个问题,如果高频率的读写信息,信息量又不小,数据库不不堪重负,必须事先考虑增加内存缓冲层。

看过不少App,什么数据都往NSUserDefaults里面塞。也只能说我们走大运,苹果为我们做的好,起码NSUserDefaults的效率真是理想,如果同样的粗野作风去操作sqlite,一定会让你想死。

再谈谈数据模型与持久化模型的统一。用过CoreData的童鞋会明白,要存储数据,需要建立NSManagedObject模型。已经有的数据Model并不能直接转换成NSManagedObject的子类,要存储一种类型要建立两次模型,活活累死人的节奏。
Realm提供了RLMObject对象,同时支持JSON的直接转换,似乎比JSONModel或者MTLModel更富有天然的优势。但RLMObject仍然有很多不完善的功能。而且Realm的不开源也让人不太放心。
最理想的方式是,表现层可以不关注数据的来源(网络来的?客户端数据库?),可以直接使用的Model结构。数据逻辑有Model层自己维护,包括并不限于支持自动更新,差量更新,懒加载技术等等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容