玩转 Swift 字符串截取

首先,这是一篇 关于Swift 的基础教程,里面包含 String 的部分API 以及扩展(Extension) 、下标(Subscripts)、自定义运算符、泛型等知识。不感兴趣的童鞋可以直接翻到底部看福利了。

字符串处理一直都是程序开发中不可避免的,而字符串截取/替换操作更是频繁。

Swift 的语法一直都在演变进化,在1.x到2.x的演变过程中也发生了很大的改变,在开源后发展更加迅速,即将发布的3.0版本也合并到了master.
字符串的处理也是变化的一部分,在最近的coding中遇到了一些频繁需要字符串处理的地方,过程中发现目前版本(2.x)字符串处理的语法实在有些啰嗦。

在Swift1.0 的时候字符串的截取是这样的:

var hp = "Hello, playground"
let hello = hp[0 ... 4]  // error

如果在Swift2.x可以,那真是极好的。

我喜欢这种以自然数提取字符串的方式,可是在Swift2.x中已经摒弃。

在Swift2.x中需要这样写:

let hello = hp.substringWithRange(Range<String.Index>(start: hp.startIndex, end: hp.startIndex.advancedBy(5)))  // "Hello"

或者更简单点:

let hello = hp[hp.startIndex..<hp.startIndex.advancedBy(5)]  // "Hello"

当然用OC中的NSString也是可以的,只是...

var nsHp = hp as NSString
let hello = nsHp.substringWithRange(NSMakeRange(0, 5))  // "Hello"

因为在Swift2.x (应该是在Swift1.2以后吧)中用String.Index代替了自然数, 新的API 毫无疑问的会占用我仅仅16GB的记忆量,我不想调用新API中那些所谓的牛逼黑科技代码。

为什么一个简单的字符串处理需要写的这么啰嗦,这里很好的总结了这一切

其实我更喜欢python截取字符串的风格,简单粗暴。

hp = "Hello, playground"
hello = hp[0:5]   // "Hello"
hello = hp[0:-12]  // "Hello"

所以我决定对它做些改变:

扩展 Extension

Swift 中的扩展和 Objective-C 中的分类类似,(只是Swift 中扩展没有名字)

Swift中的扩展可以:

  • 添加计算型属性和计算型类型属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议

下标 Subscripts

下标可以定义在类(class)、结构体(structure)和枚举(enumeration)中,是访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。

我现在添加String的扩展来定义下标,然后取出想要的字符串,下标接收Int类型的Range,Index 通过advancedBy(n)来递增,参数n是前进的个数,如果 n 大于 0 则会调用self的 successor() n 次,小于 0 则会调用 predecessor() n 次。

successor() 和 predecessor() 分别是可能出现的下一个值,和上一个值。

extension String {
  subscript (r: Range<Int>) -> String {
    get {
        let startIndex = self.startIndex.advancedBy(r.startIndex)
        let endIndex = self.startIndex.advancedBy(r.endIndex)
        return self[Range(start: startIndex, end: endIndex)]
      }
  }
}

let hello1 = hp[Range(start: 0, end: 5)]  // "Hello"
let hello2 = hp[0 ..< 5]   // "Hello"

自然数截取 极好的.

然而发现了问题,我要像python用下标从后向前取怎么办 :|

let hello = hp[0 ..< -12]    // "error"   ( system:不懂Range 别乱搞 :( )

不用Range 就是了:) 下面我将重新写一个接收两个参数下标方法,注意, 这里通过三元表达式来判断。在end 小于 0 时会 执行self.endIndex.advancedBy(end) , 前面有说到,也就是说会执行abs(end)次的predecessor()方法。

extension String {
  subscript (start: Int, end: Int) -> String {
    let s = self.startIndex.advancedBy(start)
    let e = end < 0 ? self.endIndex.advancedBy(end) : self.startIndex.advancedBy(end)
    return self[Range(start: s, end: e)]
  }
}
let hello = hp[0, -12]  // "Hello"

搞定!不过参数中[0, -12]中间的, 让整体像个Array ,感觉怪怪的,我不太喜欢,决定改掉。

自定义运算符

  • 自定义运算符可以由以下的 ASCII 字符 /、=、 -、+、!、*、%、<、>、&、|、^、? 、~自由组合.
  • 支持一元前缀 prefix (例如 --a, ++a, ! false)、一元后缀 postfix (a--, a++)、二元中置 infix (a+b, a-b, a==b)运算符
  • 支持自定义优先级

注:操作符前后空格也有规则,这里不多做说明,可自行查看官方文档。

在Swift中 : 归为字符,而非运算符,所以无法使用,我觉定用~来替代,创建一个二元中置运算符:

infix operator ~ {
  precedence 250  // 优先级 0~255 的一个值,具体参考官方文档
  associativity none  // 结合 left, right, none
}

func ~(start: Int, end: Int) -> (s: Int, e: Int) {
  return (start, end)     // 说好的运算符,除了返回个元组,啥也没干...
}

extension String {
  //接收元组的下标方法
  subscript (myRange: (s: Int, e: Int)) -> String {
    get {
        let s = self.startIndex.advancedBy(myRange.s)
        let e = myRange.e < 0 ? self.endIndex.advancedBy(myRange.e) : self.startIndex.advancedBy(myRange.e)
        return self[Range(start: s, end: e)]
    }
    set {
        let s = self.startIndex.advancedBy(myRange.s)
        let e = myRange.e < 0 ? self.endIndex.advancedBy(myRange.e) : self.startIndex.advancedBy(myRange.e)
        str.replaceRange(Range(start: s, end: e), with: newValue)
    }
  }
}

let hello = hp[0 ~ 5]  // "Hello"
hp[7 ~ 17] = "dimsky" // "Hello, dimsky"

没错,我在下标方法加了个set 方法用来替换指定下标的值。

可能有人对 advanceBy 这个方法还不太理解,下面我将用 + 运算符来替代这个方法:

func +<T: BidirectionalIndexType>(var index: T, count: Int) -> T {
  let num = abs(count)
  for _ in 0..<num {
    index = count < 0 ? index.predecessor() : index.successor()
  }
  return index
}

let hello = str[str.startIndex ..< str.startIndex.advancedBy(5)] // "hello"
let play = str[str.startIndex + 7 ..< str.startIndex + 11] // "play"

let play2 = str[str.startIndex + 7 ..< str.endIndex + -6] // "play"
let helloPlay = str[str.startIndex ..< str.endIndex.advancedBy(-6)] // "Hello, play"

我们可能还会需要经常的获取字符的下标,这是Swift 中正常获取字符所在下标的方法:

let index1 = hp.rangeOfString("H")?.startIndex  // 0 (String.CharacterView.Index)
let index2 = hp.rangeOfString("o")?.endIndex    // 5 (String.CharacterView.Index)

是的,我们没法通过上面的方法去直接获得自然数的下标,所以就有了下面的扩展方法:

extension String {
  func indexOfString(target: String) -> Int {
    let range = self.rangeOfString(target)
    if let range = range {
        return self.startIndex.distanceTo(range.startIndex)
    } else {
        return -1
    }
  }
}

let index = hp.indexOfString("o")    // 5  (Int)

当然接下来可以玩的还有很多
比如字符串替换:

hp["playground"] = "三上悠亚"   // waiting for you to complete

hp.stringByReplacingOccurrencesOfString("playground", withString: "铃原エミリ") //   "Hello, 铃原エミリ"  延用了OC的API

本文的目的并不是推崇自然数的方式访问字符串,仅仅是为了偷个小懒,啰嗦几句罢了。

与时俱进还是很重要的,Swift 字符串API 为什么难用(这里解释了这些)
...
...

然后呢

说好的福利呢。

三上悠亚
铃原エミリ

那就这样吧...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容