常規思路
通常我們的類會在加載前開啟動畫,回調到來再停止:
final class EpisodeDetailViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let titleLabel = UILabel()
convenience init(resource: Resource<Episode>) {
self.init()
// 開始網絡請求 Loading 開始加載動畫
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
// 回調數據到來 結束 Loading 加載動畫
self?.spinner.stopAnimating()
guard let value = result.value else { return }
self?.titleLabel.text = value.title
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleLabel)
titleLabel.constrainEdges(toMarginOf: view)
}
}
Protocol
輕量級,而且 Swift 里有 extension,這一點比oc實現簡單很多。
主要在定義時候注意
- 相同的可重用的放到extension里面
- 需要每個具體類做特殊處理的就聲明在協議里,當然也可以有默認實現。
我們需要規范Resource
資源,加載視圖 spinner
以及 load
方法,這里加載視圖默認是 UIActivityIndicatorView
,協議定義如下:
protocol Loading {
associatedtype ResourceType
var spinner: UIActivityIndicatorView { get }
func load(resource: Resource<ResourceType>)
}
extension Loading where Self: UIViewController {
func load(resource: Resource<ResourceType>) {
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
// 這里是回調處理 到這里的時候 result 就是解析好的數據格式
self?.spinner.stopAnimating()
guard let value = result.value else { return }
// TODO 這里和具體業務相關
}
}
}
ps: 加載視圖可以不使用
UIActivityIndicatorView
,只需要小小修改下協議就能使用自定義。
現在 TODO
和具體業務相關,我們必須想辦法規范一下,為此在協議中增加一個configure
方法 用于處理業務邏輯。
protocol Loading {
// ...
func configure(value: ResourceType)
}
extension Loading where Self: UIViewController {
func load(resource: Resource<ResourceType>) {
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
self?.configure(value)
}
}
}
現在用協議方法來實現:
final class EpisodeDetailViewController: UIViewController, Loading {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let titleLabel = UILabel()
convenience init(episode: Episode) {
self.init()
configure(episode)
}
convenience init(resource: Resource<Episode>) {
self.init()
load(resource)
}
func configure(value: Episode) {
titleLabel.text = value.title
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleLabel)
titleLabel.constrainEdges(toMarginOf: view)
}
}
至于 associatedtype ResourceType
中的 ResourceType
會在 load(resource)
推斷出來。
Container 容器封裝
有pros也有contra,首先不利的話,是每次都要addchildViewController 不是很好,在一些情況下可能出錯,比如navigation棧下貌似會出錯。
我們要實現的 LoadingViewController
容器類職責:1. 數據請求load()
,具體由外部實現; 2. 加載視圖的顯示和隱藏。
核心代碼是兩個閉包:load
和 build
:
final class LoadingViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
init<A>(load: ((Result<A>) -> ()) -> (), build: (A) -> UIViewController) {
super.init(nibName: nil, bundle: nil)
spinner.startAnimating()
load() { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
let viewController = build(value)
self?.add(content: viewController)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func add(content content: UIViewController) {
addChildViewController(content)
view.addSubview(content.view)
content.view.translatesAutoresizingMaskIntoConstraints = false
content.view.constrainEdges(toMarginOf: view)
content.didMoveToParentViewController(self)
}
}
這是我們Loading視圖控制器,注意 build
閉包會EpisodeDetailViewController
實例,具體處理業務。
EpisodeDetailViewController
和上面定義的一樣:
final class EpisodeDetailViewController: UIViewController {
let titleLabel = UILabel()
convenience init(episode: Episode) {
self.init()
titleLabel.text = episode.title
}
override func viewDidLoad() {
view.backgroundColor = .whiteColor()
view.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.constrainEdges(toMarginOf: view)
}
}
現在的關鍵是如何把兩者關聯組合起來:
let episodesVC = LoadingViewController(load: { callback in
sharedWebservice.load(episodeResource, completion: callback)
}, build: EpisodeDetailViewController.init)
category
主要是今天看到高峰的文章 認為是一個不錯的選擇
鏈接:iOS weak 關鍵字漫談
歡迎關注我的微博:@Ninth_Day