iOS电商类APP的研发

前言

本文是研发一个在线超市的电商类APP过程中,对架构的整理。


功能:

  • 1、浏览商品、购买商品、切换商店;
  • 2、查看订单、订单投诉、意见反馈;
  • 3、登陆、退出、收货地址管理;
  • 4、支付、取消订单;
  • ...

箭头的指关系 代表着 直接调用,也代表着持有引用的意思。

所有的实例都可以通过监听event,达到交流的效果。

其中Model是单例,Controller 是storyBoard 上实例,view是Controller运行时加载并初始化;

Message是工厂模式,每一个协议都实例化一个对象来解决;

Event通过封装NSNotifycation实现。

网络通讯是通过Message调用AFNetworking。

设计中遇到的问题

1,网络层请求的封装

一开始的做法是定义一个server类来处理请求,头文件中定义请求的类型,所有的网络请求都走server类,server类直接调用AFNetworking.
大致的形式如下:

Server:NSObject
{
    -(void) requestLogin;
    -(void) requestLogout;
  ....
}

这样的好处是直接调用,开发方便,逻辑也比较简单。
坏处是,所有的代码都写在一起,不方便维护,同时不好做统一的逻辑处理(比如说session过期等)。

现在采用的做法:
定义一个BaseMessage,封装AFNetworking的调用,还有统一的逻辑处理。
同时定义OrderMessage,CartMessage,GoodsMessage,ShopMessage等继承BaseMessage,来具体实现特定的逻辑。
这样做的好处,把逻辑分类,代码按照模块分散到每个SubMessage,方便维护。

还可以优化的地方:
现在的请求是一个Message就是一个网络请求(http),处理完之后要在Message抛出事件,来通知其他模块的信息。并且,每个Message都是相互独立的,并没有统一调度的过程。
可以新建一个MessageQueue类,来存放所有的Message请求,通过MessageQueue来调度http请求。
这样的涉及到的问题是:结果回来后,如何通知其他对象?
做法1:
当请求Message的时候,self实现一个接口,并且传入self;回调的时候,直接通过接口调用;
做法2:
请求的时候,带一个闭包参数,回调的时候直接调用闭包;
做法3:
请求之后监听事件;回调时通过事件响应;

比较理想的做法:
有controller 、 message 、msgCenter三个实例。
controller监听事件,发送message;
message实现接口,自己把自己添加进msgCenter队列;
msgcenter对BaseMessage进行处理,通过接口来查询message 里ID等详细信息;回调后,通过调用message的接口。
controller 和 message 用的是监听者模式;
msgCenter 和 message 之间用的是代理模式;
msgCenter 可以实现异步的与服务器交互,和对message 的统一处理。

2,MVC框架的实现

iOS的设计,本身就含有很多MVC的思想,比如说要实现一个自定义UITableView,就要继承UITableView,自定义delegate,与Controller的交流 是通过delegate实现。同时,一个页面就是一个controller,也要继承UIViewController。
自然而然地,在写代码的时候就会M(model)、V(View)、C(controller)的区分。
但在实际的需求中,遇到一些正常的需求的时候,如果没有设计好,也会很棘手。
比如说:首页下方的购物车模块、商品展示的模块,这些都是需要重复出现的模块,也就是需要重用的模块。如何设计购物车模块,使得购物车模块 和 持有购物车的模块(首页、子类目等)之间没有耦合,也是一个麻烦的事情。
具体的需求有几个:
1、购物车点开的时候,页面除购物车的背景要灰掉,同时购车要有上滑的动画;
2、点击购物车或者点击背景的时候,购物车弹下,同时灰色背景去除;
3、购物车中点击商品的增减,要实时反馈到页面上(首页、子类目等);
4、购物车点开之后的大小,由购物车内的物品决定,有最大高度;
5、购物车的物品减到0的时候,不消除;
....

controller与view之间的交互,controller持有view,可以 直接调用view;view要调用controller或者其他view,可以通过事件、委托等方式;
不管是事件还是委托,为的是解耦,让view与controller之间不耦合,所以切记不可在view定义一个controller的属性,然后传递controller进来。
解决方案:
view 与 controller 之间用委托(记得@property(weak),否则循环引用,内存无法释放);
view 与 view 之间用事件机制;

在interface builder可以用outlet 来做委托机制,非常方便,具体的流程和UITableView类似;

3,事件机制

没有用第三方的事件机制,用的是NSNotifycation来实现;
一开始的做法是:

#define NOTIFY_SERVER_USER_LOGIN     @"NOTIFY_SERVER_USER_LOGIN"
#define NOTIFY_UI_REQUEST_PHONE_CALL  @"NOTIFY_UI_REQUEST_PHONE_CALL"

直接define具体的协议,如果带有数据,就存到notify的userInfo;
这样在代码写了比较多时候,当改动一个notify的userInfo的时候,经常会忘记改其他的某处,而且往往记不得userInfo里面的数据格式,不便于维护;

于是在NSNotify的基础上做了一层封装:
定义一个BaseEvent,负责把event的类型转成nsnotify(类型名字@"",属性转成dict),同时把nsnotify转成event;
同时Event分成几类ErrorEvent ServerEvent UIEvent DataEvent等。
比如:

@interface UIMessageConfirmEvent : BaseEvent
 @property (nonatomic , copy) NSString* message;
@end

发送事件的时候,可以直接在event类型里面给message赋值;

开发中的原则

  • 1,不要有很大的文件,除非很少改动;(大文件,不方便维护和开发)
  • 2,一个函数尽量只做一个功能,如果有多个地方调用,要保证调用的意义是相同的;(尽量不要在调用参数中带默认参数,或者在复杂调用中带flag来标示这次调用的含义)
  • 3,调用时的参数检查,一般由被调用的函数检查参数;如果涉及到参数不对时,需要有相应的逻辑操作,比如弹出提示框等的,尽量由调用者来检查参数;

开发中的问题

问题1:model要不要监听事件?

目前,Model需要被改变的时候是:
1、viewController请求数据时候;
2、message发生变化的时候;(比如说登陆、注销、商店切换)

坏处:message处有各个model的代码。
比如说切换商店后,就有这些变换;

       [self lyPostNotification:NOTIFY_INDEX_DATA];
       [[CartModel instance] onShopChange];        [[CategoryDetailModel instance] onShopChange];
       [[ServiceModel instance] onShopChange];

登出后,需要把这些Model清空;

   [self lyPostNotification:NOTIFY_USER_LOGOUT];
   [[CartModel instance] clearCache];    [[AddressModel instance] clearCache];    [[UserModel instance] clearCache];
   [[OrderModel instance] clearCache];

如果新增model比如说新添加的serviceModel,需要在message处添加相应的逻辑代码;

还有的问题是,Model的clearCache等函数被message调用,耦合性特别强;相当于做了事情A(登出),接着马上做事情B(清空数据),而且是直接调用,以后修改起来很容易犯错误。

设想:
如果model监听事件的话;
那么model只需监听各个事件,然后再写处理函数;
model的逻辑都聚合在自身;

带来的后果是,model稍微变大。多个事件,和多个处理函数。
不过这个如果是message处来调用,处理函数还是一样要写,只是多了事件处理监听的代码。
model的逻辑是聚合了,但是注销的逻辑分散了,看不出来注销完干了什么事情。这个问题好像也不是问题,因为可以查看事件的监听者,看到监听了注销事件的model。
有一处代码,集合了所有的model instance。所有的model都必须先初始化。否则无法监听事件。

问题2:调用message的时候,message从各个model获取了数据,那么,谁来做参数检查?

1,谁调用(不同层次之间),谁负责检查参数;比如orderMessage需要用户登录,那么调用orderMessage的时候,就必须保证已经登录。(因为message和model不在同一层次)
2,如果是检查的内容是自身内容,那么由自己负责。

问题3:从页面A (ControllerA) 跳到 页面B(ControllerB),用户的操作数据(比如说留言),怎么合适地从A传到B?


这个是1.5版本架构具体的实现,参考两个概念MVP MVVM。
让显示逻辑和业务逻辑有所分开,是比较重要的。
待完成微信端的开发,再来写一版详细的改动日志。 (然而不可能)

附录

蘑菇街的IM 网络层:

API center 负责管理、注册所有的API;接受服务器数据(可以分离连接代买,仅提供接受数据的接口),调用解析接口,回调API;超时处理;有自己的线程。
有一个SuperAPI 还有一个 APIProtocal
superAPI 是所有API的基类,负责注册request和respone、timeout、保存返回闭包、打包数据(调用protocal)、发送数据。
APIProtocal封装了协议的解析、打包。
controller调用实现了Protocal的superAPI子类(childAPI),并且把回调函数的闭包传递进去,放在superAPI。
childAPI实现了APIProtocal 从而对服务器返回的数据解析,并且返回cmd、msgID。

APIcenter 有四个map(request response notify timeout)
request是请求 response是回复 timeout是超时处理 notify是服务器通知。
每一次通讯,都需要注册request、response、timeout。
消息处理完后,remove from map。(notify不需要remove)

流程如下:

1.controller 调用 childAPI的request,传入数据(object),和completion闭包。
2.childAPI->superAPI 的request统一处理流程,在center,注册request和respone、timeout、保存返回闭包、打包数据(调用protocal)、发送数据。
3.superAPI通过APIProtocol 调用childAPI的数据打包,调用center 的发送。
4.center收到tcp返回的bytes,调用protocal(在responseMap注册的API)解析。
5.解析完成清除map中的API(request、response、timeout),在主线程回调completion闭包,并且将数据传递过去。
6.controller 收到数据,存到model,显示处理。
7.如果在4过程中,timeout到了,那么直接清除request、resonse、timeout,回调timeout闭包。

总结

  • 1,一个函数尽量只做一个功能,如果有多个地方调用,要保证意义相同。(这样才能复用,不要带flag的参数,混乱)
  • 2,业务逻辑、显示逻辑的代码 集合在一起。(ReactiveCocoa)
  • 3,尽量少出现allModel、allEvent 的东西,不方便添加,多人开发会冲突;
  • 4,发现改一个东西,需要改很多处代码的时候,要考察下关系问题;

Callback block 代替delegate和event进行回调,实现业务聚合,(注意,这里不能代替某些event,比如说登出、拉取订单等,但是最好这些可以反映到model,由model发出变化事件)最合适用于只有一次event 监听,的event。
ViewController的瘦身是MVC实现的要点,用Category、业务细分然后把delegate把代码划分到对应的类。

后记

MVC、MVVM、MVP都是很不错的思想,设计模式里面更是前人经验的总结。
原本计划在做完整个APP再进行一次总结,可后来做微信端的开发,学习Angular-js花费了一大块的时间。再后来,就喜欢玩其他东西了。

培养一些兴趣,这些兴趣不是物质的享受,而是一种能让你思考,进步的兴趣。

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

推荐阅读更多精彩内容