做者: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。