做者:Olivier Halligon,原文連接,原文日期:2018-09-02 譯者:灰s;校對:numbbbbb,小鐵匠Linus;定稿:Forelaxgit
在 Swift 中,協議中聲明的屬性沒有訪問控制的能力。若是協議中列出了某個屬性,則必須使遵照協議的類型顯式聲明這些屬性。github
不過有些時候,儘管你會在協議中聲明一些屬性,但你是要利用這些屬性來提供你的實現,並不但願這些屬性在類型的外部被使用。讓咱們看看如何解決這個問題。swift
假設你須要建立一個專門的對象來管理你的視圖控制器(ViewControllers)導航,好比一個協調器(Coordinator)。安全
每一個協調器都有一個根控制器 UINavigationController
,並共享一些通用的功能,好比在它上面推動(push)和彈出(pop)其餘 ViewController。因此最初它看起來多是這樣 [1]:編碼
// Coordinator.swift
protocol Coordinator {
var navigationController: UINavigationController { get }
var childCoordinator: Coordinator? { get set }
func push(viewController: UIViewController, animated: Bool)
func present(childViewController: UIViewController, animated: Bool)
func pop(animated: Bool)
}
extension Coordinator {
func push(viewController: UIViewController, animated: Bool = true) {
self.navigationController.pushViewController(viewController, animated: animated)
}
func present(childCoordinator: Coordinator, animated: Bool) {
self.navigationController.present(childCoordinator.navigationController, animated: animated) { [weak self] in
self?.childCoordinator = childCoordinator
}
}
func pop(animated: Bool = true) {
if let childCoordinator = self.childCoordinator {
self.dismissViewController(animated: animated) { [weak self] in
self?.childCoordinator = nil
}
} else {
self.navigationController.popViewController(animated: animated)
}
}
}
複製代碼
當咱們想要聲明一個新的 Coordinator
對象時,會像這樣作:spa
// MainCoordinator.swift
class MainCoordinator: Coordinator {
let navigationController: UINavigationController = UINavigationController()
var childCoordinator: Coordinator?
func showTutorialPage1() {
let vc = makeTutorialPage(1, coordinator: self)
self.push(viewController: vc)
}
func showTutorialPage2() {
let vc = makeTutorialPage(2, coordinator: self)
self.push(viewController: vc)
}
private func makeTutorialPage(_ num: Int, coordinator: Coordinator) -> UIViewController { … }
}
複製代碼
這個解決方案在 protocol
的可見性上有兩個問題:翻譯
Coordinator
對象,都必須顯式的聲明一個 let navigationController: UINavigationController
屬性和一個 var childCoordinator: Coordinator?
屬性。雖然,在遵照協議的類型現實中,咱們並無顯式的使用他們 - 但它們就在那裏,由於咱們須要它們做爲默認的實現來供 protocol Coordinator
正常工做。MainCoordinator
相同的可見性(在本例中爲隱式 internal(內部)
訪問控制級別),由於這是 protocol Coordinator
的必備條件。這使得它們對外部可見,就像在編碼時可使用 MainCoordinator
。因此問題是咱們每次都要聲明一些屬性——即便它只是一些實現細節,並且這些實現細節會經過外部接口被泄漏,從而容許類的訪問者作一些本不該該被容許的事,例如:code
let mainCoord = MainCoordinator()
// 訪問者不該該被容許直接訪問 navigationController ,可是他們能夠
mainCoord.navigationController.dismissViewController(animated: true)
// 他們也不該該被容許作這樣的事情
mainCoord.childCoordinator = mainCoord
複製代碼
也許你會認爲,既然咱們不但願它們是可見的,那麼能夠直接在第一段代碼的 protocol
中不聲明這兩個屬性。可是若是咱們這樣作,將沒法經過 extension Coordinator
來提供默認的實現,由於默認的實現須要這兩個屬性存在以便它們的代碼被編譯。對象
你可能但願 Swift 容許在協議中申明這些屬性爲 fileprivate
,可是在 Swift 4 中,你不能在 protocols
中使用任何訪問控制的關鍵字。接口
因此咱們如何才能解決這個「既要提供用到這個屬性的默認實現,有不讓這些屬性對外暴露」的問題呢?
實現這一點的一個技巧是將這些屬性隱藏在中間對象中,並在該對象中將對應的屬性聲明爲 fileprivate
。
經過這種方式,儘管咱們依舊在對應類型的公共接口中聲明瞭屬性,可是接口的訪問者卻不能訪問該對象的內部屬性。而咱們對於協議的默認實現卻可以訪問它們 —— 只要它們在同一個文件中被聲明就好了(由於它們是 fileprivate
)。
看起來就像這樣:
// Coordinator.swift
class CoordinatorComponents {
fileprivate let navigationController: UINavigationController = UINavigationController()
fileprivate var childCoordinator: Coordinator? = nil
}
protocol Coordinator: AnyObject {
var coordinatorComponents: CoordinatorComponents { get }
func push(viewController: UIViewController, animated: Bool)
func present(childCoordinator: Coordinator, animated: Bool)
func pop(animated: Bool)
}
extension Coordinator {
func push(viewController: UIViewController, animated: Bool = true) {
self.coordinatorComponents.navigationController.pushViewController(viewController, animated: animated)
}
func present(childCoordinator: Coordinator, animated: Bool = true) {
let childVC = childCoordinator.coordinatorComponents.navigationController
self.coordinatorComponents.navigationController.present(childVC, animated: animated) { [weak self] in
self?.coordinatorComponents.childCoordinator = childCoordinator // retain the child strongly
}
}
func pop(animated: Bool = true) {
let privateAPI = self.coordinatorComponents
if privateAPI.childCoordinator != nil {
privateAPI.navigationController.dismiss(animated: animated) { [weak privateAPI] in
privateAPI?.childCoordinator = nil
}
} else {
privateAPI.navigationController.popViewController(animated: animated)
}
}
}
複製代碼
如今,遵照協議的 MainCoordinator
類型:
let coordinatorComponents = CoordinatorComponents()
屬性,並不用知道 CoordinatorComponents
類型的內部有些什麼(隱藏了實現細節)。MainCoordinator.swift
文件中,不能訪問 coordinatorComponents
的任何屬性,由於它們被聲明爲 fileprivate
。public class MainCoordinator: Coordinator {
let coordinatorComponents = CoordinatorComponents()
func showTutorialPage1() {
let vc = makeTutorialPage(1, coordinator: self)
self.push(viewController: vc)
}
func showTutorialPage2() {
let vc = makeTutorialPage(2, coordinator: self)
self.push(viewController: vc)
}
private func makeTutorialPage(_ num: Int, coordinator: Coordinator) -> UIViewController { … }
}
複製代碼
固然,你仍然須要在遵照協議的類型中聲明 let coordinatorComponents
來提供存儲,這個聲明必須是可見的(不能是 private
),由於這是遵照 protocol Coordinator
所要求的一部分。可是:
固然,你仍然能夠訪問 myMainCoordinator.coordinatorComponents
,可是不能使用它作任何事情,由於它全部的屬性都是 fileprivate
!
Swift 可能沒法提供你想要的全部功能。你可能但願有朝一日 protocols
容許對它聲明須要的屬性和方法使用訪問控制關鍵字,或者經過某種方式將它們在公共 API 中隱藏。
但與此同時,掌握這些技巧和變通方法可使你的公共 API 更好、更安全,避免泄露實現細節或者訪問在實現以外不該該被修改的屬性,同時仍然使用 Mixin pattern 並提供默認實現。
[1].這是一個簡化的例子;不要將注意力集中在 Coordinator 的實現 - 它不是這個例子的重點,更應該關注的是須要在協議中聲明公開可訪問的屬性。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 swift.gg。