开源项目源码分析(Kickstarter-iOS )(一)
1.Kickstarter开源项目简介
- 2016年12月15日,知名众筹平台Kickstarter在工程博客中宣布,将开源Android和iOS端的源代码,从而为初创企业提供更多的便利和帮助。由于这个伟大的决定我们这些小白才能有机会学习大神之作,Kickstarter是一个非常NB的项目,值得我们去研究它的源码:点击这里下载Kickstarter项目源码
- Kickstarter IOS app源码下载
- Kickstarter Android app源码下载
我们自己去啃这些开源源码是有一点难度的,这里介绍一本很好的书籍: Raywenderlich 的一本书 《Advanced iOS App Architecture》在介绍 MVVM 架构的时候,说到 Kickstarter 很彻底地遵循了 MVVM 架构。
-
首先第一感觉是代码非常令人爽心悦目,因为代码非常整洁。再仔细一看,发现里面有很多值得学习的地方,项目使用swift5.0编写,真正的使用MVVM架构模式,架构清晰,非常值得学习。
-
下面看几张项目架构图片:
-
用到的框架:
-
用到的第三方工具:
-
MVVM模式
2. Kickstarter项目结构
2.1 Makefile 文件
- 在把项目 clone 下来之后,我们一般首先会想着怎么把它运行起来。在项目的 readme 中的 Getting Started 我们可以看到,运行 make bootstrap安装工具和依赖,运行 make test-all 构建项目并进行测试。而这两个命令就是在 Makefile 中定义的。
ios git clone地址:https://github.com/kickstarter/ios-oss
android git clone地址:https://github.com/kickstarter/android-oss
- 打开 Makefile 文件,我们可以从中看到:1)文件的开头定义了各种变量;2)剩下的是项目中用到的命令。我们以 make bootstrap 为例:
bootstrap: hooks dependencies
brew update || brew update
brew unlink swiftlint || true
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/686375d8bc672a439ca9fcf27794a394239b3ee6/Formula/swiftlint.rb
brew switch swiftlint 0.29.2
brew link --overwrite swiftlint
- 执行 make bootstrap ,就会依次执行 bootstrap 下面包含的所有命令。
- 使用 Makefile 的好处是,我们可以把项目相关的一些命令操作都放到这个文件,即便是刚刚接手项目的同事也一目了然。
2.2 Git submodule
- 下载源码用xcode打开后你肯定会好奇,这么NB的工程居然没有用cocoapod,那么项目没有使用第三方框架么?
- 答案是否定的。把项目 clone 下来之后,我们确实会发现文件夹里面没有我们常用的
Podfile
和xcworkspace
文件。然而,Kickstarter 不是用Cocoapods
来管理第三方库的,而是使用git submodule
。 - 其实除了上面提到的两个管理第三方框架的工具之后,还可以用
Carthage
来管理第三方库。找到一篇文章:对比Carthage和Cocoapods和Git submodule,描述了这三种工具的优缺点。 - 至于选择哪一种,就看我们更看重的是什么了。我一般都是使用的cocoapods.
2.3 脚本工具
- 在根目录下的 bin 目录,我们可以看到两个用 Swift 编写的脚本:
ColorScript
和StringsScript
。
2.3.1 ColorScript脚本
- 开发者把项目中用到的颜色,保存在Colors.json文件,然后通过 ColorScript 转换成 Colors.swift文件。开发者在使用的时候只需要通过 UIColor.ksr_dark_grey_400就能得到相应的颜色了。后续如果 UI 设计师想要微调颜色,直接修改颜色, json 中的 key 的值不变,我们只需要重新生成 Colors.swift就都搞定了,而不需要更改代码。
{
"apricot_600": "FFCBA9",
"cobalt_500": "4C6CF8",
"dark_grey_400": "9B9E9E",
"dark_grey_500": "656868",
"facebookBlue": "3B5998",
...
}
- 这种统一管理颜色的方法,我觉得其实就是把颜色管理的工作交给 UI 设计师了。设计师写好
json
文件,交给开发者,开发者用脚本生成Colors.swift
,就一切都搞定了(如果颜色名字有变动或有新添加的颜色,还是需要开发者手动更改和添加)。如果不通过这种方法去做,而是开发者自己手动去写,那么可能会经常去手动修改Colors.swift
,这样就麻烦一些。
2.3.2 StringsScript脚本
- 做过国际化的开发者应该知道,如果不通过其他处理的话,我们需要通过
NSLocalizedString("Hello_World", comment: "")
去获取对应的本地化字符串,这种写法非常麻烦,而且很容易出错。 - 在 Kickstarter-iOS 中,开发者用
StringsScript
把Localizable.strings
转换生成Strings.swift
文件,然后我们在使用的时候,就可以像这样去获取想要的字符串Strings. Hello_World()
。这个脚本把 key 变成了方法名,让我们避免了在使用的时候出现错误,而且使用起来非常方便。 - 如果有做本地化的项目,采用这种方法可以给开发者带来很大的便利。
2.4 测试工具
- 测试,是软件开发中非常重要的一个环节。甚至有些公司执行 TDD (测试驱动开发(Test-Driven Development)),可以见测试的重要性。
- 在 Kickstarter-iOS 中,我们可以看到大量的 xxxTests.swift文件,包括了
Unit Test
和UI Test
。
2.5 独立的代码库
- 用 Xcode 打开 Kickstarter-iOS 的项目,你会发现
KsApi
、Library
和LiveStream
这三个文件夹不是存放在 Kickstarter-iOS文件夹里面的,而是跟它处于同一个目录。因为这三个文件夹存放的是独立于 Kickstarter-iOS 之外的 framework
- 这么做的好处当然是代码可以复用。目前我看 iPad 上的 Kickstarter 应用是跟 iPhone 共用一个的,如果以后要为 iPad 单独做一个 app,这三个 frameworks 就可以直接拿过去用。
3. Kickstarter项目MVVM架构
3.1 MVVM架构思想简介
-
MVVM架构思想
这里有一个讲解 MVVM & TDD 的视频。感兴趣的可以看一下。 -
函数响应式编程思想
代表主要有Rxswift, RAC, ReactiveSwift -
ReactiveSwift 是一个响应式编程的库,与 RxSwift 类似,这两个库非常适用于 MVVM 架构。至于要选择哪一种,可以先去了解下他们的差别,然后再决定.这里有篇很好的文章讲解了他们的区别:How does ReactiveSwift relate to RxSwift?
-
Kickstarter-iOS 把
MVVM
模式贯彻地非常彻底。MVVM 的全称是 Model-View-ViewModel,所以我们可能会觉得要有View
存在的地方,才可以用ViewModel
。但是 Kickstarter-iOS 在AppDelegate
中也使用了ViewModel
,把很多在AppDelegate
处理的逻辑剥离到AppDelegateViewModelType
中。
3.2 MVVM架构实际运用
3.2.1 使用 ReactiveSwift
- 我们平常很多项目一般都是使用Rxswift + MVVM架构模式这样搭配,然后会用到网络库Alamofire + Moya + Rxswift .数据库一般用FMDB.
- 响应式编程非常适合 MVVM 架构。在 ViewModel 中,我们通常会使用 ReactiveSwift 或者 RxSwift 去定义一些属性,然后在 UIView 和 UIViewController中的 bindViewModel() 方法里面订阅那些属性的变化,然后更新 UI。
- Kickstarter-iOS项目基本就是用 ReactiveSwift + MVVM这种。
3.2.2 UIView
- 对于
UIView
,Kickstarter 通过扩展重写awakeFromNib()
,在内部调用bindViewModel()
。代码如下:
extension UIView {
open override func awakeFromNib() {
super.awakeFromNib()
self.bindViewModel()
}
@objc open func bindViewModel() {
}
}
- 因为 Kickstarter 在整个项目中都是通过 xib 来构建 UI 的,所以 UI 在初始化时,
awakeFromNib()
会被调用,从而bindViewModel()
也被调用。那么在其他继承自UIView
的view
中,只需要重写bindViewModel()
,就能达到绑定ViewModel
的目的。
3.2.3 UIViewController
-
在
UIViewController
中就会稍微复杂一点。Kickstarter 通过runtime
,默认在viewDidLoad()
中调用bindViewModel()
。那么在其他继承自UIViewController
的ViewController
中,只需要重写bindViewModel()
,就能达到绑定ViewModel
的目的。 -
UIViewController-Preparation.swift
相关代码如下:
private func swizzle(_ vc: UIViewController.Type) {
[
(#selector(vc.viewDidLoad), #selector(vc.ksr_viewDidLoad)),
(#selector(vc.viewWillAppear(_:)), #selector(vc.ksr_viewWillAppear(_:))),
(#selector(vc.traitCollectionDidChange(_:)), #selector(vc.ksr_traitCollectionDidChange(_:))),
].forEach {
original, swizzled in
guard let originalMethod = class_getInstanceMethod(vc, original),
let swizzledMethod = class_getInstanceMethod(vc, swizzled) else {
return }
let didAddViewDidLoadMethod = class_addMethod(vc,
original,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod))
if didAddViewDidLoadMethod {
class_replaceMethod(vc,
swizzled,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}
private var hasSwizzled = false
extension UIViewController {