Разница между String и StaticString

Я просматривал документы и нашел StaticString. В нем указано:

Простая строка, предназначенная для представления текста, "знающего во время компиляции".

Первоначально я думал, что String имеет такое же поведение, как NSString, которое известно во время компиляции, но похоже, что я ошибался. Поэтому мой вопрос заключается в том, когда следует использовать StaticString вместо String, и разве только, что StaticString известно во время компиляции?

Одна вещь, которую я нашел, -

var a: String = "asdf" //"asdf"
var b: StaticString = "adsf" //{(Opaque Value), (Opaque Value), (Opaque Value)}

sizeofValue(a)  //24
sizeofValue(b)  //17

Итак, похоже, что StaticString имеет немного меньший объем памяти.

Ответ 1

StaticString можно узнать во время компиляции. Это может привести к оптимизации. Пример:

EDIT: эта часть не работает, см. ниже приведенное ниже

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

let someString = "Test"
let otherString = "Hello there"

func numberForString(string: String) -> Int {
    return string.stringValue.unicodeScalars.reduce(0) { $0 * 1 << 8 + Int($1.value) }
}

let some = numberForString(someString)
let other = numberForString(otherString)

Таким образом, функция будет выполняться с помощью "Test" и "Hello there", когда она действительно вызывается в программе, когда приложение запускается, например. Определенно во время выполнения. Однако, если вы измените свою функцию, выберите StaticString

func numberForString(string: StaticString) -> Int {
    return string.stringValue.unicodeScalars.reduce(0) { $0 * 1 << 8 + Int($1.value) }
}

компилятор знает, что прошедший в StaticString познаваемый во время компиляции, так что угадайте, что он делает? Он запускает функцию прямо во время компиляции (насколько это удивительно!). Однажды я прочитал статью об этом, автор проверил сгенерированную сборку и фактически нашел уже вычисленные числа.

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

EDIT: Dániel Nagy и я имел беседу. Вышеприведенный пример не работает, потому что функция stringValue StaticString не может быть известна во время компиляции (потому что она возвращает a String). Вот лучший пример:

func countStatic(string: StaticString) -> Int {
    return string.byteSize    // Breakpoint here
}

func count(string: String) -> Int {
    return string.characters.count    // Breakpoint here
}

let staticString : StaticString = "static string"
let string : String = "string"


print(countStatic(staticString))
print(count(string))

В сборке релизов срабатывает только вторая точка останова, тогда как если вы измените первую функцию на

func countStatic(string: StaticString) -> Int {
    return string.stringValue.characters.count    // Breakpoint here
}

срабатывают обе точки останова.

По-видимому, есть некоторые методы, которые можно выполнить во время компиляции, а другие - нет. Интересно, как на самом деле это компилятор.

Ответ 2

Похоже, что StaticString может содержать строковые литералы. Вы не можете присвоить ему переменную типа String, и она не может быть мутирована (например, с +=).

"Знать во время компиляции" не означает, что значение, удерживаемое переменной, будет определено во время компиляции, так что любое назначенное ему значение известно во время компиляции.

Рассмотрим этот пример, который работает:

var str: StaticString

for _ in 1...10 {
    switch arc4random_uniform(3) {
    case 0: str = "zero"
    case 1: str = "one"
    case 2: str = "two"
    default: str = "default"
    }
    print(str)
}

В любое время, когда вы можете предоставить Swift дополнительную информацию о том, как использовать переменную, она может оптимизировать код, используя его. Ограничивая переменную до StaticString, Swift знает, что переменная не будет мутирована, чтобы она могла более эффективно ее сохранять или более эффективно использовать отдельные символы.

Фактически StaticString может быть реализован только с указателем адреса и длиной. Адрес, на который он указывает, является только местом в статическом коде, где определена строка. A StaticString не нужно ссылаться на подсчет, так как он не существует (должен) существовать в куче. Он не выделяется и не освобождается, поэтому не требуется подсчет ссылок.

"Знать во время компиляции" довольно строго. Даже это не работает:

let str: StaticString = "hello " + "world"

который выходит из строя с ошибкой:

error: 'String' не конвертируется в 'StaticString'