Почему перечисления перечислили свойства, но не сохранили свойства в Swift?
Я новичок в Swift и наткнулся на это в документации:
Вычислимые свойства предоставляются классами, структурами и Перечисления. Сохраненные свойства предоставляются только классами и структуры.
Почему? Связанные значения для перечисления работают как хранимые свойства? Похоже, что они сначала сохранили свойства - Почему нет хранимых свойств типа для классов в swift?
Ответы
Ответ 1
enum
имеют сохраненные свойства типа - т.е. static
. У них нет сохраненных свойств экземпляра. Я не знаю, есть ли техническая причина, почему хранимые свойства экземпляра недоступны для enum
s. Возможно, вам придется задать свой вопрос на форуме разработчиков, если вы хотите получить технический ответ "почему".
В вашем вопросе вы спрашиваете, работают ли связанные значения, как сохраненные свойства. Фактически, они имеют и более гибкие (в некотором роде), чем хранимые свойства для struct
и class
es. Каждый case
в enum
может иметь свой собственный специализированный набор данных, который связан с ним. Вместо того, чтобы иметь один набор сохраненных свойств, которые применяются ко всем case
s, вы можете индивидуализировать хранимые свойства для каждого case
.
Ответ 2
Перечисления, не позволяющие сохранять свойства хранимых экземпляров, являются выбором дизайна. Перечисление с хранимыми свойствами экземпляра делает его похожим на struct (с перечислительными суперпотоками), но только с точки зрения типа теперь перечисления будут действовать как мультипликаторы типов. В основном рассмотрим
enum Set1 {
case a
case b
case c
}
enum Times {
case x
case y
var k: Set1
}
Что это означает, что перечисление Times
позволяет нам иметь любую комбинацию элементов из Set1
и Set2
, приводящую к 6 различным случаям, но подождите, мы знаем, что это цель типа кортежа вроде (Set1, Set2)
, где Times
можно объявить как
typealias Times = (Set1, Set2)
Надеюсь, это послужит разумным основанием для того, чтобы не допустить первого случая.
При этом быстрые перечисления позволяют нам связывать произвольный n-кортеж с каждым случаем, что позволяет нам объявить то, что известно как дискриминационный союз в функциональном программировании. Назовите это хранимым имуществом, прикрепленным к делу, если хотите. С точки зрения типов он теперь действует как сумматор типов.
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, существуют некоторые реальные случаи, когда вы действительно хотите эмулировать форму хранимого свойства внутри enum. Рассмотрим это:
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: "[email protected]"))
Но теперь представьте, что вы хотите различать производственный и тестовый сервер. Если вы добавляете сервер в качестве другого элемента кортежа в каждом случае:
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>
Ответ 3
Я использую небольшой трюк для хранения свойств, которые недоступны при инициализации.
Сначала я создаю класс Future
, который сохранит сохраненное свойство в будущем:
class Future<T> {
var value: T?
init(value: T? = nil) {
self.value = value
}
}
Затем я создаю свое перечисление:
enum Sample {
case Test(future: Future<String>)
}
Instantiate:
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)")
}
Это не то, что вы должны злоупотреблять, но в некоторых случаях это может быть полезно.
Надеюсь, что это поможет.
Ответ 4
Перечисление рассматривается как структурированный тип данных, который может быть изменен без необходимости изменять, например, String или Int несколько раз в вашем коде и с перечислением, которое мы никогда не можем беспокоиться об изменении одного и того же объекта более одного раза. Например, выпадающее меню:
enum DropDownMenuOptions: String {
case MenuOptionOne
case MenuOptionTwo
case MenuOptionThree
}
С сохраненными свойствами u может предварительно вычислить необходимую информацию и уменьшить код в вашей основной функции. Лучший пример - вычисление размера rect, например:
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))