iOS两个客户端代码复用小技巧

SangDahl 7年前
   <p>一般一个App只有一个客户端,因此也只有一份代码仓库,也就无所谓复用不复用。但两个客户端也不是没有,美团、饿了么等就分商家版和买家版,贝聊App也分为老师版和家长版两个客户端。有两个客户端代码复用就在所难免,比如基本的工具类,比如一些共用的业务。本文就以贝聊App为例,分享当有两个客户端时代码复用的小技巧。</p>    <h2>repository仓库划分</h2>    <p>贝聊APP远程仓库划分为三个,一个 家长端repository ,一个 老师端repository ,一个两端共用的 BLKit repository 。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/de196e021f5abaf57bf4d089f510580f.png"></p>    <p>家长端的本地 repository 包含远程的 家长端repository 和两端共用的 Kit repository 。</p>    <p>老师端的本地 repository 包含远程的 老师端repository 和两端共用的 Kit repository 。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7a25fc13b39ea0ed5a984df256dc5b6a.png"></p>    <h2>用submodule管理共用模块BLKit</h2>    <p>一个端要包括两个 repository ,而且这两个 repository 还是相互依赖,怎么管理也是个问题。</p>    <p>git 中 submodule 允许在一个 主git 中存在另外独立的 子git ,而且 主git 还能记录每一次 commit 中 子git 所在的 commit ,这是完美的仓库独立却又相互依赖的管理方案。</p>    <p>如下图:</p>    <p><img src="https://simg.open-open.com/show/150ea3e4fa1e703956d310c682ba5c5b.png"></p>    <h2>通过cocoapods将BLKit引入项目</h2>    <p>虽然利用 submodule 可以很好的管理共用模块 BLKit ,但又如何将 BLKit 引入到 project 中呢?</p>    <p>有两个方案,</p>    <p>直接加入主project</p>    <p>可以通过 add files to project ,选择所有 BLKit 中的文件,添加到 主project 中。但这样做的隐患非常大:</p>    <ul>     <li> <p>BLKit不独立</p> <p>BLKit 是两端共用模块,因此 BLKit 只能使用 BLKit 内部的类,否则在另外一个端中是不能编译的。如果 BLKit 的文件被添加到 主project 中,很容易在 BLKit 的某个文件中初始化了主项目中的一个类,导致 BLKit 很难维护。</p> </li>     <li> <p>BLKit不能更新</p> <p>BLKit 直接添加到 主project 中,如果一端有添加新文件,另外一端拉取下来后,还得手动通过 add files to project 将新添加的文件添加到 主project 中。如果每次只是添加一个文件还好,如果文件一多,想死的心都有。</p> </li>    </ul>    <p>通过cocoapods管理</p>    <p>将 BLKit 像其他第三方依赖库一样,用 cocoapods 管理,只不过 pods 的文件源连接到本地的 BLKit 仓库而已。</p>    <p>首先在主项目中的 submodule 文件夹中,创建一个 BLKit.podspec ,然后按照 podspec 的规则,填写 podsspec ,然后在主项目中的 Podfile ,像添加普通 pod 一样,将 BLKit 添加到 Podfile 中,</p>    <pre>  <code class="language-objectivec">pod 'BLKit', :path => "./BLKit/"  </code></pre>    <p>然后 pod update 即可,这时 BLKit 就像普通的第三方依赖库一样,出现在 pods 中:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b1ba16b3eda1e67eee995e2c9ceac6e8.jpg"></p>    <p>用 cocoapods 管理好处是很明显的:</p>    <ul>     <li>BLKit完全独立<br> BLKit 由于在 pods 中,不能引用主项目中的类,完全独立,不会出现一端更新,另外一端不能使用的情况。</li>     <li>BLKit自动更新<br> 由于 BLKit 其实就是个 pods 而已,如果 远程BLKit 有更新,拉取到本地后,运行 pod update --no-repo-update 即可。千万加上 --no-repo-update 这个选项,因为 pod update 很慢很慢很慢。(当然如果利用 proxychain , pod update 也很快,但实在没有必要为乐更新 BLKit 更新整个 pod )</li>    </ul>    <h2>如何在BLKit中引用主项目业务模块</h2>    <p>两端共用的模块,一般都是基础功能模块,比如网络模块,数据缓存模块、图片下载模块、视频发送模块等,都完全独立,与主项目没有耦合。但有时业务模块也有共用,比如聊天模块、大图浏览模块等。业务模块与主项目很难完全没有耦合,比如大图浏览模块长按时的操作逻辑,比如聊天模块长按消息的跳转逻辑,况且有时,有些业务模块被抽象成共用的,有些业务模块却没有,共用模块却要引用主项目中没有抽象成共用的业务模块。比如贝聊App中的聊天模块是共用的聊天模块,但大图浏览模块却不是共用的,因为历史原因,家长端和老师端各有各的实现,但点击聊天页面的图片消息,会进入大图浏览模块。</p>    <p>那如何在 BLKit 中引用主项目中业务模块?</p>    <p>以 BLKit 中的聊天模块为例,</p>    <pre>  <code class="language-objectivec">// 这个类在Pods中  class ChatViewController: UIViewController {      let chatID: String      init(chatID: String) {          self.chatID = chatID      }      // 其他业务代码        // Table Cell delegate      func didTapImageIn(cell: UITableViewCell) {          // 进入大图浏览模块          let allImages: [URL] = [self getAllMessageImages]      }  }  </code></pre>    <p>聊天的 Cell 的代理为 ChatViewController ,当点击到图片 Cell ,会调用代理方法 didTapImageIn(cell:) 。 ChatViewController 实现了这个代理方法,获取到所有图片的 URL 后,需要调用主项目中的大图浏览模块。但 Pods 中是不可能直接调用主项目中的方法的。所以需要一个桥梁。</p>    <p>定义一个信使协议</p>    <p>可以定义一个信使协议,然后在主项目中初始化 ChatViewController 时,传入具体实现这个信使协议的类。</p>    <pre>  <code class="language-objectivec">// pods中  // 信使协议  protocol ChatMessenger {      func openPhotoViewer(with photoes: [URL], in context: UIViewController)  }  </code></pre>    <p>然后主项目中创建一个类实现 ChatMessenger 协议,</p>    <pre>  <code class="language-objectivec">// 主项目中  class ChatMessengerConcrete: ChatMessenger {      func openPhotoViewer(with photoes: [URL], in context: UIViewController) {          // 具体调用主项目中的大图浏览模块      }  }  </code></pre>    <p>修改 ChatViewController 接受一个 ChatMessenger 作为初始化参数,然后在点击图片的 cell 代理方法中调用 ChatMessenger 的大图浏览:</p>    <pre>  <code class="language-objectivec">// 这个类在Pods中  class ChatViewController: UIViewController {      let chatMessenger: ChatMessenger      let chatID: String      // 初始化方法中传入ChatMessenger      init(chatMessenger: ChatMessenger, chatID: String) {          self.chatMessenger = chatMessenger          self.chatID = chatID      }      // 其他业务代码        // Table Cell delegate      func didTapImageIn(cell: UITableViewCell) {          let allImages: [URL] = [self getAllMessageImages]          // 进入大图浏览模块          chatMessenger.openPhotoViewer(with: allImages, in: self)      }  }  </code></pre>    <p>然后在主项目中的聊天入口,初始化 ChatViewController ,并传入实现 ChatMessenger 协议的 ChatMessengerConcrete :</p>    <pre>  <code class="language-objectivec">// 比如消息列表页  class ChatListViewController: UIViewController {      func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {          // 获取对应indexPath的聊天ID          let chatID = ""          let chatVC = ChatViewController(chatMessenger: ChatMessengerConcrete(), chatID: )          navigationController?.pushViewController(chatVC, animated: true)      }  }  </code></pre>    <p>使用协议扩展</p>    <p>虽然可以在主项目中聊天的入口处,传入 ChatMessenger 的实体类,达到在 Pods 中调用主项目业务模块,但如果聊天入口也都抽象到 Pods 中的 BLKit 中,那在 Pods 中是无法初始化一个主项目中的 ChatMessenger 实体类的。</p>    <p>这时可以利用 Swift 中的 protocol extension ,在主项目中给 Pods 中的 ChatMessenger 协议一个默认实现。</p>    <pre>  <code class="language-objectivec">// 主项目中  extension ChatMessenger {      func openPhotoViewer(with photoes: [URL], in context: UIViewController) {          // 具体调用主项目中的大图浏览模块      }  }  </code></pre>    <p>再让 Pods 中聊天页类 ChatViewController 遵循 ChatMessenger 协议,然后需要主项目中的业务直接调用对应的方法即可。</p>    <pre>  <code class="language-objectivec">// 这个类在Pods中  class ChatViewController: UIViewController, ChatMessenger {      let chatID: String      // 初始化方法中传入ChatMessenger      init(chatID: String) {          self.chatID = chatID      }      // 其他业务代码        // Table Cell delegate      func didTapImageIn(cell: UITableViewCell) {          let allImages: [URL] = [self getAllMessageImages]          // 进入大图浏览模块          openPhotoViewer(with: allImages, in: self)      }  }  </code></pre>    <p>协议扩展是实现 Pods 中调用主项目业务模块的最好方式。</p>    <h2>总结</h2>    <p>当有两个客户端时,</p>    <p>1)建立一个或多个两端共用的 repository ,管理两端相同的业务模块和功能模块</p>    <p>2)利用 submodule 在主项目中管理两端共用的 repository</p>    <p>3)利用私有 pods 将 submodule 中的共用代码引入 project 中</p>    <p>4)利用协议扩展轻松实现在 pods 中调用主项目中的业务模块</p>    <p> </p>    <p>来自:http://shellhue.github.io/2017/04/08/sharecode/</p>    <p> </p>