欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

Swift使用enum抹平数组元素差异实例详解,

来源: 开发者 投稿于  被查看 13051 次 评论:205

Swift使用enum抹平数组元素差异实例详解,


目录
  • 前言
  • 业务场景
  • 用什么控件
    • 使用UIScrollView的分析
    • 使用UITableView的分析
  • 加工数据
    • 总结

      前言

      通过Protocol去封装入参,抹平了入参之间的差异。

      今天这篇依然围绕一个我遇到的业务场景,给大家提供一种思路——使用enum抹平数组元素差异。

      业务场景

      我先说明一下业务场景:

      页面是一个有限可以滑动的的页面(后面我们会分析到其实无限或者有限都无所谓)

      页面的每一个子View都是和后台返回的数据绑定,其JSON大致可以理解为下面这种形式:

      {
          "aPart":{
              "some":""
          },
          "bPart":[
              {
                  "label":"",
                  "value":""
              },
              {
                  "label":"",
                  "value":""
              }
          ],
          "cPart":[
              {
                  "iconUrl":"",
                  "text":"",
                  "functionType":""
              },
              {
                  "iconUrl":"",
                  "text":"",
                  "functionType":""
              }
          ],
          "dPart":{
              "name":"",
              "age":""
          },
          "deviceType":"",
          "serviceAble":""
      }
      

      业务需求如下:

      • aPart对应aView,bPart对应bView,cPart对应cView,dPart对应dView,可以如此穷举下去
      • 每个part的JSON结构可能都不相同
      • 后台下发JSON的时候,会根据用户账号的情况,返回不同数据,比如aPart的业务没有,后台会返回"aPart":null,App的aView需要隐藏,存在可能多个part都返回为null的情况,比如"bPart""cPart"都为null的情况,那么App的bView和cView需要隐藏。

      那么问题来了,iOS端如何构建一个可以灵活配置的界面?

      用什么控件

      使用UIScrollView的分析

      作为开发App页面的第一点,选取合适的控件是非常重要,因为控件决定了最终数据源的形式。

      因为后台数据返回的并不是一个JSON数组,同时又是有限的数据,很多人优先会考虑通过UIScrollView去进行页面的构建,我一开始也是这么想的,但是麻烦的是这一点:

      后台下发JSON的时候,会根据用户账号的情况,返回不同数据,比如aPart的业务没有,后台会返回"aPart":null,App的aView需要隐藏。

      也就是说你把aViewbViewcViewdView贴在了scrollView上面之后,需要根据后台的数据隐藏页面,甚至更新布局逻辑,每一个view都有2种情况,假设后台有4个业务数据,那么那就是2的4次方——32种可能。

      其实仅隐藏还是显示非常简单,但是如果是使用SnapKit布局,那么更新view的布局,可能就并不是特别好了,同时如果这个页面后续还有新的业务数据,那么就子view会继续增加,维护成本也会越来越高。

      所以,到此使用UIScrollView的方案,别否决了,并不是说它不能构建,而是成本有些高,而且不够灵活。

      所以剩下的只剩下一种选择了——使用通过数据源绑定UI的UITableView。(备注:其实使用UICollectionViewUITableView都一样,只是多一个瀑布流布局而已)。

      使用UITableView的分析

      使用UITableView的优势:

      完完全全通过数据去驱动页面,页面是否显示完全通过有无数据决定。

      但是数据源相较普通模型有着更加严格的数据格式——数组!

      而且数组的每个元素最好都是相同的数据类型,因为如果使用[Any]这样去表达一个数据源,成本太高。

      好了,既然我们定下了使用UITableView来进行页面构建,那么剩下的难点也就来了——如何将后台的数据加工成为一个好用的数据源?

      加工数据

      将后台数据加工成为一个数组并不难,关键是统和数据类型才是难点。

      在上一篇文章里面,我通过Protocol去统和了Model[String: String],在这次情况下面可不可行呢?

      protocol EraserConvertible {}
      struct Response: Codabel {
          let aPart: APart?
          let bPart: BPart?
          let cPart: CPart?
          let dPart: DPart?
      }
      struct APart: Codabel, EraserConvertible {}
      struct BPart: Codabel, EraserConvertible {}
      struct CPart: Codabel, EraserConvertible {}
      struct DPart: Codabel, EraserConvertible {}
      

      我们回头看看这个JSON,你不得不认清这样个现实,每个Part都是独立的,完全看不到任何关联,如果想要做类型一致,EraserConvertible这个协议很难满足。

      不如做向上类型统和,也就是说数据源变成let dataSource = [Response]()这种形式,在tableView的数据源方法中获取单个数据,然后在进行分析:

      let dataSource = [Response]()
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          let item = dataSource[indexPath.row]
          if let aPart = item.aPart {
              /// 构建aCell
              let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)!
              /// 将aPart赋值给aCell
              cell.aPart = aPart
              return cell
          }
          if let bPart = item.bPart {
              /// 构建bCell,
              let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)!
              /// 将bPart赋值给bCell
              cell.bPart = bPart
              return cell
          }
          .
          .
          .
      }
      

      嗯,这样看起来非常不错,只是每个item都包含了aPart到dPart,方案的是可行。让我们想想有没有更加优雅的思路呢?

      数据源的类型不同决定了不同的Cell。我们可不可以用状态来表示?

      于是乎我写下了这样的代码:

      enum BusinessPart {
          case a
          case b
          case c
          case d
      }
      

      数据源变成let dataSource = [BusinessPart]()这种形式,那么如何区分每个case不同的数据呢?

      Swift的enum是可以带参数的,而且单个case带不带参数,带什么类型的参数都很自由。

      于是乎,我们接着改造BusinessPart

      enum BusinessPart {
          case a(APart)
          case b(BPart)
          case c(CPart)
          case d(DPart)
      }
      

      这样我现在就通过enum抹平的数组元素的差异,接下来只要把后台数据架构成为我想要的[BusinessPart]格式就好了,这里放处理逻辑:

      private func process(model: Response) -> [BusinessPart] {
          var array: [BusinessPart] = []
          /// 非空才加入数组,后台返回null,此时aPart为nil,那么aCell也不会出现
          if let aPart = model.aPart {
              array.append(.a(aPart))
          }
          if let bPart = model.bPart {
              array.append(.b(bPart))
          }
          if let cPart = model.cPart {
              array.append(.c(cPart))
          }
          if let dPart = model.dPart {
              array.append(.d(dPart))
          }
          return array
      }
      

      TableView的数据源方法中这么使用:

      let dataSource = [BusinessPart]()
      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          let item: BusinessPart = dataSource[indexPath.row]
          switch item {
              case .a(let aPart):
              /// 构建aCell
              let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)!
              /// 将aPart赋值给aCell
              cell.aPart = aPart
              return cell
              case .b(let bPart):
              /// 构建bCell
              let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)!
              /// 将bPart赋值给bCell
              cell.bPart = bPart
              return cell
              case .c(let cPart):
              /// 构建cCell
              let cell = tableView.dequeueReusableCell(withIdentifier: cCell.className)!
              /// 将cPart赋值给cCell
              cell.cPart = cPart
              return cell
              .
              .
              .
          }    
      }
      

      至于这个[BusinessPart]的dataSource怎么在UITableViewDataSource中如何处理,亦或者在BusinessPart的分类中怎么处理,这个就是一个仁者见仁智者见智的问题了。

      总结

      到最后,这个页面的数据加工,最后还是变成了向上还是向下统和的思考。

      其实就是对于一个差异性特别大的数据,如何比较好的整合为一个在iOS中合适的数组问题,通过字段的全覆盖达到模型的统合,抑或通过Swift中枚举带参的特点,抹平差异。

      从代码层面上看,两者的代码量差不多,但是使用enum抹平数组元素差异为我们提供一个新的解题思路,至少这是我自己思考的成果。

      另外一个角度就是通过布局控件来展开,因为我看Android就是直接用一个ScrollView撸起的:

      Android开发中,大部分控件都有visibility这个属性,其属性有3个分别为“visible ”、“invisible”、“gone”。主要用来设置控制控件的显示和隐藏。

      invisible当控件visibility属性为invisible时,界面保留了view控件所占有的空间;而控件属性为gone时,界面则不保留view控件所占有的空间。

      因为Android这个三个属性可以非常便利的完成隐藏还是不隐藏,以及是否保留控件所占有的空间。

      而iOS可能需要不仅改变isHidden属性,甚至连view的frame也要进行改变,成本太大了。在使用UITableView的分析已经提到。

      但是也不是不可能,比如使用FlexLib应该可以实现。(备注:我自己没用过FlexLib库,这个有待考证哈

      用户评论