也许您应该尝试完全面向对象的路径。视图组合看起来像这样:
// 可重用的协议集
protocol OOString: class {
var value: String { get }
}
protocol Executable: class {
func execute()
}
protocol Screen: class {
var ui: UIViewController { get }
}
protocol ViewRepresentation: class {
var ui: UIView { get }
}
// 可重用的功能(无 uikit 依赖)
final class ConstString: OOString {
init(_ value: String) {
self.value = value
}
let value: String
}
final class ExDoNothing: Executable {
func execute() { /* do nothing */ }
}
final class ExObjCCompatibility: NSObject, Executable {
init(decorated: Executable) {
self.decorated = decorated
}
func execute() {
decorated.execute()
}
private let decorated: Executable
}
// 可重用的 UI(uikit 依赖)
final class VrLabel: ViewRepresentation {
init(text: OOString) {
self.text = text
}
var ui: UIView {
get {
let label = UILabel()
label.text = text.value
label.textColor = UIColor.blue
return label
}
}
private let text: OOString
}
final class VrButton: ViewRepresentation {
init(text: OOString, action: Executable) {
self.text = text
self.action = ExObjCCompatibility(decorated: action)
}
var ui: UIView {
get {
let button = UIButton()
button.setTitle(text.value, for: .normal)
button.addTarget(action, action: #selector(ExObjCCompatibility.execute), for: .touchUpInside)
return button
}
}
private let text: OOString
private let action: ExObjCCompatibility
}
final class VrComposedView: ViewRepresentation {
init(first: ViewRepresentation, second: ViewRepresentation) {
self.first = first
self.second = second
}
var ui: UIView {
get {
let view = UIView()
view.backgroundColor = UIColor.lightGray
let firstUI = first.ui
view.addSubview(firstUI)
firstUI.translatesAutoresizingMaskIntoConstraints = false
firstUI.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
firstUI.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
firstUI.widthAnchor.constraint(equalToConstant: 100).isActive = true
firstUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
let secondUI = second.ui
view.addSubview(secondUI)
secondUI.translatesAutoresizingMaskIntoConstraints = false
secondUI.topAnchor.constraint(equalTo: firstUI.topAnchor).isActive = true
secondUI.leadingAnchor.constraint(equalTo: firstUI.trailingAnchor, constant: 20).isActive = true
secondUI.widthAnchor.constraint(equalToConstant: 80).isActive = true
secondUI.heightAnchor.constraint(equalToConstant: 40).isActive = true
return view
}
}
private let first: ViewRepresentation
private let second: ViewRepresentation
}
// 一个视图控制器
final class ContentViewController: UIViewController {
convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.init()
}
convenience required init(coder aDecoder: NSCoder) {
self.init()
}
convenience init() {
fatalError("Not supported!")
}
init(content: ViewRepresentation) {
self.content = content
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
view = content.ui
}
private let content: ViewRepresentation
}
// 现在是屏幕的业务逻辑(不可重用)
final class ScStartScreen: Screen {
var ui: UIViewController {
get {
return ContentViewController(
content: VrComposedView(
first: VrLabel(
text: ConstString("Please tap:")
),
second: VrButton(
text: ConstString("OK"),
action: ExDoNothing()
)
)
)
}
}
}
AppDelegate 中的用法:
window?.rootViewController = ScStartScreen().ui
笔记:
- 它遵循面向对象编码的规则(干净的编码、优雅的对象、装饰器模式……)
- 每个类都是非常简单的构造
- 类通过协议相互通信
- 所有的依赖都尽可能的通过依赖注入来给出
- 一切(最后的业务屏幕除外)都是可重用的 -> 事实上:可重用代码的组合随着你编码的每一天而增长
- 您的应用程序的业务逻辑集中在 Screen 对象的实现中
- 当对协议使用假实现时,单元测试非常简单(在大多数情况下甚至不需要模拟)
- 保留周期的问题较少
- 避免 Null、nil 和 Optionals(它们会污染你的代码)
- ...
在我看来,这是最好的编码方式,但大多数人不会那样做。