如何在Swift中使用命名空间?

159

文档只提到了嵌套类型,但不清楚它们是否可以用作命名空间。我没有找到任何显式提到命名空间的内容。


1
我不知道为什么这个问题被关闭。我在Swift图标左侧的主题演讲中看到了命名空间,但是我仍然在文档中找不到任何提及... - eonil
我找不到关于这个的任何信息,谷歌引导我来到这个问题 :). 或许 WWDC 的其中一个会议将会更详细地解释这个问题。 - Yvo
我也在等待WWDC上有人提供好的解释。 - eonil
你实际上可以使用“枚举”技巧:https://cocoacasts.com/namespaces-in-swift 这很奇怪,但它确实有效。 - Fattie
看起来这篇文章 https://www.natashatherobot.com/swift-enum-no-cases/ 在很大程度上是这个习语的起源。 - Fattie
显示剩余2条评论
9个回答

159

我认为Swift的命名空间是有抱负性的,它已经被赋予了很多广告宣传,但这与实际上并没有任何有意义的联系。

例如,WWDC视频中指出,如果你导入的框架有一个类MyClass,而你的代码也有一个类MyClass,那么这些名称不会冲突,因为“名称修饰”会给它们不同的内部名称。然而在现实中,它们确实会发生冲突,因为你自己的代码的MyClass会覆盖,你无法指定“不不,我是指框架中的MyClass”——说TheFramework.MyClass没用(编译器知道你的意思,但它会说找不到框架中的这个类)。

我的经验是,Swift实际上根本没有命名空间。在将我的应用程序从Objective-C转换为Swift时,我创建了一个嵌入式框架,因为这样做非常容易和酷。然而,导入该框架会导入框架中的所有Swift内容 - 因此,再次出现了一个全局的命名空间。而且没有Swift头文件,所以你不能隐藏任何名称。

编辑:在3号种子版本中,这个功能现在开始上线,具体来说:如果你的主代码包含MyClass,而你的框架MyFramework包含MyClass,则默认情况下前者会覆盖后者,但是你可以使用语法MyFramework.MyClass来访问后者。因此,我们实际上有了一个独立的命名空间的雏形!

编辑2:在种子版本4中,我们现在拥有访问控制!此外,在我的一款应用程序中,我有一个嵌入式框架,确实,所有内容都默认隐藏,我必须显式地公开所有的公共API部分。这是一个很大的改进。


8
谢谢您的答复。这不仅适用于框架,也适用于标准库。例如,您可以“覆盖”Array。然后,“Array”将指向您自己定制的Array类,并且标准库中的Array可用作“Swift.Array”。 - George
3
类似于前面提到的情况,如果你覆盖了NSArray这个类,仍然可以使用Foundation.NSArray来引用它。 - matt
1
那么,不必在beta版本中整理名称空间思想的历史,这个答案现在处于什么地位? - Dan Rosenstark
1
@Yar 如编辑中所述。如果存在歧义,模块名称是可选的命名空间,并且现在有隐私保护,因此除非公开,否则模块名称将被隐藏,并且名称可以限定为文件。 - matt
2
这件事情困扰了我很长时间,似乎由于苹果的声明,任何Swift项目都不应该使用前缀,但是目前仍然需要前缀,即使有访问修饰符。虽然您不会与Apple框架中的包或私有类发生冲突,但如果您再次声明公共类(例如String)或任何新类,则将使用您的类,除非您习惯于使用其命名空间引用所有类...在我看来不好。 - Oscar Gomez
太糟糕了,奥斯卡。我将继续在每个文件前缀中加入命名空间。命名空间是语言的核心特性之一,我无法理解为什么它们被忽视了。如果正确使用,您可以拥有非常简洁的命名,而不需要任何垃圾前缀。 - LegendLength

126

SevenTenElevenApple开发论坛中回答:

命名空间不是按文件而是按目标进行的(基于“产品模块名称”构建设置)。因此,你最终会得到类似这样的结果:

import FrameworkA
import FrameworkB

FrameworkA.foo()

所有的Swift声明都被认为是某个模块的一部分,所以即使你说 "NSLog"(是的,它仍然存在),你得到的也是Swift认为的 "Foundation.NSLog"。

Chris Lattner 在推特上发表了关于命名空间的言论。

在Swift中,命名空间是隐式的,所有的类(等等)都隐含地由它们所在的模块(Xcode目标)作用域限定,不需要类前缀。

这似乎和我之前想的很不一样。


10
苹果开发者论坛……我在那里看到了很多风滚草,你无法想象! - Nicolas Miari
1
Apple开发者论坛的链接现在已经失效了,而且不幸的是,苹果公司也没有将那个帖子导入到新的forums.developer.apple.com论坛网站中。 - Dai
2
@Dai 看来这就是为什么我们应该避免在苹果论坛上进行问答的原因... 但核心开发团队成员似乎并不太关心 Stack Overflow。真是一场悲剧。 - eonil
1
你说的 tumbleweed 是什么意思? - Alexander Mills
2
@AlexanderMills 我知道现在已经过去了4年以上,来回答你的问题:想象一下电影里的老西部小镇,空荡荡的街道上只有风滚草在飞舞。 - John
最后我现在明白了 tumbleweed 的事情。 - eonil

22

在进行一些实验时,我通过扩展根“包”来创建了这些自己文件中的“命名空间”类。不确定是否违反最佳实践或是否有任何我不知道的影响(?)

AppDelegate.swift

var n1 = PackageOne.Class(name: "Package 1 class")
var n2 = PackageTwo.Class(name: "Package 2 class")

println("Name 1: \(n1.name)")
println("Name 2: \(n2.name)")

PackageOne.swift

import Foundation

struct PackageOne {
}

PackageTwo.swift

import Foundation

struct PackageTwo {
}

PackageOneClass.swift

extension PackageOne {
    class Class {
        var name: String
        init(name:String) {
            self.name = name
        }
    }
}

PackageTwoClass.swift

extension PackageTwo {
    class Class {
        var name: String
        init(name:String) {
            self.name = name
        }
    }
}

编辑:

刚刚发现,如果在上面的代码中创建“子包”,那么在使用单独的文件时将不起作用。也许有人可以提示为什么会出现这种情况?

将以下文件添加到上面:

PackageOneSubPackage.swift

import Foundation

extension PackageOne {
    struct SubPackage {
    }
}

PackageOneSubPackageClass.swift

extension PackageOne.SubPackage {
    class Class {
        var name: String
        init(name:String) {
            self.name = name
        }
    }
}

编译器错误:'SubPackage'不是'PackageOne'的成员类型。将代码从PackageOneSubPackageClass.swift移动到PackageOneSubPackage.swift,它就可以工作了。有人知道原因吗?编辑2:在尝试中发现(在Xcode 6.1 beta 2中),通过在一个文件中定义包,可以在单独的文件中扩展它们。

public struct Package {
  public struct SubPackage {
    public struct SubPackageOne {
    }
    public struct SubPackageTwo {
    }
  }
}

这是我在Gist上的文件: https://gist.github.com/mikajauhonen/d4b3e517122ad6a132b8


2
不确定您所说的“测试”是什么意思,但我已经开始使用上述技术构建我的应用程序,目前看来效果还不错,只是在上面添加了一些警告。我这样做主要是因为我习惯于在其他语言中以这种方式组织我的代码,如果有更多知识的人能告诉我这是个坏主意,那我会非常感激,以免走得太远! :) - bWlrYWphdWhvbmVu
1
继续前进...这就是我们想要的...能够在两个不同的包中拥有相同名称的类,以便能够存在并相应地引用(更重要的是不同的文件),如果它不起作用,那么命名空间的整个概念就是一个失败的想法... - user2727195
2
使用“struct”作为黑客命名空间的方式会产生任何副作用吗? - Alex Nolasco
@bWlrYWphdWhvbmVu,你是如何处理storyboards的?结构体/扩展方法很完美,但是我无法指向我的storyboard中的任何命名空间类,因为它无法识别这样的类。 - Stanislav Pankevich
请看 https://dev59.com/dlsV5IYBdhLWcg3w4iJA 上的有关Swift嵌套类型扩展的讨论。 - Kirow
显示剩余2条评论

13

我相信可以通过以下方式实现:

struct Foo
{
    class Bar
    {
    }
}

然后可以使用以下方式访问:

var dds = Foo.Bar();

1
我仍然对命名空间的处理方式不太满意……如果我在一个命名空间中有十个不同的类,而且除此之外,我更喜欢将类保留在它们各自的文件中,不想用一个文件 / 结构体膨胀所有的类,你有什么建议,Kevin? - user2727195
2
我想知道为什么他们没有考虑包的问题,那才是我们需要的,而不是命名空间。我的意思是看看其他高级语言,比如Java、C#、ActionScript,它们都有包,而在这种情况下,命名空间与为项目类使用NS或其他前缀没有什么不同。 - user2727195
2
不禁想知道是否使用结构体作为一种欺骗命名空间的方式会导致未知问题。 - Alex Nolasco
1
这是一个不错的解决方法。 我尝试使用这种方法,但必须立即停止。当我尝试为我的视图相关类(比如CustomTableViewCell)命名空间时,接口构建器中的自动完成没有建议它。如果我使用了这种方法,我将不得不手动复制和粘贴视图的类名。 - ArG
1
通常情况下,您会使用“枚举”而不是“结构体”,因此无法实例化“Foo”。 - Kevin
显示剩余5条评论

8
  • 当您需要定义与现有框架中的类相同名称的类时,命名空间非常有用。

  • 假设您的应用程序名为MyApp,您需要声明自定义的UICollectionViewController

不需要像这样添加前缀和子类:

class MAUICollectionViewController: UICollectionViewController {}

像这样做:
class UICollectionViewController {} //no error "invalid redeclaration o..."

为什么?因为你声明的内容是在当前模块中声明的,也就是你的当前目标。而UIKit中的UICollectionViewController是在UIKit模块中声明的。

如何在当前模块中使用它?

var customController = UICollectionViewController() //your custom class
var uikitController = UIKit.UICollectionViewController() //class from UIKit

如何区分它们与另一个模块?

var customController = MyApp.UICollectionViewController() //your custom class
var uikitController = UIKit.UICollectionViewController() //class from UIKit

7
Swift使用模块,类似于Python(参见这里这里),正如@Kevin Sylvestre所建议的那样,您也可以使用嵌套类型作为命名空间。
此外,根据@Daniel A. White的回答,WWDC中也谈到了Swift中的模块。
还有这里解释道:

推断类型使代码更加清晰,减少错误的可能性,而模块则消除了头文件并提供了命名空间。


2
我正在寻找像您第二个链接中提到的construct这样的软件包,Python中的命名空间作为嵌套类型无法很好地使用,如果我有10个不同的类并且在命名空间或者说一个包中的不同文件中怎么办? - user2727195

3
您可以使用extension来使用所提到的struct方法进行命名空间,而无需将所有代码缩进到右侧。我已经尝试了一下,不确定是否要像下面的示例中创建ControllersViews命名空间,但它确实说明了它可以走多远:

Profiles.swift

// Define the namespaces
struct Profiles {
  struct Views {}
  struct ViewControllers {}
}

Profiles/ViewControllers/Edit.swift

// Define your new class within its namespace
extension Profiles.ViewControllers {
  class Edit: UIViewController {}
}

// Extend your new class to avoid the extra whitespace on the left
extension Profiles.ViewControllers.Edit {
  override func viewDidLoad() {
    // Do some stuff
  }
}

Profiles/Views/Edit.swift

extension Profiles.Views {
  class Edit: UIView {}
}

extension Profiles.Views.Edit {
  override func drawRect(rect: CGRect) {
    // Do some stuff
  }
}

我还没有在应用程序中使用过这个,因为我还没有需要这种程度的分离,但我认为这是一个有趣的想法。这可以消除甚至类后缀的需求,如普遍存在的* ViewController后缀,这太长了令人讨厌。

然而,在方法参数中引用时,它并不会缩短任何东西,例如:

class MyClass {
  func doSomethingWith(viewController: Profiles.ViewControllers.Edit) {
    // secret sauce
  }
}

2

如果有人感兴趣,在2014年6月10日,Swift存在一个已知的bug:

来自SevenTenEleven

"已知的bug,抱歉!rdar://problem/17127940 通过模块名称限定Swift类型无法正常工作。"


根据@matt在下面的帖子中所述,这是Swift目前已知的一个bug。 - AJ Venturella
这个问题在Beta 3版中已经修复(发布于2014年7月7日)。 - AJ Venturella

2
虽然可以使用框架和库来实现命名空间,但最好的解决方案是使用Swift Package Manager来使用本地包。除了具有访问修饰符之外,这种方法还有其他一些好处。在Swift Package Manager中,文件是基于目录系统而不是目标成员身份进行管理,因此您不必为团队合作中经常出现的合并冲突而苦苦挣扎。此外,无需设置文件成员资格。
要查看如何使用本地Swift包,请参阅以下链接:使用本地包组织代码

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接