Быстрая и изменяющаяся структура


97

Есть кое-что, чего я не совсем понимаю, когда дело касается изменения типов значений в Swift.

Как говорится в iBook «Язык программирования Swift»: По умолчанию свойства типа значения не могут быть изменены из его методов экземпляра.

Чтобы сделать это возможным, мы можем объявлять методы с mutatingключевым словом внутри структур и перечислений.

Для меня не совсем ясно следующее: переменную можно изменить извне структуры, но нельзя изменить ее собственными методами. Мне это кажется нелогичным, так как в объектно-ориентированных языках вы обычно пытаетесь инкапсулировать переменные, чтобы их можно было изменять только изнутри. Со структурами, похоже, все наоборот. Чтобы уточнить, вот фрагмент кода:

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

Кто-нибудь знает причину, по которой структуры не могут изменять свое содержимое изнутри своего собственного контекста, в то время как содержимое можно легко изменить в другом месте?

Ответы:


81

Атрибут изменчивости отмечен для хранилища (константы или переменной), а не для типа. Вы можете думать, что у struct есть два режима: изменяемый и неизменный . Если присвоить значение структуры для неизменного хранения (мы называем его letили константу в Swift) значение становится неизменяемым режимом, и вы не можете изменить любое состояние в стоимости. (включая вызов любого метода изменения)

Если значение присваивается изменяемому хранилищу (мы называем его varили переменной в Swift), вы можете изменять их состояние, и разрешается вызов метода изменения.

Кроме того, у классов нет этого неизменяемого / изменяемого режима. ИМО, это потому, что классы обычно используются для представления ссылочной сущности. А ссылочная сущность обычно является изменяемой, потому что очень сложно создавать ссылочные графы сущностей и управлять ими неизменным образом с надлежащей производительностью. Они могут добавить эту функцию позже, но не сейчас.

Программистам на Objective-C очень знакомы концепции изменчивого / неизменяемого. В Objective-C у нас было два отдельных класса для каждой концепции, но в Swift вы можете сделать это с помощью одной структуры. Половина работы.

Для программистов на C / C ++ это тоже очень знакомая концепция. Это именно то, что constключевое слово делает в C / C ++.

Кроме того, неизменяемое значение можно очень хорошо оптимизировать. Теоретически компилятор Swift (или LLVM) может выполнять копирование переданных значений let, как в C ++. Если вы разумно используете неизменяемую структуру, она превзойдет классы с пересчетом.

Обновить

Поскольку @Joseph утверждает, что это не объясняет , почему , я добавляю немного больше.

У структур есть два типа методов. простые и мутирующие методы. Обычный метод подразумевает неизменяемость (или неизменяемость) . Это разделение существует только для поддержки неизменной семантики. Объект в неизменяемом режиме вообще не должен изменять свое состояние.

Затем неизменяемые методы должны гарантировать эту семантическую неизменность . Это означает, что он не должен изменять никаких внутренних значений. Таким образом, компилятор запрещает любое изменение своего состояния в неизменяемом методе. Напротив, методы изменения могут изменять состояния.

И тогда у вас может возникнуть вопрос, почему по умолчанию используется неизменяемый? Это потому, что очень трудно предсказать будущее состояние изменяющихся значений, и это обычно становится основным источником головной боли и ошибок. Многие люди согласились с тем, что решение избегает изменяемых вещей, и тогда неизменяемость по умолчанию была на первом месте в списке желаний на протяжении десятилетий в языках семейства C / C ++ и его производных.

Подробнее см. Чисто функциональный стиль . В любом случае, нам все еще нужны изменяемые материалы, потому что у неизменяемых есть некоторые слабые места, и обсуждение их, похоже, не в теме.

Надеюсь, это поможет.


2
В общем, я могу интерпретировать это так: структура не знает заранее, будет ли она изменяемой или неизменной, поэтому она предполагает неизменность, если не указано иное. На вид это действительно имеет смысл. Это верно?
Mike Seghers

1
@MikeSeghers Я думаю, это имеет смысл.
eonil 04

3
Хотя это пока лучший из двух ответов, я не думаю, что он отвечает, ПОЧЕМУ, по умолчанию свойства типа значения не могут быть изменены из его методов экземпляра. вы намекаете, что это потому, что структуры по умолчанию являются неизменяемыми, но я не думаю, что это правда
Джозеф Найт

4
@JosephKnight Это не значит, что по умолчанию структуры неизменяемы. Это методы структур по умолчанию неизменяемы. Подумайте об этом как о C ++ с обратным предположением. В методах C ++ по умолчанию не является константой this, вы должны явно добавить a constк методу, если вы хотите, чтобы он имел 'const this' и разрешался для константных экземпляров (обычные методы не могут использоваться, если экземпляр имеет значение const). Swift делает то же самое с противоположным значением по умолчанию: метод имеет по умолчанию const this, и вы отмечаете его иначе, если он должен быть запрещен для постоянных экземпляров.
Analog File

3
@golddove Да, и нельзя. Вы не должны. Вам всегда нужно создать или вывести новую версию ценности, и ваша программа должна быть спроектирована таким образом, чтобы она преднамеренно использовалась. Функция существует, чтобы предотвратить такую ​​случайную мутацию. Опять же, это одна из основных концепций программирования в функциональном стиле .
eonil 09

25

Структура - это совокупность полей; если конкретный экземпляр структуры является изменяемым, его поля будут изменяемыми; если экземпляр неизменен, его поля будут неизменными. Таким образом, тип структуры должен быть подготовлен к тому, что поля любого конкретного экземпляра могут быть изменяемыми или неизменяемыми.

Чтобы метод структуры мог изменять поля базовой структуры, эти поля должны быть изменяемыми. Если метод, изменяющий поля базовой структуры, вызывается в неизменяемой структуре, он пытается изменить неизменяемые поля. Поскольку ничего хорошего из этого не выйдет, такое обращение нужно запретить.

Для этого Swift делит методы структуры на две категории: те, которые изменяют базовую структуру и, таким образом, могут быть вызваны только для изменяемых экземпляров структуры, и те, которые не изменяют базовую структуру и, таким образом, должны вызываться как в изменяемых, так и в неизменяемых экземплярах. . Последнее использование, вероятно, более частое, и поэтому используется по умолчанию.

Для сравнения, .NET в настоящее время (все еще!) Не предлагает способов отличить методы структуры, которые изменяют структуру, от методов, которые этого не делают. Вместо этого вызов метода структуры для экземпляра неизменяемой структуры заставит компилятор сделать изменяемую копию экземпляра структуры, позволить методу делать с ней все, что он хочет, и отбросить копию, когда метод будет выполнен. Это приводит к тому, что компилятор заставляет компилятор тратить время на копирование структуры вне зависимости от того, модифицирует ее метод или нет, даже если добавление операции копирования почти никогда не превратит семантически неправильный код в семантически правильный код; это просто приведет к тому, что код, который семантически неверен в одном отношении (изменение «неизменяемого» значения), будет неправильным в другом (позволяя коду думать, что он изменяет структуру, но отбрасывая предпринятые изменения). Разрешение методов структуры указывать, будут ли они изменять базовую структуру, может устранить необходимость в бесполезной операции копирования, а также гарантирует, что попытка ошибочного использования будет отмечена.


1
Сравнение с .NET и пример, в котором нет ключевого слова mutating, дали мне понять. Обоснование того, почему ключевое слово mutating принесет какую-либо пользу.
Binarian 02

20

Осторожно: условия непрофессионала впереди.

Это объяснение не совсем корректно на самом мелком уровне кода. Однако он был рассмотрен парнем, который действительно работает над Swift, и сказал, что это достаточно хорошо в качестве основного объяснения.

Поэтому я хочу попытаться просто и прямо ответить на вопрос «почему».

Чтобы быть точным: почему мы должны отмечать структурные функции, mutatingкогда мы можем изменять параметры структуры без каких-либо модифицирующих ключевых слов?

Итак, большая картина, это во многом связано с философией, которая поддерживает Swift Swift .

Вы могли бы думать об этом как о проблеме управления реальными физическими адресами. Когда вы меняете свой адрес, если у многих людей есть ваш текущий, вы должны уведомить их всех о том, что вы переехали. Но если ни у кого нет вашего текущего адреса, вы можете просто переехать куда хотите, и никому не нужно знать.

В этой ситуации Swift чем-то похож на почтовое отделение. Если много людей с большим количеством контактов много перемещаются, это очень дорого обходится. За обработку всех этих уведомлений приходится платить большому количеству людей, а процесс отнимает много времени и усилий. Вот почему идеальное состояние Swift - чтобы у всех в городе было как можно меньше контактов. Тогда ему не понадобится большой персонал для обработки изменений адреса, и он сможет делать все остальное быстрее и лучше.

Вот почему все Swift-люди бредят о типах значений и ссылочных типах. По своей природе ссылочные типы создают «контакты» повсюду, а типам значений обычно не требуется больше пары. Типы значений - "Swift" -er.

Итак , вернемся к маленькой картинке: structs. Структуры имеют большое значение в Swift, потому что они могут делать большинство вещей, которые могут делать объекты, но они являются типами значений.

Давайте продолжим аналогию с физическим адресом, представив, misterStructчто живет в someObjectVille. Аналогия здесь немного запутана, но я думаю, что она все же полезна.

Итак, для модели, изменяющей переменную на a struct, допустим, у misterStructнее зеленые волосы, и она получает команду переключиться на синие волосы. Как я уже сказал, аналогия оказывается неудачной, но происходит нечто вроде того, что вместо того, чтобы менять misterStructприческу, старый человек уезжает, а новый человек с голубыми волосами входит в комнату, и этот новый человек начинает называть себя misterStruct. Никому не нужно получать уведомление о смене адреса, но если кто-нибудь посмотрит на этот адрес, он увидит парня с синими волосами.

Теперь давайте смоделируем, что происходит, когда вы вызываете функцию в struct. В данном случае это как misterStructполучить заказ типа changeYourHairBlue(). Итак, почтовое отделение доставляет инструкцию: misterStruct«Иди смени свои волосы на синие и скажи мне, когда закончишь».

Если он следует той же рутине, что и раньше, если он делает то, что делал, когда переменная была изменена напрямую, то misterStructон переедет из собственного дома и вызовет нового человека с синими волосами. Но вот в чем проблема.

Приказ был: «Иди, смени свои волосы на синие и скажи мне, когда закончишь», но этот приказ получил зеленый парень. После того, как синий парень въезжает, уведомление о завершении работы все равно нужно отправить обратно. Но синий парень ничего об этом не знает.

[Если по-настоящему задуматься над этой аналогией, что-то ужасное, технически с зеленоволосым парнем случилось то, что после того, как он переехал, он немедленно покончил с собой. Значит, он не может никого уведомить о том, что задача выполнена ! ]

Чтобы избежать этой проблемы, в случаях , как это только , Swift должен идти непосредственно к дому по этому адресу и фактически изменить волосы текущего обитателя . Это совершенно другой процесс, чем просто отправить нового парня.

Вот почему Swift хочет, чтобы мы использовали mutatingключевое слово!

Конечный результат выглядит одинаково для всего, что должно относиться к структуре: теперь у обитателя дома голубые волосы. Но на самом деле процессы для его достижения совершенно разные. Похоже, он делает то же самое, но делает совсем другое. Это то, чего вообще не делают Swift-структуры.

Итак, чтобы немного помочь плохому компилятору и не заставлять его выяснять, мутирует ли функция сама по себе structили нет для каждой отдельной структурной функции, нас просят сжалиться и использовать mutatingключевое слово.

По сути, чтобы помочь Swift оставаться быстрым, мы все должны внести свой вклад. :)

РЕДАКТИРОВАТЬ:

Привет, чувак / чувак, который отверг меня, я просто полностью переписал свой ответ. Если вам так больше нравится, вы уберете отрицательный голос?


1
Это так прекрасно написано <f-word-here>! Итак, так легко понять. Я поискал это в Интернете и вот ответ (ну, в любом случае, без необходимости просматривать исходный код). Спасибо, что нашли время написать это.
Вайбхав

1
Я просто хочу подтвердить, что понимаю это правильно: можем ли мы сказать, что, когда свойство экземпляра Struct напрямую изменяется в Swift, этот экземпляр Struct «завершается», а новый экземпляр с измененным свойством «создается» за кулисами? И когда мы используем метод изменения внутри экземпляра для изменения свойства этого экземпляра, этот процесс завершения и повторной инициализации не происходит?
Тед

@Teo, я хотел бы ответить окончательно, но лучшее, что я могу сказать, это то, что я провел эту аналогию с реальным разработчиком Swift, и они сказали: «Это в основном верно», имея в виду, что это нечто большее в техническом смысле. Но с этим пониманием - что это нечто большее, чем просто это - в широком смысле, да, это то, о чем говорит эта аналогия.
Le Mot Juiced

8

Структуры Swift могут быть созданы как константы (через let) или как переменные (через var).

Рассмотрим Arrayструктуру Swift (да, это структура).

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

Почему добавление не работает с названиями планет? Потому что добавление помечено mutatingключевым словом. И поскольку planetNamesбыло объявлено использование let, все методы, отмеченные таким образом, являются запрещенными.

В вашем примере компилятор может сказать, что вы изменяете структуру, назначая одно или несколько ее свойств за пределами init. Если вы немного измените свой код, вы увидите это, xи yне всегда доступны за пределами структуры. Обратите внимание letна первую строку.

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error

5

Рассмотрим аналогию с C ++. Метод структуры в Swift mutatingbe / not- mutatingаналогичен методу в C ++, являющемуся не- const/ const. constАналогичным образом, метод, отмеченный в C ++, не может изменять структуру.

Вы можете изменить переменную вне структуры, но не можете изменить ее из собственных методов.

В C ++ вы также можете «изменить переменную вне структуры» - но только если у вас есть constнеструктурная переменная. Если у вас есть constструктурная переменная, вы не можете назначить ее переменной, а также не можете вызвать не- constметод. Точно так же в Swift вы можете изменить свойство структуры, только если переменная структуры не является константой. Если у вас есть структурная константа, вы не можете назначить ее свойству, а также не можете вызвать mutatingметод.


1

Я задавался вопросом о том же, когда начинал изучать Swift, и каждый из этих ответов, возможно, добавляющих некоторые идеи, сам по себе многословен и запутан. Я думаю, что ответ на ваш вопрос на самом деле довольно прост ...

Метод изменения, определенный внутри вашей структуры, требует разрешения на изменение каждого самого себя, который когда-либо будет создан в будущем. Что, если один из этих экземпляров будет назначен неизменной константе с помощью let? Ой ой. Чтобы защитить себя от самого себя (и дать возможность редактору и компилятору знать, что вы пытаетесь сделать), вы вынуждены быть явным, когда хотите предоставить методу экземпляра такую ​​силу.

Напротив, установка свойства извне вашей структуры работает с известным экземпляром этой структуры. Если он назначен константе, Xcode сообщит вам об этом в тот момент, когда вы наберете вызов метода.

Это одна из вещей, которые мне нравятся в Swift, когда я начинаю его все больше использовать - получать предупреждения об ошибках по мере их ввода. Конечно, это лучше, чем устранять неясные ошибки JavaScript!


1

SWIFT: использование функции изменения в структурах

Программисты Swift разработали Structs таким образом, что его свойства нельзя изменить в методах Struct. Например, проверьте код, приведенный ниже

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

При выполнении вышеуказанного кода мы получаем ошибку, потому что мы пытаемся присвоить новое значение совокупности свойств Struct City. По умолчанию свойства Structs нельзя изменять внутри его собственных методов. Так разработчики Apple построили его, поэтому структуры по умолчанию будут иметь статический характер.

Как решить эту проблему? Какая альтернатива?

Мутирующее ключевое слово:

Объявление функции как изменяющейся внутри Struct позволяет нам изменять свойства в Structs. Строка № 5 приведенного выше кода меняется на это:

mutating changePopulation(newpopulation : Int)

Теперь мы можем присвоить значение newpopulation имущества населения в рамках метода.

Примечание :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

Если мы используем let вместо var для объектов Struct, тогда мы не сможем изменить значение каких-либо свойств. Также по этой причине мы получаем ошибку, когда пытаемся вызвать функцию изменения с помощью экземпляра let. Поэтому лучше использовать var всякий раз, когда вы меняете значение свойства.

Хотелось бы услышать ваши комментарии и мысли… ..

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.