掌上链家iOS组件化(总结)

掌上链家iOS组件化

组件化目标

我们组件化的目标就是每一个组件都是一个私有的仓库,都是一个pod,壳工程用的时候直接pod install

每个组件可以看做一个小的app,里面使用MVC架构

不参考原来的逻辑,在写这个模块的时候和产品确认逻辑,把重要的模块全部重写,你在抽离这个模块的时候,要跟其他模块完全解耦,在你的模块中不能调用其他模块的任何代码,你要保证你的模块在你的测试App(你的测试壳工程)里能够完全运行起来,就要达到这个目标

组件内结构.png

准备工作:

1、制定代码规范,类前缀,文件前缀,缩进,注释规范

2、建议在开始的时候大家都在一个工程里面就行开发,因为在开发重构的过程中,代码在不断的变化相当于每一个私有仓库版本号在不断的进行更新,每新增一些代码都必须重新打个包工作量比较大

3、通过分文件夹的方式每个组件都有一个对应的文件夹, 对应组件的源码都在对应组件文件夹下面开发。需要保证的是你在组件里面的代码,不会引用到其他组件的代码,不会import其他组件的文件,也不会使用其他组件的方法

4、开发完以后再给对应的组件编写podspec文件,再把它弄到仓库里面,再给它更新版本号,再把最终的版本号填写到壳工程里面去,创建整个壳工程

抽离公共基础组件:

跨产品使用的东西,和业务没关系和产品也是没关系的,log日志,网络,地图服务,社会化分享,文件服务,category扩展

产品相关的基础库:

和当前公司产品强相关的,包括通用的base(BaseViewController等),自定义的HUD、图片选择器、用户行为统计库、应用基本配置信息(token、签名、cookie、加解密相关)以及其他自定义的UI基础组件 (和产品强相关和具体业务不相关)

把以上公共基础组件、链家产品基础库进行剥离,所谓的剥离就是把以上的代码变成一个个的私有库,以源码的形式打成一个pod,使用的时候pod install 怎么使用Cocoa Pod 发布自己的SDK?

组件内使用MVC架构的调用规则

对于比较复杂的逻辑:

1、可以加一个logic层,把一些复杂的业务逻辑放到里面,所谓的复杂业务逻辑 就可能会在你的业务里面保存一些状态机或者处理复杂的业务情况

2、比如登录模块,可能要处理一些状态机,还要处理各种异常的情况,还有注册,可能还有第三方登录,相对来说登录还是比较复杂的,所以我在里面又加了一层Logic层,专门处理一些复杂的业务逻辑,对于复杂的组件还是很有必要的,它可以把那些ViewController的代码给解放出来

模块内使用MVC架构的调用规则.png

1、UI层只能调用Resource、Logic、Model的代码,不能在Logic层反调UI层的代码,如果Logic层想要调用UI层代码,只能通过通知/Block的方式回调

2、Service层只做service相关的动作,它只能够请求网络,然后把请求回来的数据返回,不能去调用Logic层的任何东西,更不能去调UI层的东西

3、Model层就完全是Model,完全解耦,没任何依赖,Model里面不能有任何的业务代码,不能有任何的业务逻辑

Service层:

处理该组件网络接口的请求、以及服务器返回的数据转model的工作

UI层:

页面相关的事务处理就放在对应的ViewController里面,界面相关的全部放在storyboard里面去做

组件管理中心(LJComponentManager)

组件之间的交互无外乎就两个东西:下面这两个问题解决了,组件与组件之间的通讯问题就解决了

1、组件之间页面之间的跳转

2、组件之间服务的调用

组件之间跳转:

可以采用openUrl的方式,就是schemes的方式,因为苹果本身是支持scheme的,只要传一个scheme一个类似于HTTP后面是名字然后?后面跟参数

都在组件管理中心存在的问题?

如果所有的跳转的都在组件管理中心的router函数来跳转的话,如果有30个组件,每个组件跳转10个界面,router函数就300多行了,函数超过50行,100行就不是一个好的编程方式

解决方案:URL导航去中心化
URL导航去中心化.png

1、去中心化,页面的跳转交给每个组件自己去做,业务组件注册自己能够处理的URL,并且返回对应的VC

2、如果某个URL你这个组件无法处理,或者是参数不对,就直接返回nil

3、ComponentManager这边什么都不做,ComponentManager做的事情就是一个中间件,就是做一个转发,ComponentManager接到一个URL,它把这个URL传递给对应的组件,让这个组件自己去查询这个URL对应的VC

4、这个组件查询到VC过后,把VC返回给ComponentManager,ComponentManager拿到VC过后,它再去调用LJNavigator去进行路由跳转,至于是push也好,present也好就根据具体的参数去控制,跳转根据参数解析

组件之间服务调用:

组件之间服务调用.png
服务之间的调用也很简单,比如说我在某个组件想要调用首页组件的某一个服务,那我只要传递首页组件协议的名称,通过传入协议名的方式去调用它某一个对外暴露的接口就可以了

1、每个组件提供对外的接口文件,把所有需要对外暴露的接口,都写在接口文件里面

2、组件实现和组件管理中心之间增加了一层协议接口层(LJServiceProtocol集合)

接口文件的设计原则:

1、最复杂的二手房组件对外提供的接口也就5个左右,也就查询二手房,搜索,跳转,一个组件对外提供的接口我认为是不会超过10个的,如果一个组件对外提供的服务接口超过10个,那我就建议你把你的这个组件拆分成两个组件,或者更多组件

2、不建议使用动态的方法(runtime)实现,用这种对外提供接口,已经能够满足要求了

方案对比:

1、这种方案比较直观,比较容易接受,你只要把对外服务集合的接口拉下来,就可以看到所有组件对外暴露哪些接口

2、动态化的方式可能就不那么直观,直接把string变成类名,要看一下这个string对应哪个类,这个类提供了哪些方法,动态性高会失去一部分可读性

对外服务协议集合

二手房组件有个SecondHandHouseProtocol,地图组件有个LJMapProtocol,搜索组件有个SearchProtocol,所有的接口文件,我把它组成一个集合叫做业务组件对外服务协议集合,所有对外服务的接口必须要有一个独立的版本仓库去管理

为什么要用单独的git仓库地址去管理?

1、一旦你对外提供的接口文件确定了,那么你这个组件就不能对这个接口进行增删改了

2、如果你要新增一个接口或者修改接口名字修改一个参数,你这个组件版本更新了一定要更新对应的协议接口文件的版本号,一旦你这个服务协议集合的某一个接口版本变化了,一定要把整个协议集合的版本进行更新

3、把整个对外服务协议集合把它抽出来,做一个大的git地址,用一个git仓库进行管理,一旦你变化组件对外提供的服务变化,就要进行版本变化,一旦版本变化就要通知所有引用协议服务集合的依赖方,让他去更新你的版本号,让他去pod update

接入点:

1、中国要加入世贸组织,怎么加入呢,你肯定是要遵守这个组织的一些协议一些规章制度,组件也一样,你这个组件想要接入组件管理中心,想要接入这套组件管理框架,你就要遵守这套组件管理的接入协议,这个协议就叫做接入点协议LJConnectorProtocol,你这个组件只要遵循了我这个协议,那么你就可以接入我这个管理中心,你就可以被我这个中心管理器发现,进行调度

2、同时这个接入点也可以遵循一个LJComponentServiceProtocol协议,这个协议就是一个对外服务的协议,所谓的对外提供的服务就是对外提供的接口都写在了这个协议里,其他组件都可以通过组件管理器进行发现调度,就是组件之间可以进行服务的调度,任何组件只要你遵循这两个协议,你就可以接入我这个组件化的架构

接入点协议.png
任何组件只要遵守了这些协议,就可以接入组件管理中心,组件管理中心在进行任务调度的时候就可以发现这个组件,进行页面跳转服务的调用

1、ComponentManager判断你这个组件能否handle这个URL

2、这个URL对应的ViewController

3、业务组件注册自己能提供的Service,返回服务实例

解耦妥协:公共Model下沉

公共Model下沉.png

Model与字典传参的优缺点:

1、使用字典优点是完全解耦,如果有几十个参数的话,要一个一个拼接字典容易出错,可读性差,接收方又要转model进行解析

2、使用Model传递的话,会有一定的耦合性,非硬编码不会出现拼写错误

组件之间传递复杂参数: 开发效率,可读性,耦合上做一个平衡

1、几百个参数传递起来非常的麻烦,我们就想看看在组件里面有哪些Model是被公共使用的,我们有三十多个组件,用到的公共Model也就十几个,能够想出来的也就房子的Model,经纪人的model,搜索的Model,用户的Model,其实也就几个Model 经常用的,为什么不把这些Model下沉到底部,把它专门做成一个podspec,把它下沉到底层库去,所有的模块都可以依赖这个公共Model

2、好处就是接口的暴露可以使用Model进行传递而不是使用字典传递,可能会打破一些封装性,打破一些组件之间的依赖,但是便利性和效率上非常快,下沉的过程中你会发现也不会太多,也就十几个 公共Model,设计好之后只会往里面加一些属性,并不会删改一些属性,变化频率不会太高,但一定会变化的,所以必须把公共Model库单独拿出来,使用podspec管理,一旦公共model的属性增删改了以后,一定要更新它的版本号,周知业务方

掌上链家组件化架构

链家组件化架构.png

1、上面的每一个小豆腐块都是单独的私有仓库,单独的podfile,单独可以去打包,打包成framework,或者打包成源码都可以

2、整个链家其实是两个app,一个是卖家APP,一个是买家APP, 有两个入口,所有的源码都是在podfile里面,最后理想的情况写一个壳,把需要的功能podfile进去,就是一个新的APP

管理器仓库

1、有一些管理器一些组件要用另一些组件也要用,用的多了给外部暴露太多的接口不好,那还不如把它做成一个单例,把这些作为单例的manager做成一个管理器库,把它下沉到下面去,这样所有上层的一些业务都可以调用它

2、而且管理器内部又可以管理他自己的一些状态,一些变量,相对而言也比较独立,扩展也比较好

3、 定位相关的,数据缓存、持久化相关的,版本管理、push管理、城市管理、 广告的管理、联系人的管理,单例上层调用起来非常方便

4、在登录组件中会有一个LoginManager,我们把它下沉到底部去了,LoginManager管理登录相关的底层信息,比如登录的状态,当前在登录状态,还是非登录状态,还是注册状态,这个状态机我们是下沉管理的,我们把一个LoginManager放到下面去,其他组件如果需要用到登录相关的状态,比如点赞,发评论,个人设置需要知道登录的状态机,只要调用LoginManager单例的状态就可以了,LoginManager如果需要监听登录成功的事件,只要注册manager里面登录的一些回调, 或者登录的一些通知,就可以在组件里面监听登录的事件

URL动态部署和容错处理

URL动态部署和容错处理.png

使用场景:

由于一些政策原因,不能显示之前跳转的页面,要重定向一些页面的跳转

使用方法:

程序启动时从服务器下载Json格式的Scheme集合,?之前是原来跳转的界面,?之后是新跳转的界面,解析Url时进行处理

遇到的一些问题

切换环境的页面怎么做?

我们当时在首页用三根手指点击四次会弹出一个页面,页面上可以选择测试环境,生产环境,然后切换之后会提示你,环境已切换需要重新启动,就是你设置之后在文件里面写一个值,然后根据这个值去判断是生成环境还是测试环境,启动时会优先读值,然后网络环境跟着这个值走,发布的时候要把这个入口给关掉,避免用户切换到测试环境

组件的拆分粒度就是你组件化的抽象能力

比如最初设计我们二手房,新房,学区房模块的时候,最初是把房子和搜索做在一起的,我们发现搜索在二手房也有,新房里面也有,学区房里面也有,而且搜索的内容差不多,都是传入一定的参数,返回的都是房子列表,房子列表的数据结构其实都是差不多 ,后来我们就想能不能把搜索这边再抽出来做一个搜索的组件,如果你在房子的组件里面要用到搜索相关的功能,去调用它的接口去传相应的参数给它就可以了

组件的scheme是如何管理的?

每一个组件管理自己的scheme,管理自己的URL, 它里面有一个函数叫做openUrl,在这个openUrl函数里面其实就是一个if else的结构,每个组件可能有五六个甚至十几个if else ,所有它能够handle的scheme都在这个组件内部进行管理的

业务code是放在网络层处理,还是在业务层处理?

1、业务的code肯定是放在业务的code里面处理的,网络层不会处理任何的业务,组件内部不是有一个Service吗?Service层只做发送请求,服务端返回json文件,把json简单的抛给Logic层处理去做json转model的操作,Service层不会把某一个字段解析出来,看是什么type要不要做一些特殊的处理,网络层只做网络层应该做的事,只做收发信令,只做回调这个事情,至于业务是上层的logic层在做的

不同组件中,资源重复怎么处理?

如果把资源文件下沉,由于很多组件的资源在不停的变化,一同去维护资源的版本号非常的麻烦,最后我们决定把每一个资源拆分到自己的组件里面去,虽然会造成各个组件里面一些资源的重复,但是这些资源浪费是在合理的范围之内,也没有太大,像一些启动图片,引导图片完全可以放到壳工程里面去,建议每个组件里面要有自己的资源

同一个工程多个组件如何协同开发?

组件化设计建议:

1、组件之间如果用到相同的cell,建议多份拷贝,不要下沉,在新房、二手房的房源列表里面发现其实很多cell都长得差不多 ,开始我们想要复用就把cell抽到底层的公共控件库去了,后来我们发现没有这个必要,哪怕两个组件的cell一模一样,你单独拷一份重新命名,跟着组件走,千万不要把它下沉到底部去,一旦你开了这个先例,你会发现以后往下沉的东西越来越多,慢慢的就失去了组件的意义了,所有东西都往下沉就变成原来的MVC架构了

2、非常不建议在项目中有common控件和common字样,任何组件一定是分工明确功能单一的,如果一开始有common,慢慢的越来越多的垃圾往里面放,到最后就没法维护了千万不要有common组件

参考资料:

掌上链家iOS端组件化分享

demo示例

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