为什么枚举在 Swift 中有计算属性但没有存储属性?

Why do enums have computed properties but not stored properties in Swift?

我是 Swift 的新手,刚在文档中看到这个:

Computed properties are provided by classes, structures, and enumerations. Stored properties are provided only by classes and structures.

这是为什么?枚举的关联值是否像存储属性一样工作?看起来他们最初存储了属性 - Why no stored type properties for classes in swift?

枚举被认为是一种结构化数据类型,无需在代码中多次更改 String 或 Int 即可对其进行修改,使用枚举,我们永远不必担心多次更改同一事物.例如下拉菜单:

enum DropDownMenuOptions: String {
  case MenuOptionOne
  case MenuOptionTwo
  case MenuOptionThree
}

使用存储的属性,您可以预先计算所需的信息并减少主函数中的代码。最好的例子是计算矩形的大小,例如:

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))

enums 确实存储了 type 属性 - 即 static 属性。他们没有存储 instance 属性。我不知道是否有技术原因导致 enums 无法使用存储的实例属性。如果您想要 "why".

的技术答案,您可能需要在开发论坛上提问

在您的问题中,您询问关联值是否像存储属性一样工作。事实上,它们确实如此,并且比 structclass 的存储属性更灵活(在某些方面)。 enum 中的每个 case 都可以有自己的专用数据集与之关联。您可以为每个 case.

个性化存储属性,而不是拥有一组适用于所有 case 的存储属性

我使用了一个小技巧来存储初始化时不可访问的属性。

首先,我创建一个 class Future 来存储将来存储的 属性:

class Future<T> {
  var value: T?
  init(value: T? = nil) {
      self.value = value
  }
}

然后我创建我的枚举:

enum Sample {
  case Test(future: Future<String>)
}

实例化:

let enumTest = Sample.Test(future: Future())

后面的代码:

switch enumTest {
  case let .Test(future):
  future.value = "Foo"
}

稍后,您可以访问值:

switch enumTest {
  case let .Test(future):
  // it will print Optional("Foo")
  print("\(future.value)")
}

这不是您应该滥用的东西,但在某些情况下它会有所帮助。

希望对您有所帮助。

不允许存储实例属性的枚举是一种设计选择。具有存储实例属性的枚举使其像一个结构(具有枚举超能力),但从类型的角度来看,现在枚举将像类型乘法器一样工作。基本考虑

enum Set1 {
    case a
    case b
    case c
}

enum Times {
    case x
    case y

    var k: Set1
}

这实际上意味着枚举 Times 允许我们对 Set1Set2 中的元素进行任意组合,从而产生 6 种不同的情况,但是等等,我们知道实际上这是一个像 (Set1, Set2) 这样的元组类型的用途,其中 Times 可以声明为

typealias Times = (Set1, Set2)

我希望这是不允许前一种情况的合理理由。

也就是说,swift 枚举允许我们将任意 n 元组与每个 case 相关联,从而允许我们在函数式中声明所谓的可区分联合编程。如果您愿意,可以将其称为附加到案例的存储 属性。从类型的角度来看,它现在充当类型加法器。

enum Add {
    case lhs(Set1)
    case rhs(Set2)
}

我们现在有 5 个不同的案例。如果我们现在存储 2 元组:

enum AddTimes {
    case lhs(Set1, Set2)
    case rhs(Set3, Set4)
}

我们现在基本上有了乘法和 (Set1 * Set2 + Set3 * Set4)。 在模式匹配方面,这是一个非常强大的工具。

HOWEVER,在一些实际情况下,您实际上想要模拟枚举中存储的 属性 的形式。考虑一下:

public enum Service {
    case registerNewUser(username: String, password: String, language: String)
    case login(username: String, password: String, deviceTokenº: String?)
    case logout(sessionToken: String)
    case sendForgotPassword(email: String)
}

是一种定义 REST 端点的声明方式(在 Moya 等框架中) 当你想触发一个请求时,你会做类似

的事情
MoyaProvider<Service>.request(.sendForgotPassword(email: "foo@foo.com"))

但现在假设您想区分生产服务器和测试服务器。如果在每种情况下都将服务器添加为另一个元组元素:

case forgotPassword(sessionToken: String, serverBaseURLString: String)

这将有一个错误的语义,因为您最初打算每个元组存储请求参数,但现在它存储一个服务器基址。

为了避免这样的事情,我们实际上可以通过以下方式参数化我们的类型。而不是将服务器定义为:

enum Server: String {
    case production = "https://service.info"
    case test = "http://test.service.info"
}

我们可以为每种情况定义不同的类型,例如:

public struct ProductionServer: ServerType {
    public static var baseURLString: String { return "https://service.info" }
}
public struct TestServer: ServerType {
    public static var baseURLString: String { return  "http://test.service.info" }
}
public protocol ServerType {
    static var baseURLString: String { get }
}

最后将我们的 ServiceType 参数化为

public enum Service<T> where T: ServerType {
    case registerNewUser(username: String, password: String, language: String)
    case login(username: String, password: String, deviceTokenº: String?)
    case logout(sessionToken: String)
    case sendForgotPassword(email: String)

    var serverURL: URL {
        return T.baseURL
    }
}

public typealias ProdutionService = Service<ProductionServer>
public typealias TestService = Service<TestServer>