前语

在日常开发中一个好的项目结构能够带来明晰的思路在保护代码和搭档之间的帮忙上降低了成本,而在当时IM技能日益推广的背景下很多同行都会面对对IM相关事务的开发。在这里咱们通过对事务层对IM架构进行分拆和大家探讨一下合理的项目架构 项目架构是MVP规划形式:

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构

现状

IM音讯有多种多样例如文字音讯、图片音讯、语音音讯、图文音讯、视频音讯等,这些音讯在数据结构上来讲都有相同的数据模型能够分为音讯内容、音讯ID、音讯类型、是否展示时间、是否重发等.在界面呈现上每个音讯直接的cell同样存在相同的元素因此咱们往往对模型和cell采纳继承计划 但是在tableivew的数据源办法中咱们不免会判别当时的数据模型是属于哪一种音讯从而去加载对应类型的cell,假如每个cell当中都存在一些工作需求传递到controller那么咱们的tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell办法就会十分臃肿。下面我用3种cell的状况咱们看看作用

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构

从上面能够看出,3种类型的cell所发生的代码现已相当多,假如有更多的音讯类型这里的代码变得更臃肿。另外假如咱们把所有的工作处理都放在presenter中处理那么跟着音讯类型的增加cellforrow办法和presenter也会不断增加代码,不方便保护。特别是像音频播放会触及多个逻辑当其他搭档来保护或者修正逻辑时要查找和修正对应的代码也是操心费力。

改造

下面咱们来聊聊我的一个改造计划

1.对Controller的改造

  1. cell的注册,这里的技巧是选用音讯的messageType作为cell的仅有id.这样做能够达到模型驱动UI的作用,下面结合tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell就能看出作用
  private func registCell(){
        tableView.register(ChatTextCell.self, forCellReuseIdentifier: Self.cellIdentifier+"(MessageModel.IMMessageType.text.rawValue)")
        tableView.register(ChatImageCell.self, forCellReuseIdentifier: Self.cellIdentifier+"(MessageModel.IMMessageType.image.rawValue)")
        tableView.register(ChatVoiceCell.self, forCellReuseIdentifier: Self.cellIdentifier+"(MessageModel.IMMessageType.voice.rawValue)")
    }
  1. tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell办法的改造,下面不到6行的办法就能处理多种类型cell的判别问题
   func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
        let msgModel = presenter.dataSource[indexPath.row]
        let cell:BaseTableViewCell? = tableView.dequeueReusableCell(withIdentifier: Self.cellIdentifier + "(msgModel.messageType.rawValue)") as? BaseTableViewCell
        return cell ?? UITableViewCell()
    }

看一下UI作用和本来的一致,代码量大大减少

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构

2.对cell的工作进行改造

  1. 在BaseTableViewCell中界说工作统一回调
     /*
      event: ChatEvenType枚举类型的工作
  msgModel: 音讯模型
  */
  var eventCallback:((_ event: ChatEvenType, _ msgModel: MessageModel?)->Void)?

ChatEvenType如下

   enum ChatEvenType: Int32{
      case CellTapText = 0
      case CellImageLongPress  = 1
      case CellVoiceStopPlayTap  = 2
       //假如有其他工作能够在下面界说
  }
  1. 在对应的cell的工作中增加回调,举文字信息为例,图片、声响的原理相同
    //MARK: Action
    @objc private func mesLabelClick(){
        guard let textModel = msg as? TextMsgModel else{return}
//        self.clickTextClosure?(textModel) 本来的回调计划
        self.eventCallback?(.CellTapText,textModel) 
    }
  1. Controller上的回调
 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
        let msgModel = presenter.dataSource[indexPath.row]
        let cell:BaseTableViewCell? = tableView.dequeueReusableCell(withIdentifier: Self.cellIdentifier + "(msgModel.messageType.rawValue)") as? BaseTableViewCell
        cell?.msg = msgModel
        cell?.eventCallback = {
            [weak self](evenType,model) in
            guard let self = self else {
                return
            }
            //presenter新增dispatchEvent办法
            self.presenter.dispatchEvent(evenType, model)
        }
        return cell ?? UITableViewCell()
    }

由此看20行以内的代码 就完成了在func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell办法加载不同类型cell并且把工作呼应交给了presenter。最重要的工作是此刻无论增加多少种音讯类型每种音讯当中增加多少种工作回调cellforRow办法都不需求动任何代码,只需求在注册cell的办法 private func registCell()把对应的音讯类型注册进去就能够了

3. Presenter的改造

  1. 现在咱们还没处理所有时间呼应都是在Presenter中处理的窘境,代码臃肿不利保护的问题还是存在。为了处理这个问题我引入了Action这个概念,每个Action对应指定的一种音讯,该音讯的工作处理就在对应的Action中处理,每个Action遵从ChatActionProtocol协议

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构
2. 初始化init()注册Action

//MARK: 注册Action
    private mutating func registAction(){
        let  textActionKey = ViewController.cellIdentifier + "(MessageModel.IMMessageType.text.rawValue)"
        actionDic[textActionKey] = ChatTextAction()
        let imgActionKey = ViewController.cellIdentifier + "(MessageModel.IMMessageType.image.rawValue)"
        actionDic[imgActionKey] = ChatImageAction()
        let voiceActionKey = ViewController.cellIdentifier + "(MessageModel.IMMessageType.voice.rawValue)"
        actionDic[voiceActionKey] = ChatVoiceAction()
    }
  1. func dispatchEvent(_ evenType:ChatEvenType, _ msgModel: MessageModel)办法中找出对应的action对工作进行分发
func dispatchEvent(_ evenType:ChatEvenType, _ msgModel: MessageModel){
        let key = ViewController.cellIdentifier + "(msgModel.messageType.rawValue)"
        if actionDic.keys.contains(key), let action:ChatActionProtocol = actionDic[key]{
            action.dispatchEvent(evenType, msgModel)
        }
    }

4. Action中的完成

  1. ChatTextAction
import Foundation
struct ChatTextAction: ChatActionProtocol {
    func dispatchEvent(_ evenType: ChatEvenType, _ msgModel: MessageModel) {
        if let textModel = msgModel as? TextMsgModel {
            switch evenType {
            case .CellTapText:
                print("当时点击的文字是" + textModel.title)
            default:
                print("其他的工作疏忽")
            }
        } 
    }
}
  1. ChatImageAction
import Foundation
struct ChatImageAction:ChatActionProtocol {
    func dispatchEvent(_ evenType: ChatEvenType, _ msgModel: MessageModel) {
        if let imgModel = msgModel as? ImageMsgModel {
            switch evenType {
            case .CellImageLongPress:
                print("长按图片查看高清图")
            default:
                print("其他的工作疏忽")
            }
        }
    }
}

工作传递架构

一个有效解决多种类型cell带来的代码臃肿和事件处理不明确的设计架构

小结

至此IM事务架构构建完成,咱们能够看到在在新增音讯时候在func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell中咱们是不需求增加任何代码的,只需求把对应的cell注册就好完成用少代码完成杂乱功用的作用;另外工作分工上是清晰的,当咱们要修正或者处理音频相关的工作时,直接找到ChatVoiceAction即可,大大提高了代码的可保护度,以及降低了搭档之间的交流成本.因自己事务中是IM事务所以以IM事务侧举例,假如大家遇到相似的状况也能够参阅上述框架。

Demo gitee地址:gitee.com/liangaijun/…