Swift 中两个让人难受的字符串操作方式及正确姿势

2015-06-25 09:02:57 +08:00
 banxi1988
1. 其一是 获得字符数的属性。
在 在我最开始使用 Swift 编程时,我惊奇的发现 Swift的 String 结构没有获得字符串长度的 `size`,`length`,`count` 之类的属性或者方法。 然后发现可以使用 `countElements` 这样一个全局方法。
然后在 Swift 1.2 中这个方法名称简化成 `count` 。
虽然说也有其他编程语言如 Python 是用一个全局的内置函数 `len` 来求字符数 (1)的。
但是作为习惯了 `NSString` 来说,这有一点点难受。 所以一般会在 `String` 的 extension
中加上 `count` 或者 `length` 这么一个属性。

但是从 Swift 2.0 开始 对于 `String` 结构来说,`count` 方法已经不能用了。
经过我的探索,我发现,正确的获得字符串方法。变成了如下方法:

```Swift
let hello = "Hello,world"
hello.characters.count // 11
```

当然如果你误用了,Swift 提供的另一个以 `length` 开头的 `lengthOfBytesUsingEncoding` 方法,就可能有麻烦了。因为 它确实是统计字符所使用字节数的 :

```Swift
let hello = "Hello,world"
hello.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) // 11
let hello2 = "Hello,世界"
hello2.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) // 14
```

2. Swift 中另一个常用操作变得不很不直观的地方就是,substring 类的操作了。
一开始,或者从 `NSString` 时代的操作习惯,获得一个字符串的前三个字符,可能这么写:

```Swift
let hello = "Hello,world"
let hel = hello.substringToIndex(3)
```
或者想着 Swift 字符串可能支持切片,这样写: `hello[0..3]`

但是这些都会报编译错误。

之前我是将其转成 `NSString` 再操作的。 后来在 WWDC 上发现了下面的 Swift Style 的写法:

```Swift
let hello = "Hello,world"
let hel = hello.substringToIndex(advance(hello.startIndex, 3)) // "Hel"
```

核心在于这里的 Index 不是整形的,而是一个 `String.Index` 结构。
`String` 结构本身提供了 `startIndex` 和 `endIndex`
然后想得到其他位置的 Index 你一般就需要使用 这个全局的 `advance` 方法。

当然如果对 `Index` 结构多做些研究,根据它提供的 `successor` 方法,上面任务可以使用如下代码完成:

```Swift
let hello = "Hello,world"
let hel2 = hello.substringToIndex(hello.startIndex.successor().successor().successor()) // "Hel"
```


值得注意的是,为了防止越界,如果是取得最多18个字符,可以这样改写:

```Swift
let hello = "Hello,world"
let hel = hello.substringToIndex(advance(hello.startIndex, 18,hello.endIndex)) // "Hello,world"
```
5143 次点击
所在节点    iDev
12 条回复
babyname
2015-06-25 09:07:33 +08:00
这么反人类?
onevcat
2015-06-25 09:15:02 +08:00
可以去看看今年的 Session 408 以及标准库里 String 的实现方式你就会知道为什么要这么做了。尽量不要用传统 OO 的思维来看 Swift,这样会写得舒心一些。
pheyer
2015-06-25 09:15:19 +08:00
奇怪为什么不用length呢
BuilderQiu
2015-06-25 09:17:29 +08:00
有点诡异
反人类+1
forkon
2015-06-25 09:20:18 +08:00
swift变得强大的同时确实把一些东西整复杂了,当然更多的是简便
PrideChung
2015-06-25 09:25:37 +08:00
字符串切片的操作的确是很让人犯晕,在Ruby,Python里面都是很简单直观的写法,到Swift里面我是写一次忘一次
lilydjwg
2015-06-25 10:26:31 +08:00
Rust 里取字符数也是类似的,s.chars().count()。取(UTF-8)字节数是 s.bytes().count()。还有个 s.graphemes().count() 可以用,它是数 grapheme cluster 数的,比如 ç 是两个字符,却是一个 grapheme cluster(然后还有 ç 是一个字符)。人类的文字很复杂的,Unicode 的某些设计使之更复杂了……
fwee
2015-06-25 10:48:50 +08:00
@lilydjwg 那是1.8,1.9早就直接size取字符
otakustay
2015-06-25 11:03:55 +08:00
看上去很像是ObjC继承过来的理念,让代码本身的可读性接近自然语言,是一种OO与其它编程模式的混合体
chmlai
2015-06-25 11:12:57 +08:00
因为 Unicode, 早超过16位了, NSString.length 返回的长度也是不可靠的, 算得是内部 UTF-16 码元的数量, 不是字符数.
ibremn
2015-06-25 12:21:06 +08:00
最初的 FoundationKit 是随着 Unicode 标准一起诞生的,那时 Unicode 只有16位。后来 Unicode 扩展了,但 FoundationKit 的设计仍旧被保留下来了,所以 NSString.length 取到的东西,实际上是 UTF-16 码元数量,并非实际 charactor 数量,由此也带来了大量的令人困惑的 API 和很多错误的用法。看一下这个文章吧: http://objccn.io/issue-9-1/

新语言了,厘清一下这些概念也许会好些。我觉得以后应该也会有一些更简单些的写法,但应该不会再和 NSString 一样了。。
banxi1988
2015-06-26 07:30:26 +08:00
@onevcat 408 是讲 Protocol Oriented Programming 的,我发我现我之前看过,感觉很深一点是,Swift 的 Protocol 表现力增强不少。Apple 的这些 开发者 讲得很好,有设计的虚拟人物,循序渐进。

但是说到 String 类中字符个数的功能,Apple 不提供, 大家也还是会在扩展中 弄一个 `count`, `length` 之类的扩展的。 因为这是一种很强的使用习惯了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/200971

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX