Swift中的多功能名称空间

在Swift中,某些API(例如RxSwift)使用的技术将它们公开的代码限制在专用名称空间中。 在这篇文章中,我们将说明如何以最通用,最通用的方式完成此操作。

随时访问我的博客以阅读原始文章(语法突出显示更好)

let myButton = UIButton() 
myButton.rx.tap.subscribe(…)
// this is a RxCocoa kind of code

您是否想过在Swift中编写这样的代码行是怎么可能的?

这是.rx。 乍一看似乎很尴尬的部分,不是吗? 它的行为就像某种自定义名称空间。

  • 这是变量吗?
  • 这是内部阶级吗?

我们有一些线索可以弄清楚:

  • 由于可以通过点符号访问。 它必须是UIButton类的成员
  • 它作为tap等一些属性,因此它是一个数据结构

通过这两个线索,我们可以尝试创建自己的名称空间。

在以下示例中,我们将尝试向UIButton添加自定义名称空间,以显示单个函数hello

由于名称空间必须是数据结构,因此我们尝试使用Swift结构:

 struct ButtonNameSpace { 
func hello () {
print ("Hello")
}
}

到目前为止,一切都很好。 由于名称空间应该是UIButton的成员,因此我们将其添加为计算属性:

 extension UIButton { 
var nameSpace: ButtonNameSpace {
return ButtonNameSpace()
}
}

现在,我们可以像这样使用它:

 let myButton = UIButton() 
myButton.nameSpace.hello()

调用的结果将是:“ Hello ”。

这可以用来做什么? 我不会说什么有趣的事情,因为在命名空间中,我们无法访问UIButton属性和函数。 我们将只做一些UIButton外部的事情,将一些上下文添加到我们的名称空间将是很棒的。

为了访问UIButton属性和函数,我们必须在此按钮上引用用于名称空间的数据结构。

 struct ButtonNameSpace { 
private let button: UIButton
init(with button: UIButton) {
self.button = button
}
func hello () {
let title = self.button.title(for: .normal) ?? ""
print ("Hello \(title)")
}
}

ButtonNameSpace结构在创建它的UIButton上保留一个引用。

我们仍然向UIButton添加一个计算属性,当创建ButtonNameSpace时,我们将对按钮本身的引用传递给它。 现在,我们可以访问按钮属性,例如title

 extension UIButton { 
var nameSpace: ButtonNameSpace {
return ButtonNameSpace(with: self)
}
}

我们仍然可以像这样使用它:

 let myButton = UIButton() 
myButton.setTitle("My button", for: .normal)
myButton.nameSpace.hello()

调用的结果将是:“ Hello My Button ”。 命名空间在这里更有意义,因为它与创建它的对象有关。

例如,如果我们必须为UIImage写一个命名空间该怎么办? 在实际方法中,我们还应该声明一个ImageNameSpace结构。

实际上,这种结构的真正目标是在创建它的对象上保留一个引用。 听起来仿制药可以参与其中吗? 不是吗

答案是肯定的 。 该结构必须是通用的。

 struct MyNameSpace { 
private let base: Base
init(with base: Base) {
self.base = base
}
}

如我们所见,此结构的唯一目的是保留对作为init参数给出的内容的引用,以便我们可以在进一步的调用中对其进行访问。

现在,我们可以在Swift中使用真正很棒的东西: 条件扩展 。 仅当Base与我们想要的类型匹配时,它才允许我们仅向该结构添加功能。 例如,如果Base是一个UIButton,我们添加一个Hello函数,该函数将执行与旧ButtonNameSpace相同的操作

 extension MyNameSpace where Base: UIButton { 
func hello () {
let title = self.base.title(for: .normal) ?? ""
print ("Hello \(title)")
}
}

不用访问self.button.title ,我们可以访问self.base.title,因为我们知道base当然是一个UIButton。 我们可以通过考虑名称空间的通用性,向UIButton添加一个计算属性。

 extension UIButton { 
var myNameSpace: MyNameSpace {
return MyNameSpace(with: self)
}
}

用法和结果仍然相同。

 let myButton = UIButton() 
myButton.setTitle("My button", for: .normal)
myButton.myNameSpace.hello()

让我们回到UIImage的情况。 不再需要定义专用的名称空间结构,我们可以将通用的名称空间结构与新的条件扩展结合使用。

 extension UIImage { 
var myNameSpace: MyNameSpace {
return MyNameSpace(with: self)
}
}
extension MyNameSpace where Base: UIImage {
func hello () {
let title = self.base.accessibilityHint ?? ""
print ("Hello \(title)")
}
}
let myImage = UIImage()
myImage.accessibilityHint = "My Image"
myImage.myNameSpace.hello()

实际上,这正是RxSwift实现rx名称空间的方式,如下所示:Reactive.swift

希望这可以帮助。

敬请关注