玩轉(zhuǎn) Swift 字符串截取

首先,這是一篇 關(guān)于Swift 的基礎(chǔ)教程,里面包含 String 的部分API 以及擴(kuò)展(Extension) 、下標(biāo)(Subscripts)、自定義運(yùn)算符、泛型等知識(shí)。不感興趣的童鞋可以直接翻到底部看福利了。

字符串處理一直都是程序開發(fā)中不可避免的,而字符串截取/替換操作更是頻繁。

Swift 的語法一直都在演變進(jìn)化,在1.x到2.x的演變過程中也發(fā)生了很大的改變,在開源后發(fā)展更加迅速,即將發(fā)布的3.0版本也合并到了master.
字符串的處理也是變化的一部分,在最近的coding中遇到了一些頻繁需要字符串處理的地方,過程中發(fā)現(xiàn)目前版本(2.x)字符串處理的語法實(shí)在有些啰嗦。

在Swift1.0 的時(shí)候字符串的截取是這樣的:

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

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

我喜歡這種以自然數(shù)提取字符串的方式,可是在Swift2.x中已經(jīng)摒棄。

在Swift2.x中需要這樣寫:

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

或者更簡(jiǎn)單點(diǎn):

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

當(dāng)然用OC中的NSString也是可以的,只是...

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

因?yàn)樵赟wift2.x (應(yīng)該是在Swift1.2以后吧)中用String.Index代替了自然數(shù), 新的API 毫無疑問的會(huì)占用我僅僅16GB的記憶量,我不想調(diào)用新API中那些所謂的牛逼黑科技代碼。

為什么一個(gè)簡(jiǎn)單的字符串處理需要寫的這么啰嗦,這里很好的總結(jié)了這一切

其實(shí)我更喜歡python截取字符串的風(fēng)格,簡(jiǎn)單粗暴。

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

所以我決定對(duì)它做些改變:

擴(kuò)展 Extension

Swift 中的擴(kuò)展和 Objective-C 中的分類類似,(只是Swift 中擴(kuò)展沒有名字)

Swift中的擴(kuò)展可以:

  • 添加計(jì)算型屬性和計(jì)算型類型屬性
  • 定義實(shí)例方法和類型方法
  • 提供新的構(gòu)造器
  • 定義下標(biāo)
  • 定義和使用新的嵌套類型
  • 使一個(gè)已有類型符合某個(gè)協(xié)議

下標(biāo) Subscripts

下標(biāo)可以定義在類(class)、結(jié)構(gòu)體(structure)和枚舉(enumeration)中,是訪問集合(collection),列表(list)或序列(sequence)中元素的快捷方式。

我現(xiàn)在添加String的擴(kuò)展來定義下標(biāo),然后取出想要的字符串,下標(biāo)接收Int類型的Range,Index 通過advancedBy(n)來遞增,參數(shù)n是前進(jìn)的個(gè)數(shù),如果 n 大于 0 則會(huì)調(diào)用self的 successor() n 次,小于 0 則會(huì)調(diào)用 predecessor() n 次。

successor() 和 predecessor() 分別是可能出現(xiàn)的下一個(gè)值,和上一個(gè)值。

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"

自然數(shù)截取 極好的.

然而發(fā)現(xiàn)了問題,我要像python用下標(biāo)從后向前取怎么辦 :|

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

不用Range 就是了:) 下面我將重新寫一個(gè)接收兩個(gè)參數(shù)下標(biāo)方法,注意, 這里通過三元表達(dá)式來判斷。在end 小于 0 時(shí)會(huì) 執(zhí)行self.endIndex.advancedBy(end) , 前面有說到,也就是說會(huì)執(zhí)行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"

搞定!不過參數(shù)中[0, -12]中間的, 讓整體像個(gè)Array ,感覺怪怪的,我不太喜歡,決定改掉。

自定義運(yùn)算符

  • 自定義運(yùn)算符可以由以下的 ASCII 字符 /、=、 -、+、!、*、%、<、>、&、|、^、? 、~自由組合.
  • 支持一元前綴 prefix (例如 --a, ++a, ! false)、一元后綴 postfix (a--, a++)、二元中置 infix (a+b, a-b, a==b)運(yùn)算符
  • 支持自定義優(yōu)先級(jí)

注:操作符前后空格也有規(guī)則,這里不多做說明,可自行查看官方文檔。

在Swift中 : 歸為字符,而非運(yùn)算符,所以無法使用,我覺定用~來替代,創(chuàng)建一個(gè)二元中置運(yùn)算符:

infix operator ~ {
  precedence 250  // 優(yōu)先級(jí) 0~255 的一個(gè)值,具體參考官方文檔
  associativity none  // 結(jié)合 left, right, none
}

func ~(start: Int, end: Int) -> (s: Int, e: Int) {
  return (start, end)     // 說好的運(yùn)算符,除了返回個(gè)元組,啥也沒干...
}

extension String {
  //接收元組的下標(biāo)方法
  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"

沒錯(cuò),我在下標(biāo)方法加了個(gè)set 方法用來替換指定下標(biāo)的值。

可能有人對(duì) advanceBy 這個(gè)方法還不太理解,下面我將用 + 運(yùn)算符來替代這個(gè)方法:

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"

我們可能還會(huì)需要經(jīng)常的獲取字符的下標(biāo),這是Swift 中正常獲取字符所在下標(biāo)的方法:

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

是的,我們沒法通過上面的方法去直接獲得自然數(shù)的下標(biāo),所以就有了下面的擴(kuò)展方法:

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)

當(dāng)然接下來可以玩的還有很多
比如字符串替換:

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

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

本文的目的并不是推崇自然數(shù)的方式訪問字符串,僅僅是為了偷個(gè)小懶,啰嗦幾句罷了。

與時(shí)俱進(jìn)還是很重要的,Swift 字符串API 為什么難用(這里解釋了這些)
...
...

然后呢

說好的福利呢。

三上悠亞
鈴原エミリ

那就這樣吧...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容