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中,作为开始,我们引入了以下内容:

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我们写:

$(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我们写:

$(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引入的第三方库在打包的时候,拷贝到特定目录。