Swift有访问修饰符吗?

293

在 Objective-C 中,实例数据可以是 publicprotectedprivate。例如:

@interface Foo : NSObject
{
  @public
    int x;
  @protected:
    int y;
  @private:
    int z;
  }
-(int) apple;
-(int) pear;
-(int) banana;
@end

我在Swift参考文档中没有找到任何关于访问修饰符的提及。在Swift中是否可能限制数据的可见性?


已更新答案以适用于Xcode 6.1.1的最终版本 - holroy
Swift 4 [更新的答案] (https://dev59.com/NWAg5IYBdhLWcg3wE3nQ#39697920)。 - Ahmad F
16个回答

453

从 Swift 3.0.1 开始,有4个访问级别,从最高(最不限制的)到最低(最限制的)分别描述如下。


1. openpublic

允许实体在定义模块(目标)之外使用。通常在为框架指定公共接口时使用openpublic 访问。

然而,open 访问仅适用于类和类成员,与 public 访问不同如下:

  • public 类和类成员只能在定义模块(目标)内进行子类化和重写。
  • open 类和类成员可在定义模块(目标)内外进行子类化和重写。

// First.framework – A.swift

open class A {}

// First.framework – B.swift

public class B: A {} // ok

// Second.framework – C.swift

import First

internal class C: A {} // ok

// Second.framework – D.swift

import First

internal class D: B {} // error: B cannot be subclassed

2. internal

internal允许在定义模块(目标)内使用实体。通常在定义应用程序或框架的内部结构时使用internal访问。

// First.framework – A.swift

internal struct A {}

// First.framework – B.swift

A() // ok

// Second.framework – C.swift

import First

A() // error: A is unavailable

3. fileprivate

限制实体仅在其定义的源文件中使用。通常,当这些细节在整个文件中使用时,您会使用fileprivate访问来隐藏特定功能的实现细节。

// First.framework – A.swift

internal struct A {

    fileprivate static let x: Int

}

A.x // ok

// First.framework  B.swift

A.x // error: x is not available

4. private

限制实体只能在其封闭声明中使用。通常在某个功能的实现细节仅在单个声明中使用时,使用private访问修饰符来隐藏这些细节。

// First.framework – A.swift

internal struct A {

    private static let x: Int

    internal static func doSomethingWithX() {
        x // ok
    }

}

A.x // error: x is unavailable

38
有人可以解释一下为什么这不是个大问题吗? - Zaky German
16
面向对象编程(OOP)中,总是有一些方法或变量应该是私有(private)或受保护(protected)的。这使得实现SOLID设计原则更加容易,因为大的方法可以分解成许多小的方法,每个方法都有自己的职责,可以被重写,但只有"主"方法应该对公众可用。 - akashivskyy
19
个人而言,我不喜欢使用下划线或特殊字符作为前缀的“私有”方法这样的解决方案。即使可以保证只有我自己能够查看此代码,它也使得代码更加安全/不易出错,因为编译器会阻止你做一些不应该做的事情。因此,我认为他们应该尽快摆脱“访问控制机制”,这样人们就不会养成不良习惯。 - Jonas Eschmann
10
Xcode 6 beta版本的发行说明中写道:"在此版本中未启用访问控制(公共/私有成员)。 (15747445)" - Martin Ullrich
9
@alcalde 公共接口的概念非常有价值。如果你认为一个类中的所有代码都必须放在公共API的函数中,那么这会相当受限制。另一方面,指定公共API允许实现发生变化(包括使用私有方法),而不会影响消费者。如果有人“需要”使用内部类方法,我认为他们误解了类功能的限制(或试图使用有缺陷的类)。 - jinglesthula
显示剩余19条评论

39

Swift 4 / Swift 5

根据 Swift 文档-访问控制,Swift 有5种访问控制级别:

  • openpublic: 可以被它们所在模块的实体以及导入到该模块的任何模块中的实体访问。

  • internal: 只能从它们所在的模块的实体访问。这是默认的访问级别。

  • fileprivateprivate: 只能在定义它们的作用域内部访问。


openpublic之间有什么区别?

open 和 Swift 先前版本中的 public 是相同的,它们允许其他模块的类使用和继承它们,即:它们可以被其他模块的子类化。此外,它们还允许来自其他模块的成员使用和覆盖它们。对于它们的模块也是相同的逻辑。

public 允许其他模块的类使用它们,但不允许继承它们,即:它们不能被其他模块子类化。此外,它们允许来自其他模块的成员使用它们,但不允许覆盖它们。对于它们的模块,它们具有与 open 相同的逻辑(允许类使用和继承它们;允许成员使用和覆盖它们)。

fileprivateprivate 之间有什么区别?

fileprivate 可以从它们所在文件的任何位置访问。

private 只能从声明它们的单个位置以及在同一文件中的 扩展 中访问该声明;例如:

// Declaring "A" class that has the two types of "private" and "fileprivate":
class A {
    private var aPrivate: String?
    fileprivate var aFileprivate: String?

    func accessMySelf() {
        // this works fine
        self.aPrivate = ""
        self.aFileprivate = ""
    }
}

// Declaring "B" for checking the abiltiy of accessing "A" class:
class B {
    func accessA() {
        // create an instance of "A" class
        let aObject = A()

        // Error! this is NOT accessable...
        aObject.aPrivate = "I CANNOT set a value for it!"

        // this works fine
        aObject.aFileprivate = "I CAN set a value for it!"
    }
}



Swift 3和Swift 4的访问控制有什么区别?

正如在SE-0169提案中提到的,Swift 4唯一的改进是将private访问控制范围扩展到在同一文件中的声明的扩展中可以访问;例如:

struct MyStruct {
    private let myMessage = "Hello World"
}

extension MyStruct {
    func printMyMessage() {
        print(myMessage)
        // In Swift 3, you will get a compile time error:
        // error: 'myMessage' is inaccessible due to 'private' protection level

        // In Swift 4 it should works fine!
    }
}

所以,没有必要将myMessage声明为fileprivate才能在整个文件中访问。


17

当谈到在Swift或ObjC(或Ruby或Java等)中制作“私有方法”时,这些方法并不是真正私有的。它们周围没有实际的访问控制。任何提供即使只有少量内省的语言都可以让开发者从类外部访问这些值,如果他们真的想这样做的话。

所以我们在这里真正谈论的是一种定义公共接口的方式,仅展示我们想要的功能,并“隐藏”我们认为是“私有”的其余部分。

在Swift中声明接口的机制是protocol,它可以用于此目的。

protocol MyClass {
  var publicProperty:Int {get set}
  func publicMethod(foo:String)->String
}

class MyClassImplementation : MyClass {
  var publicProperty:Int = 5
  var privateProperty:Int = 8

  func publicMethod(foo:String)->String{
    return privateMethod(foo)
  }

  func privateMethod(foo:String)->String{
    return "Hello \(foo)"
  }
}

请记住,协议是一流类型,可在任何类型可以使用的地方使用。而且,以这种方式使用时,它们只会公开自己的接口,而不会公开实现类型的接口。

因此,只要在参数类型等中使用MyClass而不是MyClassImplementation,一切都应该正常工作:

func breakingAndEntering(foo:MyClass)->String{
  return foo.privateMethod()
  //ERROR: 'MyClass' does not have a member named 'privateMethod'
}

有些情况下需要直接赋值时,你必须明确指定类型,而不能依赖Swift进行推断,但这似乎并不是致命问题:

var myClass:MyClass = MyClassImplementation()
使用协议的方式是语义化、相当简洁,并且在我看来很像我们一直在 ObjC 中使用的类扩展。

1
如果协议不允许我们使用默认参数,那么我如何创建一个带有可选参数的公共方法,仍然符合协议要求? - bdurao
我不明白你的意思。以下代码创建了一个带有可选参数的公共方法。看起来并没有问题:https://gist.github.com/anonymous/17d8d2d25a78644046b6 - jemmons
由于某种原因,在我的项目中可选参数不像应该的那样工作,我已经尝试了类似于您在GitHub上的示例的东西。由于我们无法在协议上设置默认参数,所以我陷入了困境,最终只能问一个问题。感谢您的帮助。 - bdurao
我们都知道任何东西都是可以被黑客攻击的。我们只需要一些秩序,这就是为什么我们需要访问修饰符的原因。 - canbax

14
据我所知,没有关键字“public”、“private”或“protected”。这意味着一切都是公开的。
然而,苹果可能希望人们使用“protocols”(在世界其他地方称为接口)和factory design pattern来隐藏实现类型的细节。
无论如何,这通常是一个很好的设计模式;因为它可以让你改变你的实现类层次结构,同时保持逻辑类型系统不变。

这很好,因为它还可以减少耦合并使测试更容易。 - Scroog1
4
如果有一种方法可以隐藏协议的实现类,那就更好了,但似乎并没有这样的方法。 - David Moles
有人能提供一个说明性的例子来阐述这个模式吗? - bloudermilk
这个回答在之前的Swift版本中是有效的,但似乎现在不再有效了 :) 请查看我的回答 - Ahmad F

12

通过协议、闭包和嵌套/内部类的组合,现在可以使用类似模块模式来隐藏Swift中的信息。虽然阅读起来并不是非常干净或美观,但确实可行。

例如:

protocol HuhThing {
  var huh: Int { get set }
}

func HuhMaker() -> HuhThing {
   class InnerHuh: HuhThing {
    var innerVal: Int = 0
    var huh: Int {
      get {
        return mysteriousMath(innerVal)
      }

      set {
       innerVal = newValue / 2
      }
    }

    func mysteriousMath(number: Int) -> Int {
      return number * 3 + 2
    }
  }

  return InnerHuh()
}

HuhMaker()
var h = HuhMaker()

h.huh      // 2
h.huh = 32 
h.huh      // 50
h.huh = 39
h.huh      // 59

innerVal和mysteriousMath在此处被隐藏以防止外部使用,任何试图深入对象的尝试都应该导致错误。

我只是开始阅读Swift文档的一部分,如果这里有什么缺陷,请指出来,非常想知道。


8
仍然可以访问 :P reflect(h)[0].1.value // 19 - John Estropia
2
约翰,你找到了好东西 - 我不知道反射。它似乎可以将对象转换为元组 - 是否有关于该函数或其他Swift元编程内容的官方文档?我在iBooks上查看了语言指南,但没有看到它。 - Dave Kapp
1
@JohnEstropia 我认为反射不算。在 Java 中(一门更成熟的语言),确实有访问修饰符,但它们也无法防止反射技巧。 - 11684
@11684: 注意,我认为Dave的方法已经足够优雅了,但他要求提及任何缺陷,所以我展示了仍然有一种方法可以提取隐藏的类、方法和属性。 - John Estropia
可能很难让人们普遍同意这是否是一个缺陷,但它绝对值得知道,所以我非常高兴它被指出了。 :) - Dave Kapp
显示剩余3条评论

10

从Xcode 6 beta 4开始,Swift具有访问修饰符。根据发行说明:

Swift访问控制有三个访问级别:

  • private实体只能在定义它们的源文件中访问。
  • internal实体可以在定义它们的目标内的任何位置访问。
  • public实体可以从目标内的任何位置以及导入当前目标模块的任何其他上下文中访问。

隐式默认值为internal,因此在应用程序目标中,您可以省略访问修饰符,除非您想要更严格限制。在框架目标中(例如,如果您嵌入框架以在应用程序和共享或今天视图扩展之间共享代码),请使用public指定要向您的框架客户端公开的API。


好的,这个答案在之前的Swift版本中是有效的,但现在似乎不再有效了 :) 请查看我的答案 - Ahmad F

6
Swift 3.0 提供了五种不同的访问控制:
  1. open
  2. public
  3. internal
  4. fileprivate
  5. private

Open 访问和 public 访问使实体能够在定义模块中的任何源文件中使用,并且在导入定义模块的另一个模块的源文件中也可以使用。通常在指定框架的公共接口时使用 open 或 public 访问。

Internal 访问使实体能够在其定义模块的任何源文件中使用,但不能在该模块外的任何源文件中使用。通常在定义应用程序或框架的内部结构时使用内部访问。

File-private 访问将实体的使用限制为其自己的定义源文件。当这些细节在整个文件中使用时,使用 file-private 访问来隐藏特定功能的实现细节。

Private 访问将实体的使用限制为封闭声明。当这些细节仅在单个声明中使用时,使用 private 访问来隐藏特定功能的实现细节。

Open 访问是最高(最不受限制)的访问级别,而 private 访问是最低(最受限制)的访问级别。

默认访问级别

如果您没有明确指定实体的访问级别,那么代码中的所有实体(除了一些特定的例外)都具有默认的内部访问级别。因此,在许多情况下,您不需要在代码中指定显式的访问级别。

有关该主题的发布说明:

声明为 public 的类不能在其定义模块之外进行子类化,声明为 public 的方法不能在其定义模块之外进行覆盖。要允许外部子类化类或外部覆盖方法,请将它们声明为 open,这是一个超出 public 的新访问级别。导入的 Objective-C 类和方法现在都作为 open 导入,而不是 public。使用 @testable import 导入模块的单元测试仍将允许子类化 public 或 internal 类以及覆盖 public 或 internal 方法。(SE-0117)

更多信息和详细内容: The Swift Programming Language (Access Control)


嗯,这个答案在之前的Swift版本中是有效的,但现在似乎不再有效了 :) 请查看我的答案 - Ahmad F

4
在Beta 6中,文档指出有三种不同的访问修饰符:
  • 公共的
  • 内部的
  • 私有的
这三种修饰符适用于类、协议、函数和属性。
public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}

更多信息请查看访问控制


应该有一个受保护的修饰符,以便更轻松地创建具有更高安全性的类。 - Kumar C
好的,这个回答在之前的Swift版本中是有效的,但现在似乎不再有效了 :) 请查看我的回答 - Ahmad F

2
Swift 3和4为变量和方法的访问级别带来了很多变化。现在,Swift 3和4有4个不同的访问级别,其中“open/public”访问是最高(最少限制)访问级别,“private”访问是最低(最严格)访问级别:
- “private”函数和成员只能从实体本身(struct、class等)及其扩展(在Swift 3中,扩展也受到限制)的范围内访问。 - “fileprivate”函数和成员只能从声明它们的源文件中访问。 - “internal”函数和成员(如果您没有显式添加访问级别关键字,则为默认值)可以在定义它们的目标中的任何地方访问。这就是为什么TestTarget不能自动访问所有源代码的原因,它们必须在xCode的文件检查器中标记为可访问。 - “open或public”函数和成员可以从目标中的任何地方以及从导入当前目标模块的任何其他上下文中访问。
有趣的是:您可以将一些方法(例如通常是辅助函数)覆盖在类/struct的扩展中,并将整个扩展标记为“Private”,而无需将每个单独的方法或成员标记为“private”。
class foo { }

private extension foo {
    func somePrivateHelperFunction01() { }
    func somePrivateHelperFunction02() { }
    func somePrivateHelperFunction03() { }
}

这是一个不错的主意,有助于获得更易维护的代码。而且你只需更改一个单词就能轻松切换到非私有模式(例如为了单元测试)。 苹果文档

嗯,这个答案在之前的Swift版本中是有效的,但现在似乎不再有效了 :) 请查看我的答案 - Ahmad F

2

对于Swift 1-3:

不,这是不可能的。根本没有私有/受保护的方法和变量。

一切都是公开的。

更新 自从Swift 4以后,可以看到其他帖子中的答案。


1
此注释适用于当前的种子。 - Jesper
2
对于当前的种子。它将在未来出现 - Jesper
1
“public” / “protected” / “private” 目前不存在,但是您可以使用闭包、协议和内部类来隐藏内容 - 这使其有点像 JavaScript 中常用的模块模式。请参见我在此回复中的示例代码,以了解如何执行此操作。如果我对其工作方式有误并且我的示例不正确,请指出,因为我也在学习中。 :) - Dave Kapp
似乎它已经无效了 :) 请查看我的回答 - Ahmad F

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