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

purplesnake 发布于9天前 阅读18次
0 条评论

一般一个App只有一个客户端,因此也只有一份代码仓库,也就无所谓复用不复用。但两个客户端也不是没有,美团、饿了么等就分商家版和买家版,贝聊App也分为老师版和家长版两个客户端。有两个客户端代码复用就在所难免,比如基本的工具类,比如一些共用的业务。本文就以贝聊App为例,分享当有两个客户端时代码复用的小技巧。


repository仓库划分

贝聊APP远程仓库划分为三个,一个家长端repository,一个老师端repository,一个两端共用的BLKit repository。

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

家长端的本地repository包含远程的家长端repository和两端共用的Kit repository。

老师端的本地repository包含远程的老师端repository和两端共用的Kit repository。

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

用submodule管理共用模块BLKit

一个端要包括两个repository,而且这两个repository还是相互依赖,怎么管理也是个问题。

git中submodule允许在一个主git中存在另外独立的子git,而且主git还能记录每一次commit中子git所在的commit,这是完美的仓库独立却又相互依赖的管理方案。

如下图:

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

通过cocoapods将BLKit引入项目

虽然利用submodule可以很好的管理共用模块BLKit,但又如何将BLKit引入到project中呢?

有两个方案,

直接加入主project

可以通过add files to project,选择所有BLKit中的文件,添加到主project中。但这样做的隐患非常大:

1、BLKit不独立

BLKit是两端共用模块,因此BLKit只能使用BLKit内部的类,否则在另外一个端中是不能编译的。如果BLKit的文件被添加到主project中,很容易在BLKit的某个文件中初始化了主项目中的一个类,导致BLKit很难维护。

2、BLKit不能更新

BLKit直接添加到主project中,如果一端有添加新文件,另外一端拉取下来后,还得手动通过add files to project将新添加的文件添加到主project中。如果每次只是添加一个文件还好,如果文件一多,想死的心都有。

通过cocoapods管理

将BLKit像其他第三方依赖库一样,用cocoapods管理,只不过pods的文件源连接到本地的BLKit仓库而已。

首先在主项目中的submodule文件夹中,创建一个BLKit.podspec,然后按照podspec的规则,填写podsspec,然后在主项目中的Podfile,像添加普通pod一样,将BLKit添加到Podfile中,

pod 'BLKit', :path => "./BLKit/"

然后pod update即可,这时BLKit就像普通的第三方依赖库一样,出现在pods中:

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

用cocoapods管理好处是很明显的:

1、BLKit完全独立

BLKit由于在pods中,不能引用主项目中的类,完全独立,不会出现一端更新,另外一端不能使用的情况。

2、BLKit自动更新

由于BLKit其实就是个pods而已,如果远程BLKit有更新,拉取到本地后,运行pod update --no-repo-update 即可。千万加上--no-repo-update这个选项,因为pod update很慢很慢很慢。(当然如果利用proxychain,pod update也很快,但实在没有必要为乐更新BLKit更新整个pod)


如何在BLKit中引用主项目业务模块

两端共用的模块,一般都是基础功能模块,比如网络模块,数据缓存模块、图片下载模块、视频发送模块等,都完全独立,与主项目没有耦合。但有时业务模块也有共用,比如聊天模块、大图浏览模块等。业务模块与主项目很难完全没有耦合,比如大图浏览模块长按时的操作逻辑,比如聊天模块长按消息的跳转逻辑,况且有时,有些业务模块被抽象成共用的,有些业务模块却没有,共用模块却要引用主项目中没有抽象成共用的业务模块。比如贝聊App中的聊天模块是共用的聊天模块,但大图浏览模块却不是共用的,因为历史原因,家长端和老师端各有各的实现,但点击聊天页面的图片消息,会进入大图浏览模块。


那如何在BLKit中引用主项目中业务模块?

以BLKit中的聊天模块为例,

// 这个类在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]
    }
}


聊天的Cell的代理为ChatViewController,当点击到图片Cell,会调用代理方法 didTapImageIn(cell:)。ChatViewController实现了这个代理方法,获取到所有图片的URL后,需要调用主项目中的大图浏览模块。但Pods中是不可能直接调用主项目中的方法的。所以需要一个桥梁。

定义一个信使协议

可以定义一个信使协议,然后在主项目中初始化ChatViewController时,传入具体实现这个信使协议的类。

// pods中
// 信使协议
protocol ChatMessenger {
    func openPhotoViewer(with photoes: [URL], in context: UIViewController)
}
然后主项目中创建一个类实现ChatMessenger协议,
// 主项目中
class ChatMessengerConcrete: ChatMessenger {
    func openPhotoViewer(with photoes: [URL], in context: UIViewController) {
        // 具体调用主项目中的大图浏览模块
    }
}

修改ChatViewController接受一个ChatMessenger作为初始化参数,然后在点击图片的cell代理方法中调用ChatMessenger的大图浏览:

// 这个类在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)
    }
}

 

然后在主项目中的聊天入口,初始化ChatViewController,并传入实现ChatMessenger协议的ChatMessengerConcrete:

// 比如消息列表页
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)
    }
}

 


使用协议扩展

虽然可以在主项目中聊天的入口处,传入ChatMessenger的实体类,达到在Pods中调用主项目业务模块,但如果聊天入口也都抽象到Pods中的BLKit中,那在Pods中是无法初始化一个主项目中的ChatMessenger实体类的。

这时可以利用Swift中的protocol extension,在主项目中给Pods中的ChatMessenger协议一个默认实现。

// 主项目中
extension ChatMessenger {
    func openPhotoViewer(with photoes: [URL], in context: UIViewController) {
        // 具体调用主项目中的大图浏览模块
    }
}

再让Pods中聊天页类ChatViewController遵循ChatMessenger协议,然后需要主项目中的业务直接调用对应的方法即可。

// 这个类在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)
    }
}

 

协议扩展是实现Pods中调用主项目业务模块的最好方式。


总结

当有两个客户端时,

1)建立一个或多个两端共用的repository,管理两端相同的业务模块和功能模块

2)利用submodule在主项目中管理两端共用的repository

3)利用私有pods将submodule中的共用代码引入project中

4)利用协议扩展轻松实现在pods中调用主项目中的业务模块

免责声明:
杰微刊遵循行业规范,任何转载的稿件都会明确标注来源和链接。
转载目的在于传递更多信息,并不代表杰微刊赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本网联系,我们将在第一时间删除内容。
杰微刊的原创文章,请转载时务必注明文章作者、链接和"来源:杰微刊"。

查看原文: iOS两个客户端代码复用小技巧

共收到0条回复

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。