字符串和静态字符串的区别

32

我正在浏览文档,并发现了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的内存占用要比较少。

2个回答

13

看起来 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 只需使用地址指针和长度即可实现。它所指向的地址仅是定义字符串的静态代码中的位置。由于 StaticString 不需要(需要)存在于堆栈中,因此无需进行引用计数。它既不被分配也不被释放,因此不需要引用计数。

"在编译时可知"是非常严格的。即使这样也不行:

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

出现错误:

错误:'String' 无法转换为 'StaticString'


我对“变量所持有的值将在编译时确定,只是分配给它的任何值在编译时都是可知的”有点困惑。那么,在编译时可知和在编译时确定之间有什么区别呢?此外,StaticString 不能被改变,但也可以被重新分配。因此,基本上编译器无法确定其值。 - Dániel Nagy
编译器在编译时无法确定类型为StaticString的变量将保存哪个值,但是它确信分配给该变量的任何值都是在编译时已知的字符串文字。Swift将如何处理这些信息只有Swift作者在发布开源代码之前才知道。 - vacawama

8

StaticString 在编译时是可知的。这可以导致优化。例如:

编辑:此部分不起作用,请参见下面的编辑

假设您有一个函数,它为一些在编译时定义的常量的一些String值计算一个Int

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在编译时可知,所以你猜它会做什么?它直接在编译时运行该函数(多么棒啊!)。我曾经读过一篇文章,作者检查了生成的汇编代码,实际上他发现已经计算出了数字。
正如你所看到的,这在某些情况下非常有用,比如上述例子中可以节省运行时间,因为这些任务可以在编译时完成。
编辑:Dániel Nagy和我进行了交谈。我之前给出的例子不起作用,因为StaticStringstringValue函数无法在编译时确定(因为它返回一个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
}

两个断点都被触发。

显然,有些方法可以在编译时完成,而其他方法则不能。我想知道编译器如何实际确定这一点。


所以基本上当我在调试时,在使用StaticString的函数中设置断点,它甚至不应该进入该函数? - Dániel Nagy
@DánielNagy 嗯,我无法验证这一点,无论是发布版还是调试版,它都会在断点处停止。你可能取消选择了“调试可执行文件”吗? - Kametrixom
我在哪里找到那个选项? - Dániel Nagy
@DánielNagy 会在菜单下:产品>方案>编辑方案>运行>信息。但我想问的是,为什么你的断点不停止,因为每次它都会停止我的程序。 - Kametrixom
让我们在聊天室里继续这个讨论 - Dániel Nagy
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接