本文讲解的是如何在一个仓库进行模块化,当然,如果你想拆分一个模块对应一个仓库,也行,改改就是了。
建议先仔细阅读 Mr.Casa 的《在现有工程中实施基于CTMediator的组件化方案》,本文魔改于此。
本文要点:
- Swift 项目怎样使用 Mediator 进行模块化
- 使用 CocoaPods 管理模块
- 一键创建模块(包括 Xcode 工程),一键发布
Pod库 - 发布私有库不使用
pod repo push,不进行验证,即使库编译不通过,也可以 release 新版本 - Swift 的一些实践心得
本文分为三部分:
一、目录解析
二、实战
三、实践心得
Lego
├── ConfigPrivatePod
├── Frameworks
│ ├── EggKit
│ ├── LegoKit
│ └── Networking
├── Modules
│ ├── Mediator_Door
│ ├── Door
│ ├── Lego
└── Specs
├── EggKit
├── LegoKit
└── Networking
└── 1.0.1
└── Networking.podspec
ConfigPrivatePod文件夹,存放着配置私有模块的脚本Frameworks文件夹,存放着各种framework,EggKit代表公司各项目通用库,LegoKit代表本项目各模块通用库Modules文件夹,存放着Lego项目的全部模块,其中Modules/Lego就是主模块Specs文件夹,存放着framework和模块的版本的podName.podspec,即常说的Private Spec Repo
ConfigPrivatePod
├── config.sh
└── pod-template
└── templates
├── Podfile
├── module
│ ├── extension
│ │ ├── Mediator_Project.swift
│ │ └── ProjectProtocol.swift
│ └── target
│ └── Target_Project.swift
├── pod.podspec
├── release.sh
├── update_version.sh
└── version_compare.sh
ConfigPrivatePod 文件夹内含快速配置私有模块的脚本
config.sh 新创建 Pod库 时使用,作用:
- 创建 Xcode 工程(注:原理和
pod lib create NAME一致) - 为
Pod库配置基本文件,如pod.podspec - 配置
release.sh脚步,需要为Pod库发布新版本时,直接敲命令./release.sh即可 - 为模块配置与
Mediator关联的文件,如:Target_Project.swift,ProjectProtocol.swift,Mediator_Project.swift
大家都知道 CocoaPods 可以指定第三方依赖的版本,比如:pod 'MonkeyKing', '~> 1.2.1'
那 CocoaPods 是如何管理所有已经发布了的版本? 答案就在此 CocoaPods Specs 仓库。Specs 仓库里面存放着所有已经发布了的版本。
比如 MonkeyKing 的已发布的版本 Specs,在此 Specs 里面,列举 MonkeyKing 所有已经 released 的版本。
MonkeyKing 在 Specs 文件夹的呈现:
└── Specs
└── MonkeyKing
└── 0.0.1
└── MonkeyKing.podspec.json
.......
└── 0.9.3
└── MonkeyKing.podspec.json
└── 1.1.0
└── MonkeyKing.podspec.json
└── 1.2.0
└── MonkeyKing.podspec.json
└── 1.2.1
└── MonkeyKing.podspec.json
└── 1.2.2
└── MonkeyKing.podspec.json
https://github.com/CocoaPods/Specs.git 是 CocoaPods 官方的 Specs 仓库,平时我们用 pod trunk push podName.podspec 来发布新版本,其实就是向此仓库添加一个名为 podName.podspec 的文件。
CocoaPods Specs 是线上版,在我们本地,其实也有这个仓库,执行下面的命令就可以看到,其中 master 对应 CocoaPods 官方的 Specs 仓库。
cd ~/.cocoapods/repos && ls
而在此教程的 Lego 仓库里,有一个文件夹 Specs,此文件夹里存放着 framework 和模块 Pod 的 podspec,相当于 Private Spec Repo
更多详情请查看官方资料:Private Pods
-
新建一个仓库,就先名为
Lego,页面先不急着关,打开Terminal -
pod repo add [私有Pod源仓库名字] [私有Pod源的repo地址],添加私有源到本地 -
把刚刚新建的仓库
clone到本地,把 ConfigPrivatePod 文件夹放进去 -
在
./ConfigPrivatePod/config.sh文件里,填写httpsRepo,sshRepo,specsRepo,homePage,author,email,具体参考当然,在这里填写这4个默认参数的前提是,想把全部模块都放在同一个
仓库里面,如果打算一个模块一个仓库,请参考 Mr.Casa 的《在现有工程中实施基于CTMediator的组件化方案》 -
新建三个文件夹
FrameworksModulesSpecs,最终的结构如下:
Lego
├── ConfigPrivatePod
│ ├── config.sh
│ └── templates
│ └── pod-template
│ ......
├── Frameworks
├── Modules
└── Specs
使用 Xcode 创建一个名为 Lego 的工程,放在 Lego/Modules 下,此工程就是我们的主模块。
若想新建一个 Door 模块,需要两个 Pod库,
- Door业务Pod
- 方便其它模块调用 Door业务 的 Mediator_Door 的 Pod。
这里多解释一句:Mediator_Door Pod 本质上只是一个方便方法,它对 Door Pod 不存在任何依赖
开始创建:
-
同创建主模块一样,创建一个名为
Door的工程,放在Lego/Modules下,此模块主打注册登录。 -
再创建一个名为
Mediator_Door的工程,同样放在Lego/Modules下,此工程主要为了方便其它模块调用 Door业务,本质就是通过Mediator利用运行时,找到在Door内相对应的方法。 -
在
ConfigPrivatePod下,执行./config.sh,脚本会问你要一些信息。配置
Door工程:Enter Project Name: Door ================================================ 1 : Module 2 : Extension 3 : Framework ================================================ Enter Project Type Number: 1配置
Mediator_Door工程:Enter Project Name: Door // 注:Project Name 也是 Door ================================================ 1 : Module 2 : Extension 3 : Framework ================================================ Enter Project Type Number: 2若配置模块工程,
Project Type Number输入1,若配置模块Extension工程,输入2,若配置普通的framework输入3配置完
Door工程后,在Modules/Door/Door下,会多了一个也同样名为Door的文件夹,以后所有需要打包出去给别人用的都在此文件夹下,因为在Door.podspec文件内定义的源文件就指指定了此文件夹。 -
打开
Door.xcodeproj,把Modules/Door/Door/Door此文件夹工程里面。Mediator_Door工程同理。Door工程的目录简如下:Door ├── Door │ └── Door │ ├── WelcomeViewController.swift │ └── Targets │ ├── Target_Door.swift ├── Door.xcodeprojMediator_Door工程的目录简如下:Mediator_Door ├── Mediator_Door │ └── Mediator_Door │ ├── Door.swift │ ├── Mediator_Door.swift ├── Mediator_Door.xcodeproj
到此,Door模块 基本配置完成,可以前往 主模块Lego 引入它了。
Modules/Lego/Podfile:
source 'https://github.com/Limon-O-O/Lego.git' # 这是 Specs 仓库的地址,在本文中,和项目的仓库地址一致
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
target 'Lego' do
use_frameworks!
# Door,在开发初期,先用相对路径,因为现在 CocoaPods 支持跨工程修改,便于开发
pod "Mediator_Door", :path => "../Mediator_Door"
pod "Door", :path => "../Door"
# Me,若模块已经开发得差不多了,并 released 了,可从 Private Spec 拉取
pod "Mediator_Me"
pod "Me", '~> 1.0.1'
end
在 Modules/Door 下,敲命令 ./release.sh,脚本会问你需要发布的版本号,仅需要输入一个版本号,其它的脚本都帮你做好了。
发布成功之后,还需要更新 Private Spec,命令:pod repo update [Name],这个 [Name] 就是 私有Pod源的名字,在上面我们用命令 pod repo add [私有Pod源仓库名字] [私有Pod源的repo地址] 添加了的。也可以 cd ~/.cocoapods/repos && ls 查看
release.sh 主要作用有:
- 更新
Door.podspec - 更新
README.md - 更新
Info.plist的版本号 - 复制一份
Door.podspec到Specs文件夹 git push相关变动到远程仓库
pod install时遇到Unable to find a specification for [PodName],就是没执行'pod repo update [NAME]'
-
网络层,
LegoProvider+LegoAPI,全部API都放在一个Pod库内,方便测试。Networking和API分开。 -
UserDefaults分模块,也稍微避免UserDefaults.standard存储大量数据之后,导致读写慢UserDefaults(suiteName: "top.limon.door") UserDefaults(suiteName: "top.limon.lego")如果模块之间需要传递
UserDefaults的值,通过Mediator调度,不直接公开UserDefaults。 -
通过
Mediator传递的Data必须是NSObject,不然崩溃extension Target_Door { func Action_DidLogin() -> [String: Any] { return ["result": DoorUserDefaults.didLogin] } func Action_DidLogin() -> Bool { // Bool 不是 NSObject,崩溃 return true } }然并卵,
[String: Any]理论上是AnyObject,但却不崩溃,难道自动转成了NSDictionary?如果返回
Bool,崩溃信息:unrecognized selector sent to instance,若想更深入探讨,可运行 God项目 进行测试 -
使用
Mediator进行模块化,避免不了Hard Code,特别是在模块之间的通讯时,建议Hard Code尽量写在Extension内,比如Mediator_Door.swift的deliverParams的navigationBarHidden,callbackActionextension Door where Base: Mediator { public func welcomeViewController(_ navigationBarHidden: Bool = true, _ callbackAction: @escaping (([String: Any]) -> Void)) -> UIViewController? { let deliverParams: [String: Any] = ["navigationBarHidden": navigationBarHidden, "callbackAction": callbackAction] return base.performTarget("Door", action: "WelcomeViewController", params: deliverParams) as? UIViewController } } -
使用
Storyboard Reference连接其它模块的Storyboard,注意Bundle的填写 -
使用
Protocol+Extension更好地区分作用域,Mediator.shared.door.accessToken(),其中的door是不是挺好看的 🌝,而不是Mediator.shared.door_accessToken() -
关于脚本创建 Xcode 工程,使用的是
CocoaPods的 pod-template,另外也可以使用 Xcodeproj 或 liftoff注:平时用
pod lib create NAME创建pod,就是拉取 pod-template 来创建 Xcode 工程pod lib create TestPod Cloning `https://github.com/CocoaPods/pod-template.git` into `TestPod`. Configuring TestPod template.
