精华内容
下载资源
问答
  • 本文谈谈本人是如何设计一个简单的 iOS 响应式架构
  • iOS响应式架构

    2018-08-01 10:34:54
    iOS响应式架构 自2014年起,移动端的架构中开始渐渐的融入了响应式的元素。时至今日,融入响应式的架构已经在很多产品的多次迭代中经受住了考验。十年后,再回首现在这一时间点,很可能会被认为是一个新的移动端...

    iOS响应式架构


    自2014年起,移动端的架构中开始渐渐的融入了响应式的元素。时至今日,融入响应式的架构已经在很多产品的多次迭代中经受住了考验。十年后,再回首现在这一时间点,很可能会被认为是一个新的移动端架构时代的开端。很多文章中都提出了针对自己产品研究出来的一套架构,却鲜有文章单独对架构中的响应式元素进行讨论。笔者就写下了这样一篇关于iOS响应式架构的文章来阐述响应式架构的历史、响应式架构的本质、响应式架构的优点以及如何在现有架构中融入响应式架构这几个值得探讨的问题。

    First Things First

    首先,重中之重。界定一下响应式架构与响应式编程和其他架构的边界。响应式架构与FRP(响应式编程)有必然联系吗?没有的,响应式架构并不需要引入Rx等FRP范式方案。响应式架构与MVC、MVVM有必然联系吗?没有的,MVC中可以很愉快的融入响应式架构,MVVM也可以。不过,响应式架构与Immutability倒是密不可分,毕竟不可变才能“响应”。

    Immutability

    2014年Facebook开源了immutable.js,React、Flux等以immutability作为核心元素的方案,可以说是席卷了前端。这看上去是在前端的浪潮之中,Facebook提出了这一概念。事实并非如此,Immutability的概念早在面向对象编程诞生的阶段就已经有了。而Facebook的工程师是这个时代发现珍珠的人。

    这里也不打算长篇大论,简要的回答这几个问题。不可变性是啥?可变数据与不可变数据有什么区别?

    不可变性就是不可变数据具有的不可变性质,而可变数据和不可变数据的区分就在是否具有状态,可变数据具有,而不可变数据不具有。首先来思考一下这个有趣的问题,现实世界中有钟表反映时间的流逝,那在计算机世界中如何反映时间的流逝?同一数据发生了变化就可以,比如说,计时器的本质不就是同一数据一直在递增,即状态反映了时间的流逝。其实,状态是对现实世界时间流逝的隐式表现。引入了状态,也就向计算机世界引入了时间。

    那我们为什么要关心状态,状态有什么危害吗?数据本身改变其状态实际上没什么危害,而且是必需的,任何对本身状态的修改都是可预测的。而多数据共享的状态则不同,任何一方的修改对其他方来说都是不可预测的。这也就是平时编程bug的源泉。(可变数据带来的同一性问题)

    除此了共享状态,状态带来的时间,也同样带来了语句的时序问题,CPU流水线执行的读写状态的语句就变得至关重要,影响了程序的正确性。其中最令人头痛的就是线程安全问题。

    Immutable Model Layer in iOS

    状态带来这两点危害,在GUI编程中尤其显著。为了缩短平时与bug争斗的时间,前端和客户端的工程师们开始了Immutable之旅。然而从编程的模型中剥离状态是不现实的,那么就从架构的一个层面上剥离状态吧,很显然,Model赢得了头奖。

    Facebook、Pinterest、 linkedin很早就有文章说明他们在这方面的尝试,并都对外输出了自己的开源方案。Facebook开源了 remodel,linkedin开源了Consistency-Manager,Pinterest开源了 Plank。而大家管App上剥离状态的这种设计叫做Immutability Model Layer。Immutability Model Layer不是一步登天的,Facebook在其文章中分享了这段心路历程。

    首先,在日常业务编写Model的时候,工作无趣而且繁重,很多工作量就是将后端提供的API转换成Model,根本不需要动脑进行抽象,这是个要解决的问题。

    其次,在平时面向对象编码过程中很容易将多个对象共享的状态混在其中一个对象的行为里更改。比如说,多个对象的共享状态,在其中任何一个对象的方法里更改。实际上,应该再单独抽象一个对象(single model)去做对共享状态的修改,这使共享状态的代码隔离出来,提高了维护性,也使共享状态的修改更加可预测。

    这种方法确实会减少bug,让代码更加可预测,然而每次要警觉到这一点再创建个新的single model听上去就是个无法“实施”的方案。唯有继续上下而求索,后来摸索出的方案就是本文的主题,直接让Model Immutable化。将Model设计成与其他Immutable数据NSArray、NSDictionary一致。Model生成后不可以修改其内容,如果要修改再重生成一个新的Model。

    Facebook和Pinterest提出的方案都是将Model的属性声明为readonly。并用Builder设计模式,抽象一个Model Builder,Model Builder通过原Model生成,生成后,Model Builder可以自由修改数据,然后再由Model Builder生成修改后的新Model。

    本来编写Model就是比较枯燥的工作,如果再将Model Immutable化,要花更多的时间写样板化(boilerplate)代码,更加加重了平时编写Model的负担。为了将Model Immutable化还不给平时工作加重负担,remodel就横空出世了。

    Flux

    要想理解响应式架构的本质,还是要把Facebook提出的响应式架构元老Flux请出来。听说过Flux的都知道,Flux有三个重要部件,view、dispatcher、store,并且Flux是单向data flow。

    • view:类似于controller-views,将data存储、变动隔离出去。view只能引起数据变动和等待data变动的通知。view这一部分更适合用React(Immutable化的视图)。
    • store:明确抽象data状态变动部件,由于data存储经常是data状态变动的副作用,所以将data存储和data状态变动共同抽象为一个部件。只有store中的data是可变的,其他部件中的data都是不可变的,状态变动限制在局部。
    • dispatcher:明确抽象调度者部件,降低多方数据源依赖时和频繁失效式(cascading updates)data变动带来的复杂度。
    • action:由于其他部件不能更改data,若想更改data必须提出申请,抽象更改data单位,提交给dispatcher处理。
      任何一种架构,都是用合适的抽象降低复杂度,Flux也不例外。Flux主要降低的是以下三点:

    将不定的data flow简化为固定的data flow,固定的data flow主要指的是必须通过固定的接口,经过固定的部件,并使每条data flow清晰、易于人脑理解,打log、debug,更重要的是这让data flow可预测。
    将data存储与其状态变动明确抽象为一个部件,并把状态改变限制在局部。
    明确抽象调度者,降低多方数据源依赖时和频繁失效式状态更新带来的复杂度。
    注意:Flux虽然有如此多的好处,但是Flux架构的前题是App足够复杂,这个足够复杂的标志是一个Controller会对应多个View、Model,如果想从中更改或者添加一个View、Model,会怕破坏现有稳定性而无从下手。并且解决这种复杂并不是只有Flux这一种解决方式。

    Flux将data的状态变动从view中隔离出去,view同样也Immutable化了?实际上,还没有,因为不管是浏览器,还是iOS、Android,view都是有状态的。这就轮到React出场了,iOS中则是ComponentKit作为花旦,当view有状态变动,重新生成view。如果还按照以往的render逻辑,很明显会产生性能问题。所以,React就会对view层进行重构,将每个view抽象为一个component,component又嵌套component,模拟树的层级结构,抽象的粒度尽量小,当view的状态变动,React将diff出所有变动的地方,然后重新生成涉及到的component。

    本质

    那什么是响应式架构的本质?这里就下个定义吧,并不是通过工具生成Immutable化Model,也不是固定的几个抽象部件,而是将软件的视图当做data状态的一个响应呈现。

    响应式架构有以下三个明显优势:

    固定data flow,易于人脑理解,也让data flow更可预测。
    从架构层面上消灭共享状态,让多方共享的数据可预测。
    共享数据线程安全。
    那如果不用响应式架构,可不可以同样的学习其思想?我们可以尽量写出更可预测的代码,比如Swift对于Optional的引入,就是让可能为nil更可预测。

    融入现有架构

    首先,你要足够了解产品现有的架构。其次,请尽量融入,而不“重造”。融入响应式架构不只有一种方式,尽量选择较温和的那一款。

    使用或开发一套自动生成Model的工具,将Model Immutable化。通过观察者设计模式,在需要得知Model改变的地方注册观察者。任何Model的修改,无论是网络请求、交互等原因,都新生成一个Model并把Model push出去,具体设计可以参考Consistency-Manager。业务编码要在controller-views里observe,在数据层接口获取Model和交互改变Model的地方push Model。
    引入Rx等FRP范式方案,结合MVVM,具体设计参考limboy的这篇文章。
    无论如何选择,请一定要参考产品的复杂度和已有架构。毕竟,重要的是写出更可预测的代码,形式只是表达。而以笔者业务开发的经历,自动生成Model是很有必要的,自动生成的代码,更少的错误、更统一的规范,这都是我们写代码的崇高追求。不过,国内外开发环境不一样,国外开发的工具确实不能生搬硬套到国内,这就是另外一个话题了,后期有机会笔者会开源正在使用的自动代码生成工具。

    除了将Model Immutable化,那View要Immutable化吗?这是一个值得好好权衡的问题,硅谷的科技公司对View的Immutable化做的很彻底,从GMTC大会上Airbnb分享的代码实践里将View起名为Component就能猜测出一二了。但是国内确实是不温不火,究其原因还是学习成本成了拦路虎,国内公司的迭代速度是硅谷无法匹敌的,在快速的试错、迭代中,View层的变化始料不及,公司对技术的要求必然是可让新手快速上手、迭代,将View Immutable化,要整体重构系统View层,对于没有接触过的新手很不友好。如果让新人入职第二天开始写需求,而他都没法在新项目中好好写个View,这就是不可接受的了。

    说到底,什么应用适合响应式架构呢?新闻类和信息展示类应用会很适合响应式架构,换句话说,也就是大部分App都适合。

    展开全文
  • iOS 单向响应式架构.zip,一种反应式单向swift应用架构框架
  • 响应式架构简介

    2021-03-01 16:14:06
    在介绍响应式编程技术会使软件开发工作变得简单之前,让我们先了解一下开发企业级软件为何如此之难。当然,你可能在开发企业级软件时遇到过各种难题,如需要经常使用的数据库出问题、应用程序服务器拖慢了你开发的...
  • 我们采用用最精简的代码,还原vue2.0响应式架构实现。 以前写的那篇 vue 源码分析之如何实现 observer 和 watcher可以作为本次分享的参考。 不过不看也没关系,但是最好了解下Object.defineProperty 本文分享什么 ...
  • 响应式架构 消息模式Actor实现与Scala.Akka应用集成_沃恩·弗农_PDF:PDF电子版_2016.07_417_14043812的电子版。
  • 响应式架构 消息模式Actor实现与Scala.Akka应用集成,响应式架构 消息模式Actor实现与Scala.Akka应用集成
  • 响应式架构 消息模式Actor实现与Scala.Akka应用集成 高清扫描版
  • 结合目前技术体系和业务特点的思考,我们在业务中实践了响应式架构以及 RxJava 框架,来解决系统与业务复杂所带来的问题。 一、实践响应式架构 响应式架构是指业务组件和功能由事件驱动,每个组件异步驱动,可以并行...

    阿里云幸运券

    随着有赞零售业务的快速发展,系统和业务复杂度也在不断提升。如何解决系统服务化后,多个系统之间的耦合,提升业务的响应时间与吞吐量,有效保证系统的健壮性和稳定性,是我们面临的主要问题。结合目前技术体系和业务特点的思考,我们在业务中实践了响应式架构以及 RxJava 框架,来解决系统与业务复杂所带来的问题。

    一、实践响应式架构
    响应式架构是指业务组件和功能由事件驱动,每个组件异步驱动,可以并行和分布式部署及运行。

    响应式架构可以带来以下优势:

    大幅度降低应用程序内部的耦合性

    事件传递形式简化了并行程序的开发工作,使开发人员无须与并发编程基础元素打交道,同时可以解决许多并发编程难题,如死锁等。

    响应式架构能够大幅度提高调用方法的安全性和速度。

    对复杂业务系统的领域建模,响应式架构可以天然支持。每个系统组件就可以对应到一个业务实体,业务实体之间通过接收事件来完成一次业务操作。

    我们使用响应式架构主要是为解决多个系统间的多次远程调用带来的分布式问题,尤其在长任务场景中,响应式架构显得尤其必要。

    有赞连锁出现后,随着连锁商家经营规模的扩张,会在系统中创建新的门店。创建新门店会引发一系列业务初始化工作,例如店铺、员工、仓库、商品、库存等业务域,并且各业务域之间存在一定的依赖关系(如图1所示),例如商品依赖仓库初始化完成。

    图1 连锁新建分店系统依赖关系
    商家新增门店时,在店铺初始化完成后,连锁系统发送店铺初始化成功消息,相应系统对事件进行响应,处理完成(成功/失败)后将回执给连锁系统,连锁系统根据相关业务的反馈,决定是继续通知下游业务,还是结束整个过程。新建门店部分流程如图2所示。

    在创建门店业务中,每个系统响应连锁系统发出的消息,处理完成后进行回执。通过这种模式,业务系统本身不关心其他系统是否成功或失败,只需对通知的事件进行处理,整体初始化进度与异常处理由连锁系统来控制。这种设计使得各业务系统之间没有直接耦合并保持相互独立。

    图2 连锁体系新增分店消息驱动图
    上面的案例介绍了在复杂业务场景下系统间对响应式架构的实践,系统内部同样会遇到复杂业务场景。下面介绍下在系统内部应对复杂业务的实践。

    二、RxJava在有赞零售实践
    Rxjava 是用来编写异步和基于消息的程序的类库。RxJava 在 Android 有着广泛的使用,主要应用在用户界面绘制与服务端通讯等场景。RxJava 的核心思想是响应式编程以及事件、异步这两个特点。响应式编程是一种通过异步和事件流来构建程序的编程模型。在复杂的业务开发中,最棘手的问题就是如何清晰直观的展现复杂的业务逻辑,并且方便后续的业务维护与扩展。

    2.1 响应式编程使得复杂业务逻辑更清晰
    有赞零售的业务场景中有着复杂的业务逻辑,有赞目前提供多种产品供商家选择,商家在不同产品进行切换时,为了商家更好的体验,不同业务的切换会进行数据初始化与处理。例如有赞微商城转换到有赞零售。

    这里拿着微商城升级零售的业务场景给大家举例。微商城升级为零售时需要对商品进行转换。首先初始化店铺基础信息。然后读取商品流,将微商城的商品类型转换成零售支持的商品类型。最后读取规格,为规格创建供应链商品库,创建门店商品与添加网店商品的供应链商品关联关系。整体转换流程如图3所示。图中也画出了可以并发处理的场景。

    图3 微商城升级有赞零售流程
    如果单纯使用设计模式来解决上面这种场景单一、但业务逻辑特别复杂的场景,是很难做到的。也可以看到除了初始化信息那一步,后面的商品模型转化自始至终在业务中流转的事件都是商品,这里就可以使用 RxJava 来优化业务代码使得处理流程可以并发,加快升级速度。

    最终我们按照图3的流程处理升级逻辑,其中的并发场景,比如保存完零售商品后,并发处理库存、和销售渠道,使用 rxjava 封装的方法帮助我们进行并发操作。如下所示代码结构清晰,对外屏蔽了复杂的并发处理逻辑。

    Observable
    .zip(

    callAsync(()->处理库存相关操作),
    
    callAsync(()->更新商品库门店销售渠道),
    
    callAsync(()->创建商品库与网店商品关联关系),
    
    (sku1,sku2,sku3)-> sku
    

    ).blockingFirst();

    最终我们的整体的代码:

    UpgradeItem
    .listItems(manager, shop)

    .flatMap(item-> fromCallable(()->更新为零售商品类型))
    
    .flatMap(item-> fromCallable(()->并发处理商品操作), 
    

    true
    )

    .flatMap(item-> 商品流转化为sku流, 
    

    true
    )

    .flatMap(sku-> fromCallable(()->保存零售商品))
    
    .flatMap(sku-> fromCallable(()->并发处理保存商品后续操作, 
    

    true
    )

    .subscribeOn(
    

    Schedulers
    .io());

    整个商品处理流程就是上面这段代码,一目了然,后面扩展可以自己在中间加入处理流程,也可以在对应业务方法中修改逻辑。

    2.2 多服务、数据源组合
    随着微服务架构兴起,我们将不同的业务域拆分成不同的系统。这样方便了系统的维护,提升了系统的扩展性,但是给上层业务系统也带来了很多麻烦。往往我们为了展示一个页面会涉及到 2-3 个或更多的应用,而多次的分布式调用不但使得系统的rt增加,也使得核心页面的出错风险更高。

    降低rt:在假设第三方接口已经达到性能顶点的情况下,并发是解决多次分布式调用降低rt的常用方法。

    自动降级:传统编程方法中,自动降级处理,意味着我们代码中会出现一大堆try/catch,而使用 rxjava,我们可以直接定义当流处理异常时,程序需要怎么做,这样的代码看起来非常简洁。

    商品搜索作为商品管理的核心入口,根据不同场景聚合商品、优惠、库存等信息。由于商品列表页展示的信息涉及到多服务数据的整合,一方面需要保证整个接口的 rt,另一方面不希望由于一个商品数据或外部服务的异常影响到整个商品列表的加载。因此该场景非常适用于 RxJava。

    最终我们的代码

    1.根据入参获取商品加载器

    //只有包含的merger才会加载

    List
    <
    SkuAttrMerger

    validMergers =

    Observable
    .fromIterable(skuAttrMergers).filter(loader -> request.getAttributes().contains(loader.supportAttribute().getValue())).toList().blockingGet();

    2.根据 es 结果获取商品各个属性详情并加载到 SkuAttrContext 中(某类属性加载失败则忽略)

    //调用load并发加载数据到商品属性上下文中

    Observable
    .fromIterable(商品信息加载器列表)

    .flatMap(商品信息加载器->
    Observable
    .fromCallable(() ->异步加载商品信息))

    .onErrorResumeNext(
    Observable
    .empty())
    //如果失败则忽略

    .subscribeOn(
    Schedulers
    .io()),
    false
    ,线程数(为加载器数

    量)).blockingSubscribe();

    3.组装搜索结果(如果某个 sku 组装失败则直接忽略)

    //调用merge将数据合并到目标对象

    商品搜索返回结果列表 =
    Observable
    .fromIterable(商品id列表)

    .map(商品id->初始化商品搜索结果返回对象)
    
    .flatMap(商品搜索结果返回对象-> {
    
        val observables=
    

    Observable
    .fromIterable(商品加载器列表)

            .map(loader -> 
    

    Observable
    .fromCallable(() ->合并每个sku的不同属性)).toList().blockingGet();

    return

    Observable
    .zipIterable(observables, (a) -> sku,
    false
    , 线程数)

        .onErrorResumeNext(
    

    Observable
    .empty());
    //如果失败则忽略

        }, 
    

    false
    ,
    1
    )

    .toList()
    
    .blockingGet();
    

    腾讯云代金券

    原文链接

    https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA%3D%3D&mid=2455759277&idx=1&sn=3096d192749deadeab3136751579493a&chksm=8c686f88bb1fe69e8182cf4659915a415b7d340c4257ab5d6a794283659906142837f824dd18&mpshare=1&scene=23&srcid=04051ZtIw5kEcsoJw0xy9Hqi%23rd

    服务推荐

    展开全文
  • Airbnb开源框架,真响应式架构——MvRx Airbnb开源框架,真响应式架构——MvRx- https://mp.weixin.qq.com/s/pttfFzkAO5MnwscXDX-dDw Airbnb开源框架,真响应式架构——MvRx: https://github.com/airbnb/MvRx ...

    > Airbnb开源框架,真响应式架构——MvRx

    Airbnb开源框架,真响应式架构——MvRx- https://mp.weixin.qq.com/s/pttfFzkAO5MnwscXDX-dDw
    Airbnb开源框架,真响应式架构——MvRx: https://github.com/airbnb/MvRx
      MvRx 有四个重要的概念,分别是 State、ViewModel、View 和 Async。
    有了 Epoxy 的帮助,绝大部分界面都可以放入RecyclerView 中。对于不适宜使用 RecyclerView 的界面,或者 RecyclerView 之外的一些界面元素,MvRx 至少也提供了与 Android Architecture Components 相似的能力,并且其与 RxJava 的结合更加的友好。
      这个库综合运用了以下几种技术:Kotlin (MvRx is Kotlin first and Kotlin only);Android Architecture Components;RxJava;React (概念上的);Epoxy (可选但推荐);

     MVP 模式在 Android 界一直很流行,因为它比较好理解。其核心思想是,通过接口隔离数据与显示,数据的变动通过接口回调的方式去通知界面更新。这正是典型的命令式 M-V(数据-显示)链接。在这种模式下 View 层是完全被动的,完全受控于 Presenter 层的命令。这种模式并没有什么大问题,只是有一些不太方便之处,主要体现在 M-V 的紧密链接,导致复用比较困难,要么 View 层需要定义不必要的接口(这样 Presenter 可以复用),要么就需要为几乎每个 View 都定义一个对应的 Presenter,想想都心累。

    - MvRx 的真响应式 MVVM
    MvRx 构建的 MVVM 模式,完美地解决了上述的问题。MvRx 放弃了LiveData,使用State 来通知 View 层数据的改变(当然仍然是可感知生命周期的)。MvRx 可以方便地把RxJava Observable 的请求过程包装成 Ansyc 类,不仅可以改变 State 来通知 View 层,而且也包含了数据加载的状态(成功、失败、加载中等)。如果结合 Airbnb 的另一个开源库 Epoxy,那么几乎可以做到真正的响应式,即View层在数据改变时仅仅描述当前数据状态下界面的样子,Epoxy 可以帮我们实现与之前数据状态的比较,然后找出差别,仅更新那些有差别的 View 部分。这是对 MvRx 的大致描述。下面来看看 MvRx 是如果使用的。

    > Epoxy

    Epoxy——RecyclerView的绝佳助手- https://www.jianshu.com/p/369c08c29593
    Epoxy is an Android library for building complex screens in a RecyclerView- https://github.com/airbnb/epoxy

    展开全文
  • ⭐ ViaBus 是一款响应式架构,借助总线转发数据的请求和响应,实现ui、业务的完全解耦。
  • 响应式架构:消息模式Actor实现与Scala、Akka应用集成》由10章构成,详细介绍了使用Actor模型中的响应式消息传输模式的理论和实用技巧。其中包括:Actor模型和响应式软件的主要概念、Scala语言的基础知识、Akka...
  • 前言 正如“100个读者就有100个哈姆雷特”一样...本文谈谈本人是如何设计一个简单的 iOS 响应式架构。iOS 架构 DEMO 一、关于组件化 组件化似乎是项目发展壮大过后必然要做的事情,它能让各个业务线的工程师不需要过...

    前言

    每一个架构设计者都要考虑到各个因素,比如团队成员的技术水平、具体的业务场景、项目的成长阶段和开发周期。

    本文谈谈本人是如何设计一个简单的 iOS 响应式架构。iOS 架构 DEMO

    一、关于组件化

    组件化似乎是项目发展壮大过后必然要做的事情,它能让各个业务线的工程师不需要过多的关注其他业务线的代码,有效的提高团队整体效率。然而实施组件化的时机是在需求相对稳定、产品闭环形成过后。所以本文不会应用组件化,但是这里简单谈谈业界的组件化方案。

    组件化的核心问题就是组件间如何通讯。“软件工程的一切问题都能通过一个间接的中间层解决。”中介模式很自然的运用起来:

    1542853035652480.png

    这样虽然能统一组件间的通讯请求,但是却没有避免 Mediator 和目标组件的耦合,ModuleA 工程中仍然需要导入 ModuleB 。

    所以重点问题落在了解耦上:

    1542853050519144.png

    要达到 Mediator 和目标组件的解耦,就需要实现它们之间的间接调用(图中虚线),既然是间接调用,必然需要一种映射机制。在 iOS 开发中,业界大概有三种方式来处理。

    (1) 使用 URL -> Block 解耦

    简单来说就是将组件的调用代码放入 block 中,然后 URL 作为 key,block 作为 value,存入一个全局的 hash 容器,组件通过一个 URL (比如 “native/id=10/type=1” )向 Mediator 发起请求,Mediator 找到对应的代码块执行。由此,解开了 Mediator 和目标组件的耦合(见博客:蘑菇街 App 的组件化之路)。

    这种方案的缺陷很多:组件越多常驻内存越多;解析 URL 逻辑复杂;URL 无法表述具体语言相关的对象类型。所以这种方式并不适合组件化解耦。

    (2) 使用 Protocol 解耦

    阿里的 BeeHive 是该方案的很好实践,笔者阅读了一下源码,它的大致工作原理如下:注册 Protocol 对应的组件,这个和上面说的 URL->Block 方式如出一辙,只不过这里是 Protocol-> Module ;组件申请访问时导入对应的 Protocol 通过 Mediator 获取到对应的组件对象。由于协议的表述能支持所有的对象类型,所以这种方式能基本解决组件间通信的需求。

    BeeHive 注册组件有几种方式,一种是监听了动态链接时 image 二进制文件加载完成的回调,通过修改代码段的方式判断对应的模块进行注册;第二种是在 +load 方法里面注册;第三种是异步注册,但是这种方式存在一个问题,可能组件使用方准备使用组件的时候,这个组件还未注册成功。

    BeeHive 还为组件设置了优先级的概念,它通过数组来保持优先级排序,在源码中能看到一些数组排序的逻辑,这就带来了相当多的高时间复杂度的运算。

    所以,组件数量过多的话,会延长动态链接库的过程。

    BeeHive 为了让每一个组件享有独自的 app 生命周期、3D touch 等功能,会将这些系统级的事件发送给每一个组件,且不谈大量的方法调用损耗,它必须让入口文件 AppDelegate 继承自 BeeHive 的 BHAppDelegate,笔者感觉侵入性过强,并且当开发者需要复写 AppDelegate 方法的时候,还要注意让super调用一下,可以说很不优雅了。

    在基于协议的组件化方案中,组件使用方能直接拿到目标组件的实例,那么使用者可能对该实例进行修改,这可能会带来安全问题。

    (3) 使用 Target-Action 解耦

    Casa Taloyum 前辈的 iOS应用架构谈 组件化方案 为此做出了最佳实践。

    Mediator 使用 Target-Action 来间接的调用目标组件,无需专门注册。组件维护者需要做一个 Mediator 的分类,通过硬编码调用目标组件,然后组件使用者只需要依赖这个分类就行了。封装的 Mediator 源码只有简单的 200+ 行代码,并且很易懂。这也让开发者能对组件化的实施更加有信心,不会因为基础设施的错误而束手无策。

    小总结

    关于以上组件化的简单表述仅代表笔者的个人见解,由于笔者并没有真正的实施组件化,所以理解可能有误。

    虽然笔者设计的 iOS 架构不会应用组件化,但是这给我们的架构设计带来了前瞻性的引导,这非常重要。

    二、模块化思维划分文件

    在团队开发中,项目发展到后期总是会出现某些文件或代码难以管理,出现这种情况的主要原因通常是项目开发过程中对文件的管理过于随意。

    开发者应该尽量将所有代码文件归于模块,而不要出现模拟两可的文件。而笔者这里说的模块,是有具体意义的模块,比如图片处理模块、字体处理模块,而不是诸如 Public、Common 等无具体意义的代码文件。

    试想,在多人开发中,当所有人都觉得有些代码不知道怎么归类的时候,就会往 Public 里面扔。当你某天想要整理一下这个 Public,会发现已经无从下手;或者当你需要迁移项目中的某个业务模块时,会附带迁移一些模块,当这个模块是有意义的(比如图片处理模块),你的迁移成本会非常低,但是当这个藕断丝连的模块是 Public 时,时间成本可能高于你的想象,估计你会将它完整的拷贝过去,而又对新项目造成了污染。

    全局的公共文件是产生垃圾代码的源头。笔者认为几乎所有的代码都是可以归类为模块的。

    大致梳理了一个文件分类,当然这个分类是灵活的,只是要分模块划分:

      - GeneralModules 放项目独有的通用配置模块(比如通用颜色模块、通用字体模块)  
      - ToolModules 放工具类模块(比如系统信息模块) 
      - PackageModules 放基于业务的一些封装(比如提示框模块、加载菊花模块) 
      - BusinessModules 放业务模块(比如购物车、个人中心)
    

    具体里面放了些什么,可以查看笔者的 DEMO。

    三、减少全局宏的使用

    很多时候,过多的宏让项目很不整洁,每一个开发者都往全局文件添加宏,而往往只是一段简单的代码,笔者认为开发中应该尽量少使用宏,原因如下:

    • 宏在预编译阶段替换为实际代码,存在效率问题
    • 使用宏的地方可能只需要一块内存,但是宏替换过后开辟了多个(这种情况应该用常量替换宏)
    • 可能存在潜在的宏命名冲突
    • 宏包装过多的代码难以理解和调试
    • 代码迁移时需要处理全局的宏

    实际上,非得使用宏的地方并非那么多,比如需要定义一个全局的导航栏字体方便使用,可以将通用字体的配置参数作为一个模块:

    @interface HQGeneralFont : NSObject
    /** 导航栏标题字体 */
    + (UIFont *)navigationBarTitleFont;
    @end
    

    或者用常量来代替宏:

    .h
    FOUNDATION_EXTERN NSString * const kNotify_xxx; 
    //xxx通知 key.m
    NSString * const kNotify_xxx = @"kNotify_xxx";
    

    这么做也便于转换思维,毕竟 swift 中是没有宏的。

    四、去基类化设计

    代码设计中,应该尽量避免基类的使用,也就是说,你不应该总是要求开发者去继承你的基类来做功能。使用基类将造成不可避免的耦合,为业务的长期发展带来阻碍(当然某些情况是可以使用基类的)。

    其实使用基类就算了,若是将大量的业务逻辑放入基类中将是灾难的开端。试想,当项目新成员一来就看见成千上万行的基类代码TA作何感想?

    另外一种场景,当需要将项目中的某个模块迁移到其他项目,或者需要将其他项目合并入当前项目,基类的合并将是一个非常头疼的问题,它藕断丝连的模块和代码会让你抓狂。
    那么,类的工具方法应该放哪儿?对所有类的统一配置应该放哪儿?对封装模块的个性化定制应该怎么做?

    装饰模式

    类的工具方法,按道理说可以提取为模块,但是有些场景可能显得不够简洁。

    其实只要留意 iOS 官方的 API,你就不难发现装饰模式的大量应用,使用数个分类将大量的方法按照功能分类,会清晰且优雅:

    @interface UIViewController (HQGeneral)
    /** 基础配置 */
    - (void)HQGeneral_baseConfig;
    @end
    
    @interface UIViewController (HQGeneralBackItem)
    /** 配置通用系统导航栏返回按钮 */
    - (void)HQGeneral_configBackItem;
    /** 重写该方法以自定义系统导航栏返回按钮点击事件 */
    - (void)HQGeneral_clickBackItem:(UIBarButtonItem *)item;
    @end
    

    不过要注意的时,定义分类的时候一定要加一个前缀标识以避免方法覆盖。

    AOP

    面向切面编程在 iOS 领域经典的应用就是利用 Runtime 去 Hook 方法:

    + (void)load {
        [self HQGeneralHook_exchangeImplementationsWithOriginSel:@selector(viewDidLoad) customSel:@selector(HQGeneralHook_viewDidLoad)];
    }
    
    + (void)HQGeneralHook_exchangeImplementationsWithOriginSel:(SEL)originSel customSel:(SEL)customSel {
        Method origin = class_getInstanceMethod(self, originSel);
        Method custom = class_getInstanceMethod(self, customSel);
        if (origin && custom) {
            method_exchangeImplementations(origin, custom);
        }
    }
    
    - (void)HQGeneralHook_viewDidLoad {
        NSLog(@"进入:%@", self);
        [self HQGeneral_baseConfig];
        if (self.navigationController && [self.navigationController.viewControllers indexOfObject:self] != 0) {
            [self HQGeneral_configBackItem];
        }
        
        [self HQGeneralHook_viewDidLoad];
    }
    
    

    代码中统一配置了 UIViewController 的系统导航栏返回按钮,注意这里调用的业务配置方法都是定义在 UIViewController 的分类里面的。若有某些导航栏需要格外配置返回按钮的需求,可以拓展一个属性来控制。

    面向协议设计模式

    对于一些封装的组件,多考虑使用协议来个性化定制,继承作为最差方案,而非是首选方案。

    定义一个遵守组件定制协议的属性是常用的解决方法:

    @property (nonatomic, strong) id<someprotocol>  strategy;</someprotocol>
    

    不同的属性作为不同的策略,组件内部通过调用对应的协议方法实现个性化定制。而当使用者想要改变策略时,只需要更改这个属性就行了。面向协议设计模式结合策略模式是一个很好的实践。

    五、MVC?MVP?MVVM?VIPER?

    业务具体的架构模式是个让很多开发者头疼的问题,因为有时候能让复杂业务更清晰,有时候却因为胶水代码过多而臃肿。

    实际上为什么要严格的遵守架构模式呢?为什么每一个业务模块的架构模式都要一模一样呢?

    笔者认为正确的架构思路一定是根据业务来的,不同的模块,不同的业务线完全可以有不同的架构,只需要架构足够清晰不至于晦涩。

    大致设计了一下架构的主旋律:

    1542853405354937.png

    • DataCenter 负责数据的获取、处理、缓存等。
    • Model 设计为“瘦” Model,便于复用和迁移;也考虑到数据源可能数量庞大,若 Model 设计得过于“胖”,会造成更多的内存占用。
    • View 负责数据的展示,可以根据业务情况权衡是否需要 ViewModel 处理界面逻辑。
    • ViewController 作为 DataCenter 和 View 的桥梁。

    笔者设计的项目目前不会很复杂,多数情况上面的架构就已经够用,若某个页面功能过多,完全可以提取一些额外的模块,比如 DataCenter 处理过于复杂,那就把数据的处理和缓存提取出来:xxxDataProcesser、xxxDataCache。这些都是灵活的,只需要按照模块化的思维提取,ViewController 的代码相信也不会太多。

    关于响应式框架

    Reactivecocoa 虽然强大,笔者以前也用过,不过它是一个重量级框架,学习成本有点高,可能会因为团队成员对其了解不足导致难以定位的错误。

    而美团的 EasyReact 似乎是一个福音,笔者大概浏览了一下源码,质量确实很高,对性能方面的处理很精致,基于图论算法的处理也感觉很棒,项目侵入性也很小。不过缺点就是太新了,需要开发社区一定时间的验证,暂时笔者持观望态度。

    结语

    本文只是作者思考过后对一个项目架构的简单设计,还有很多部分需要完善和补充,具体细节也可能会按照具体情况修改。Demo 只是一个雏形,希望和各位读者朋友能有所交流。

    展开全文
  • 响应式架构 消息模式Actor实现与Scala.Akka应用集成 ,沃恩·弗农
  • 目前响应式是一个极度被使用的名词。牛津词典定义响应式是”对刺激作出响应”,因此,响应式软件基于它接受到刺激作出响应、调节它的行为。百度百科给出的答案是响应式编程是一种面向数据流和变化传播的编程范式。而...
  • 分享前啰嗦 我之前介绍过vue1.0如何实现observer和watcher。本想继续写下去,可是vue2.0横空出世.....我们采用用最精简的代码,还原vue2.0响应式架构实现 以前写的那篇 vue 源码分析之如何实现 observer...
  • 响应式架构++消息模式Actor实现与Scala.Akka应用集成+,沃恩·弗农+
  • https://tech.youzan.com/xiang-ying-shi-jia-gou-yu-rxjavazai-you-zan-ling-shou-de-shi-jian/

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,094
精华内容 837
关键字:

响应式架构