Быстрые "изменчивые" строки Swift действительно изменяемы или похожи на строки Java?
В языке Swift Programming в разделе Strings, подраздел String Mutability, он говорит следующее:
Вы указываете, может ли конкретный String
быть изменен (или изменен) путем назначения его переменной (в этом случае она может быть изменена) или константе (в этом случае она не может быть изменена):
и дает пример кода:
var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified"
Книга в iBooks здесь или в веб-браузере здесь.
В следующем параграфе он утверждает, что "строки являются типами значений".
Мой вопрос: для меня это не похоже на изменяемую строку. Это похоже на то, к чему я привык в Java (или С#, Python и другие): неизменяемые строковые объекты с изменяемыми привязками переменных. Другими словами, существовал объект "Лошадь", а затем он создал новый объект String "Лошадь и каретка" и установил его в одну и ту же переменную. И так как нет способа сказать разницу между ссылкой на неизменяемый объект и тип значения (правильно?), Мне интересно: почему они описывают это так? Есть ли разница между этими строками Swift и тем, как это происходит на Java? (Или С#, Python, Objective-C/NSString)
Ответы
Ответ 1
В определенном смысле "изменчивые" и "неизменные" имеют смысл только при разговоре о ссылочных типах. Если вы попытаетесь расширить его до типов значений, то все типы значений можно считать функционально эквивалентными "неизменяемым" ссылочным типам.
Например, рассмотрим var
типа Int
. Это изменчиво? Некоторые из вас могут сказать, конечно - вы можете изменить свое видимое "значение", назначив ему (=
). Однако то же самое можно сказать о var
of NSNumber
и NSString
- вы можете изменить его видимое значение, назначив ему. Но NSNumber
и NSString
описываются как непреложные классы.
То, что действительно происходит для ссылочных типов, заключается в том, что назначение им приводит к тому, что переменная (указатель) указывает на новый объект. Ни сам старый, ни новый объект не "изменен", но поскольку он указывает на другой объект, вы видите "новое" значение.
То, что мы подразумеваем, когда говорим, что класс является "изменчивым", заключается в том, что он предлагает API (метод или ссылку) для фактического изменения содержимого объекта. Но откуда мы знаем, что объект изменился? (а скорее это новый объект?) Это потому, что мы могли бы иметь другую ссылку на один и тот же объект, а изменения объекта через одну ссылку видны через другую ссылку. Но эти свойства (указывающие на разные объекты, имеющие несколько указателей на один и тот же объект) по своей сути применимы только к ссылочным типам. Типы значений, по определению, не могут иметь такой "общий доступ" (если только часть "значения" не является ссылочным типом, например, в Array
), и, следовательно, следствием "изменчивости" не может быть значение для типов значений.
Итак, если вы создадите неизменяемый класс, который обертывает целое число, он будет в рабочем порядке эквивалентен Int
- в обоих случаях, единственный способ изменить значение переменной - это присвоить ему (=
), Поэтому Int
также следует считать "неизменным".
Типы значений в Swift немного сложнее, потому что они могут иметь методы, некоторые из которых могут быть mutating
. Итак, если вы можете вызвать метод mutating
для типа значения, он изменен? Тем не менее, мы можем преодолеть это, если мы рассмотрим вызов метода mutating
для типа значения для синтаксического сахара для назначения ему целого нового значения (независимо от того, какой метод будет мутировать его).
Ответ 2
В Swift Структуры и перечисления - это типы значений:
Фактически, все основные типы в Swift-целых числах, числа с плавающей запятой, булевы, строки, массивы и словари - являются типами значений и реализуются как структуры за кулисами.
Итак, строка - это тип значения, который копируется при назначении и не может иметь несколько ссылок, но его базовые персональные данные хранятся в совместно используемом буфере копирования-на-записи. Ссылка API для String
struct гласит:
Хотя строки в Swift имеют семантику значений, строки используют стратегию копирования на запись для хранения своих данных в буфере. Затем этот буфер может совместно использоваться разными копиями строки. Строковые данные копируются только лениво, после мутации, когда более одного экземпляра строки использует один и тот же буфер. Следовательно, первая в любой последовательности мутирующих операций может стоить O (n) времени и пространства.
Таким образом, var
vs. let
объявляет изменчивую и неизменяемую привязку к символьному буфере, который кажется неизменным.
var v1 = "Hi" // mutable
var v2 = v1 // assign/pass by value, i.e. copies the String struct
v1.append("!") // mutate v1; does not mutate v2
[v1, v2] // ["Hi!", "Hi"]
let c1 = v1 // immutable
var v3 = c1 // a mutable copy
// c1.append("!") // compile error: "Cannot use mutating member on immutable value: 'c1' is a 'let' constant"
v3 += "gh" // mutates v3, allocating a new character buffer if needed
v3.append("?") // mutates v3, allocating a new character buffer if needed
[c1, v3] // ["Hi", "High?"]
Это как не конечные vs. final
переменные Java String с двумя морщинами.
- С изменением привязки вы можете вызвать методы mutating. Поскольку не может быть никакого наложения на тип значения, вы не можете определить, действительно ли мутирующий метод модифицирует буфер символов или переназначает переменную String, за исключением воздействия на производительность.
- Реализация может оптимизировать мутирующие операции путем изменения символьного буфера на месте, когда он используется только одним экземпляром String.
Ответ 3
На самом деле строки Swift выглядят точно так же, как Objective-C (неизменяемый) NSString
; Я нашел это в документации, к которой вы привязались -
Swifts Тип String легко соединяется с классом NSString Foundations. Если вы работаете с базой Foundation в Cocoa или Cocoa Touch, весь API NSString доступен для вызова любого созданного вами строкового значения в дополнение к функциям String, описанным в этой главе. Вы также можете использовать значение String с любым API, для которого требуется экземпляр NSString.
Ответ 4
Строки Swift - это значения, а не объекты. Когда вы меняете значение, оно становится другим значением. Поэтому в первом случае, используя var
, вы просто присваиваете новое значение той же переменной. В то время как let
гарантированно не имеет никакого другого значения после его назначения, поэтому он дает ошибку компилятора.
Итак, чтобы ответить на ваш вопрос, строки Swift получают почти то же отношение, что и в Java, но считаются значениями, а не объектами.
Ответ 5
Сначала вы используете неизменяемый метод, который создает новый экземпляр значения. Вам нужно использовать методы mutating
, такие как extend
, чтобы выполнить операцию в мутирующей семантике.
То, что вы сделали здесь, - это создание новой неизменяемой строки и привязка ее к существующему имени.
var variableString = "Horse"
variableString += " and carriage"
Это мутирует строку на месте без дополнительной привязки имени.
var variableString = "Horse"
variableString.extend(" and carriage")
Во-вторых, цель неизменяемого/изменчивого разделения обеспечивает более легкую и более безопасную модель программирования. Вы можете безопасно делать более предположения относительно неизменной структуры данных, и это может устранить многие случаи головной боли. И это помогает оптимизации. Без неизменяемого типа вам нужно скопировать все данные, когда вы передаете их в функцию. В противном случае исходные данные могут быть изменены функцией, и такой эффект непредсказуем. Затем такие функции нужно аннотировать как "Эта функция не изменяет передаваемые данные". С неизменным типом вы можете с уверенностью предположить, что функция не может изменить данные, тогда вам не нужно ее копировать. Swift делает это неявно и автоматически по умолчанию.
Да, фактически изменяемая/неизменная разница - не что иное, как разница в интерфейсе на языках более высокого уровня, таких как Swift. Ценностная семантика просто означает, что она не поддерживает сравнение идентичности. Поскольку многие детали абстрагированы, внутренняя реализация может быть чем угодно. Быстрое кодовое замечание поясняет, что строка использует трюки COW, и я полагаю, что оба неизменяемых/изменяемых интерфейса фактически отображаются в основном неизменяемые реализации. Я считаю, что вы получите такой же результат, независимо от выбора интерфейса. Но все же это обеспечивает преимущества неизменяемого типа данных, о которых я упоминал.
Тогда пример кода фактически делает то же самое. Единственное различие заключается в том, что вы не можете мутировать оригинал, связанный с его именем в неизменяемом режиме.