App 的工程结构

首先,我们从工程结构开始。构成整个项目的文件,一共可以分成五大类,分别是:

  • A - 和 App 启动相关的代码、配置以及资源;
  • B - 和 UI 的显示以及交互相关的代码;
  • C - 对 UIKit 的公共扩展;
  • D - 存储数据的 model;
  • E - 使用的第三方库;

接下来,我们可以新建一个 Single View Application 项目,基于它来构建整个工程。

A

对于 A 这部分,主要包含 App 启动相关的代码、配置以及资源:

  • AppDelegate:在这里我们定义 App 的启动流程;
  • Info.plist:在这里我们配置 App 的行为;
  • LaunchScreen.storyboard:在这里我们定义 App 的启动屏幕;
  • Localizable.strings:这里我们定义支持多语言的各种翻译;

这是初始 Single View Application 模板被删减后的部分
avatar

可以看到,我们删掉了默认的 Main.storyboard,并在 Info.plist 中去掉了 Main storyboard file base name 的配置。在整个 App 里,我们将使用代码来实现 UI。

AppDelegate.swift 中是 App 的入口,由于删掉了 Main.storyboard,我们将专门准备一节来讨论相关的内容。

BxLaunchScreen.storyboard 是 App 的启动 UI,别忘了在 Boxue Target 中也设置成对应的文件:
avatar

Localizable.strings 则是多语言文件,同样,我们会单独用一节来分享 Boxue App 中使用的多语言处理方法。

B / C / D

对于 B / C / D 这三部分,由于它们彼此独立,为了可以同步开发,并让 C 和 D 的代码可重用,我们把这三部分内容定义成了三个 framework。其中:

  • Boxue_iOS 里是和 UI 的显示以及交互相关的代码;
  • BoxueUIKit 是整个项目中会用到的对 UIKit 的公共扩展;
  • BoxueDataKit 是整个项目的数据存储以及访问接口,也可以理解为是 App 的 View Model 以及 Model;

在项目的 Targets 列表中,可以看到 app 以及这三个 framework,如果你要自己动手练习,只要在自己的项目中,点击 Targets 列表左下角的 +,然后选择添加 CocoaTouch framework 就好了。

B

是 Boxue_iOS,这个 framework 中都是处理 UI 显示以及交互的代码,它的使用者,是刚才我们提到的 Boxue Target。这里,iOSApp group 包含了全部 iOS 相关的实现。
avatar

在这个 group 里,处在根目录的文件有三类:

  • 一个是 BoxueAppDepedencyContainer.swift,它主要处理对象的初始化工作,稍后我们会专门用几个小节讨论依赖注入和容器的话题;
  • 一类是处理 App 从启动阶段到注册前交互的 View Controllers;
  • 还有一类是和 View Controller 对应的 View;

遵循这三个分类,我们可以进一步在这个 group 中创建 sub group,例如上图中的 PreSignIn group 就表示所有处理登录前 UI 显示和交互的 View Controllers。而等我们开发到后期的时候,登录后 UI 的处理也可以采用同样的模式。

同样,大家现在只要知道这些文件的功能就好,大可不必关心其中的实现,我们会在后面的内容中专门讲述代码的细节。

C

BoxueUIKit 这个 framework,可以理解为是我们的 UIKit 工具库,包含了项目中使用的对 UIKit 的各种扩展、工具类和工具函数。它的使用者,则是 Boxue_iOS Target。

avatar

在这个 group 里,Reusable / UIKit 这个 sub group 中,是对 UIKit 的扩展,我们可以按照功能分类继续在其中创建 sub group,例如我们当前的:

  • Style group 是对样式相关类的扩展;
  • Nibless group 是为了纯代码实现 View Controller 对各种 View Controllers 以及 View 类的扩展;
  • 理解了这个思路之后,我们就可以用这种方法,把这个 framework 的各种接口组织起来了。慢慢地,我们开发的功能越多,这个 framework 功能就会越丰富。当我们切换到其它项目中时,也能很方便的重用这些积累起来的功能。

D

最后一个 framework 是 BoxueDataKit,这个 framework 包含了两类内容:一类是直接和服务端打交道的 Model;另一类,是和 View Controllers 打交道的 View Model。因此,BoxueDataKit 中包含了两个 sub group,DataLayer 表示 Model,PresentLayer 表示 View Model:
avatar

但在一个实际的项目中,除了 Model 和 View Model 之外,还有一类内容,就是围绕 Model 要完成的各种业务逻辑。例如,围绕着用户信息的注册、登录和改密码;或者,围绕着用户 Session 的读取和删除,都属于此类。显然,把处理这些功能的接口直接写在 Model 里是不合适的。为此,我们还在 Model group 中新建了一个 Repositories 的 sub group,这里,就是围绕着 Model 要定义的各种业务逻辑接口,例如 Remote group 表示所有的远程访问 API,Persistence group * 表示本地持久化存储数据 API 等等。当然,我们现在只要知道这样做的意图就好了,完全没必要关心代码的细节。

而相比 DataLayer,PresentLayer 的内容倒是简单很多。基本上,App 的每一屏,都会对应一个 View Model 文件。并且,功能相关的一组 View Models,我们还可以给它们创建一个公共的 subgroup。例如用于支持引导用户注册、登录或直接浏览功能的 View Model,就统一放在了 Guide group 里,这样方便维护。

E

对于项目中使用的第三方库,我们选择了 Carthage。在项目根目录的 Cartfile 中,作为开始,我们引入了以下内容:

plaintext
github "mxcl/PromiseKit" ~> 6.4.0
github "onevcat/Kingfisher" ~> 4.9.0
github "ReactiveX/RxSwift" ~> 4.3.0

其中:

  • PromiseKit 用于简化异步操作的代码;
  • Kingfisher 用于缓存下载的图片
  • RxSwift 用于管理 App 中的事件

作为开始,暂时就包含这 3 个,以后随着开发用到,我们再添加就好了。添加完成后,我们要在项目根目录执行:

carthage update --platform iOS --no-use-binaries

来安装所有的依赖关系,这里,–no-use-binaries 很重要,如果不带这个参数直接安装 Carthage 默认的 framework,当你使用 LLDB 调试的时候,就会出现类似 error: Couldn’t IRGen expression 这样的提示,这是我们要注意的地方。

完成后,在 TARGETS 列表中,选中 Boxue,在 General tab 的最底部就会看到,Embeded binary 包含了我们自己创建的三个 frameworks,Linked Frameworks and Libraries 则包含了我们自己创建的三个 frameworks 以及通过 Carthage 引入的三个 frameworks:
avatar

接下来,保持 Boxue TARGET 选中,切换到 Build Phases tab,在这里,点击 +,选择 New Run Script Phase,这时,Xcode 就会要求我们填写一些东西。
avatar

其中,执行的命令,我们写:

/usr/local/bin/carthage copy-frameworks

Input Files 我们写:

plaintext
$(SRCROOT)/Carthage/Build/iOS/Kingfisher.framework
$(SRCROOT)/Carthage/Build/iOS/RxSwift.framework
$(SRCROOT)/Carthage/Build/iOS/RxCocoa.framework
$(SRCROOT)/Carthage/Build/iOS/PromiseKit.framework

Output Files 我们写:

plaintext
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Kingfisher.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxSwift.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RxRocoa.framework
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/PromiseKit.framework

完成后,看起来是这个样子的:
avatar

这个命令的作用就是把之前我们用 Carthage 引入的第三方库在打包的时候,拷贝到特定目录。