本文讲解的是如何在一个仓库进行模块化,当然,如果你想拆分一个模块对应一个仓库,也行,改改就是了。
建议先仔细阅读 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的组件化方案》 -
新建三个文件夹
Frameworks
Modules
Specs
,最终的结构如下:
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.xcodeproj
Mediator_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
,callbackAction
extension 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.