1. iOS模块化第一部分: 扼杀庞然大物

大多数iOS工程师都熟悉iOS Monolith。我指的是一个代码库,它要么是一个单独的项目本身,要么是由一个只包含一个项目(可能加上一个Pods项目)的工作区(workspace)组成。

你的工作区是这样的吗?

avatar

In the example above, that FunkySocial project will become pretty big, pretty quickly, if other popular social media apps are to go by!

在上面的例子中,FunkySocial项目将变得非常大!

该项目可能包括新闻feed、用户简介、好友列表、事件、群组、照片、视频、第三方内容查看、消息和贴图等功能。这样的例子不胜枚举。

当我们开始开发任何应用时,我们很少能够预见到应用的发展规模。所以在一个项目中构建所有东西是很常见的。对于那些从一开始就计划模块化工作空间的人来说,那真是太棒了!👏

对于我们其他人来说,我们的单一项目将会失去控制,这时我们应该考虑“扼杀”我们的整体,将其分割成单独的功能模块。这类似于我们的平台工程师同事所参考的微服务架构。

“Strangling” in this sense is a term that Martin Fowler uses to describe code migration, gradually creating a new system around an old system, until the old system is strangled of functionality and therefore becomes redundant.

从这个意义上说,“扼杀”是Martin Fowler用来描述代码迁移的术语,即在旧系统周围逐渐创建一个新系统,直到旧系统在功能上被扼杀,从而成为冗余。

我以类似的方式使用它来描述将iOS整体元素迁移到功能模块中。 逐渐从整体中提取功能,每次创建一个功能模块,直到主应用(之前是一个整体)被所有的责任所窒息。

avatar

上图显示了我们如何将代码组织成功能的垂直切片。

即使只有一个项目代码库,您可能已经有了类似于上图的架构分离。但是在单独的Xcode项目中显式地进行这种分离也有好处。

1.1. Not a new concept, but…

这一概念在iOS中并不新鲜,但Swift的引入无疑让它变得更简单。在Objective-C中,将内部作用域与公共作用域分开总是有点麻烦(每个都需要单独的头文件)。

使用Swift,在整个模块中公开内部功能非常简单。默认情况下,我们所有的模块代码都是内部的,所以我们被迫限制向其他模块和主应用公开的内容。

1.2. Layers in detail

Main App

主应用程序包含了应用程序委托,仅此而已!它还可能包含一些编译时配置,稍后将注入到feature模块中。 主应用程序也可能包含某种根视图控制器,可能是(tabbar)标签栏控制器,这取决于应用程序的类型。

Feature

每个垂直的Feature模块是完全独立的。您的特性模块应该尽可能少地公开公共功能。例如,如果你的应用程序是基于标签的,你应该公开一个“Router”对象,你的主应用程序可以嵌入它。 就是这样。有时您的用户旅程涉及在不同模块的视图之间导航。在这种情况下,你可以将导航委托给Main App项目中的“Router”对象。

Shared

可能有一些模块需要共享功能。例如,您可能有一个NewsFeed模块和一个Profile模块,它们都依赖于一个video模块。但是您可能有一个Auth模块不需要这种依赖关系,所以您限制了依赖于Shared功能的特性模块。

您可能需要在模块之间共享模型对象。您可以考虑将模块化代码库分解为单独的UI和模型模块,如VideosUI和Videos(如在iOS SDK中看到的,如Photos和PhotosUI, Contacts和ContactsUI等)。

Common

而且很可能会有公开给整个代码库的公共功能。您可以在这里公开公共助手对象和扩展。

Benefits

这样做的好处是很多的。它强制功能之间的关注点分离,因为默认情况下所有内容都具有内部访问级别,并且您必须显式地将模块添加为依赖项。

可以使用不同的体系结构编写单独的模块。例如,您可能在一个模块中遵循MVVM,而在另一个模块中遵循VIPER。如果您正在尝试一种新的架构方法,这是很好的。模块甚至可以用不同的语言编写。 如果你有一个大型的Objective-C代码库,你可能会考虑扼杀它,并将单个模块迁移到Swift。

通过拥有一个模块化的代码库,你也可以将模块单独迁移到Swift的下一个版本。您不必一次性迁移整个代码库。

对我最有价值的好处是使模块重用变得更容易。 将你的工作空间划分为功能模块可能是将这些功能划分为单独的存储库以供其他应用使用的一步。或者您甚至可以从相同的存储库构建一个二级应用程序。请看下图。

avatar

在我们的社交媒体应用程序示例中,我们可能会从相同的代码库构建一个消息应用程序。二级应用程序只包括应用程序委托和其他一些东西,但它可以依赖于它需要的任何特性模块。

1.3. The result!

结果就是,你最终会从左图所示的整体空间,转向右图所示的美丽的模块化工作空间:

avatar

注意,在特性模块中可能有模型对象。只有那些模型对象需要在特性模块之间共享,而这些特性模块需要存在于单独的模块中。

1.4. Coming up next

在本系列的下一篇文章中,我将详细讨论分割代码库的实现细节。在后续的文章中,我将介绍跨多个模块进行测试、设置依赖关系管理和在模块之间共享配置。