原作者:Nate Cook
遇到問題磅摹,哦,要用NSRegular?Expression了稠腊。?
其實(shí)呢,有一些淮逻,是要注意的。
正則表達(dá)式是一種DSL, 有一些討論。說他不好,畢竟Regex里面都是各種符號。說他好笆檀,Regex簡明強(qiáng)大,用途廣泛盒至。
公認(rèn)的是酗洒,Cocoa 給NSRegular?Expression 設(shè)計(jì)了一套冗長的API. 先比對下Ruby,這段Ruby代碼的作用是,從HTML代碼片段中提取URL.
htmlSource = "Questions? Corrections? <a href=\"https://twitter.com/NSHipster\"> @NSHipster</a> or <a href=\"https://github.com/NSHipster/articles\">on GitHub</a>."
linkRegex = /<a\s+[^>]*href="([^"]*)"[^>]*>/i
links = htmlSource.scan(linkRegex)
puts(links)
# https://twitter.com/NSHipste
#?https://github.com/NSHipster/articles
Ruby 三行代碼實(shí)現(xiàn)枷遂。
現(xiàn)在看Swift 中用?NSRegularExpression 樱衷,同樣的功能實(shí)現(xiàn)(從HTML代碼片段中提取URL.)
let htmlSource = "Questions? Corrections? <a href=\"https://twitter.com/NSHipster\">?@NSHipster</a> or
<a href=\"https://github.com/NSHipster/articles\">on GitHub</a>."
let linkRegexPattern = "<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>"? ?// 比起Ruby 的, 多了一個(gè)轉(zhuǎn)義字符 '\'
let linkRegex = try! NSRegularExpression(pattern: linkRegexPattern, options: .caseInsensitive )
let matches = linkRegex.matches(in: htmlSource,? range: NSRange(location: 0, length: htmlSource.utf16.count))
let links = matches.map{ result -> String in
? ? ? ?let hrefRange = result.rangeAt(1)
? ? ? ?let start = String.UTF16Index(encodedOffset: hrefRange.location)? ? ? ? ?
? ? ? ?let end = String.UTF16Index(encodedOffset: hrefRange.location + hrefRange.length)
? ? ? return String(htmlSource.utf16[start..<end])!
}
print(links)
// ["https://twitter.com/NSHipster", "https://github.com/NSHipster/articles"]
{
效果圖:?
簡單說明:?
第一段酒唉,? ?<a\\s+ , 先找??<a 兩個(gè)特定字符矩桂, 再來一個(gè)轉(zhuǎn)義,尋找一到多個(gè)空格痪伦。
第二段侄榴, [^>]*, 要求 緊接著的任意的字符串中,不能包含 > .
第三段网沾, href=\" . 尋找緊接著 href=\"?
第四段癞蚕, ([^\"]*),緊接著的任意字符串不得包含?\"
第五段, \"[^>]*> , 先來一個(gè) 轉(zhuǎn)義辉哥,再要求緊接著的字符串滿足 桦山,* 和 > 之間, 不包含 > .
}
NSRegular?Expression 不好的醋旦,就說到這里恒水。
原文(英文原版)不會深入淺出地講解正則表達(dá)式(要自己學(xué)習(xí) 通配符‘*’ ‘+’? , 反向引用‘^’ 饲齐,提前量‘[]’? 钉凌,等等?)
Swift 中的 Regex 學(xué)習(xí),? NSRegularExpression,?NSTextCheckingResult , 注意下難點(diǎn)捂人、特例御雕, 就可以了。
字符串方法 先慷,?NSString?Methods
上手Cocoa中的正則,當(dāng)然是不用?NSRegular?Expression .
NSString 中的?range(of:...) 方法 可實(shí)現(xiàn)輕量級的字符串查找咨察,需要用?.regularExpression 切換 regular expression mode .? ( OC 的 NSString论熙, 對應(yīng) Swift 中的 String)?
OC 代碼
NSString * source = @"For NSSet and NSDictionary, the breaking...";
// 作用是 , 匹配字符串中長得像Cocoa 類型 type 的 單詞
// 例如: UIButton, NSCharacterSet, NSURLSession
NSString * typePattern = @"[A-Z]{3,}[A-Za-z0-9]+";
NSRange typeRange = [source rangeOfString: typePattern options: NSRegularExpressionSearch];
if( typeRange.location! = NSNotFound ){
? ? ? ?NSLog(@"First type: %@",[sourcesubstringWithRange:typeRange]);
? ? ? ?// First type: NSSet
}
Swift 代碼
let source="For NSSet and NSDictionary, the breaking..."
// Matches anything that looks like a Cocoa type:?
// UIButton, NSCharacterSet, NSURLSession, etc.
let typePattern = "[A-Z]{3,}[A-Za-z0-9]+"
if let typeRange = source.range(of: typePattern , options: .regularExpression){
????????print("First type: \(source[typeRange])")
????????// First type: NSSet
}
{
link:?https://regex101.com/r/U7TC8v/1
第一段摄狱, [A-Z]{3,} ,? 用于匹配至少3個(gè)A-Z 中的字符脓诡。
第二段无午, [A-Za-z0-9]+ ,? ?用于匹配至少一個(gè)該集合中的字符,A-Z 之間 加上 a-z 之間祝谚, 再加上 0-9 之間
}
替換也是常用的功能宪迟,同樣的選項(xiàng)option, 使用?replacingOccurrences(of:with:...) .
下面,用一個(gè)看起來怪的代碼交惯,在上句中的coco 類型單詞外面加括號次泽。看起來清楚一些吧席爽。
OC 代碼
NSString * markedUpSource = [source stringByReplacingOccurrencesOfString:typePattern withString: @"`$0`" options: NSRegularExpressionSearch range : NSMakeRange(0,source.length)];
NSLog(@"%@",markedUpSource);
// "For `NSSet` and `NSDictionary`, the breaking..."
Swift 代碼
let markedUpSource = source.replacingOccurrences(of: typePattern, with: "`$0`", options: .regularExpression)
print(markedUpSource)
// "For `NSSet` and `NSDictionary`, the breaking...""
{
說明:?
這里有一個(gè) 正則表達(dá)式中意荤,獲取正則分段的概念。
可以參見 這個(gè)鏈接:?????https://stackoverflow.com/questions/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression
}
用上面的替換模版只锻,正則可以處理推導(dǎo)分組玖像。西方有一個(gè)關(guān)于元音的字母轉(zhuǎn)換,
OC 代碼
NSString * ourcesay = [source stringByReplacingOccurrencesOfString: @"([bcdfghjklmnpqrstvwxyz]*)([a-z]+)" withString: @"$2$1ay" options: NSRegularExpressionSearch | NSCaseInsensitiveSearch range: NSMakeRange(0,source.length)];
NSLog(@"%@",ourcesay);
// "orFay etNSSay anday ictionaryNSDay, ethay eakingbray..."
Swift 代碼
let ourcesay = source.replacingOccurrences(of: "([bcdfghjklmnpqrstvwxyz]*)([a-z]+)", with: "$2$1ay", options: [.regularExpression,.caseInsensitive])
print(ourcesay)
// "orFay etNSSay anday ictionaryNSDay, ethay eakingbray..."
{
link :? ?https://regex101.com/r/lZxWuY/2
第一段齐饮, ([bcdfghjklmnpqrstvwxyz]*)? , 匹配不限長度的 不含 a e i o u 的 任意英文字母捐寥。
第二段,?([a-z]+) ,? 匹配 至少一個(gè)長度的 任意英文字母
}
很多需要運(yùn)用正則的場景下祖驱,上面兩個(gè)方法就可以了握恳。復(fù)雜的功能實(shí)現(xiàn),就要用到NSRegularExpression這個(gè)類了羹膳。首先睡互, 解決Swift中的一個(gè)正則新手易犯錯(cuò)誤。
NSRangeand Swift
比起 Foundation 的 NSString , Swift有著作用域更大陵像、更復(fù)雜的API 就珠,來處理字符串的字符和子串。Swift的標(biāo)準(zhǔn)庫有四種接口來處理字符數(shù)據(jù)醒颖,可以用字符妻怎、Unicode 標(biāo)量、UTF-8 碼泞歉、?UTF-16 碼 來獲取字符串的數(shù)據(jù)逼侦。
這與 NSRegularExpression 相關(guān),很多?NSRegularExpression 方法使用?NSRange腰耙, 用?NSTextCheckingResult 對象保存匹配到的數(shù)據(jù)榛丢。 NSRange 使用整型 integer ,記錄他的起始點(diǎn) location 和 字符長度?length 挺庞。但是字符串 String 是不用整型?integer 作為索引的
let range = NSRange(location: 4, length: 5)
// 下面的代碼晰赞,是編不過的
source[range]
source.characters[range]
source.substring(with:range)
source.substring(with:range.toRange()!)
接著來。
Swift 中的 String 其實(shí)是通過 utf16 接口操作的,同 Foundation 框架下 NSString 的 API 一樣掖鱼∪蛔撸可以通過 utf16 接口的方法,用整型 integer 創(chuàng)建索引戏挡。?
let start = String.UTF16Index(encodedOffset: range.location)
let end = String.UTF16Index(encodedOffset: range.location + range.length)
let substring = String(source.utf16[start..<end])!
// substring 現(xiàn)在是 "NSSet"
下面放一些 String 的 Util 代碼芍瑞,調(diào)用 Swift 相關(guān)正則的語法糖, 有 Objective-C 的感覺
extensionString{
????????/// 這個(gè) nsrange 屬性 褐墅,包含了字符串的所有范圍
????????var nsrange: NSRange{
????????????????return NSRange(location:0,length:utf16.count)
????????}
????????/// 用之前給出的?nsrange 屬性拆檬,返回一個(gè)字串。?
? ? ? ?/// 如果字符串中沒有這個(gè)范圍掌栅, 就 nil 了
????????func substring( with nsrange: NSRange) -> String?{
????????????????guard let range = Range(nsrange, in: self)
????????????????????else { return nil }
? ? ? ? ? ? ? ? ?return String( self[range] )
????????}
????????/// 返回 與之前掏出來的 nsrange 屬性秩仆,等同的 range
????????///?如果字符串中沒有這個(gè)范圍, 就 nil 了
????????func range(from nsrange: NSRange) -> Range<Index>?{
? ??????????????guard let range = Range(nsrange, in: self)? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? else { return nil }? ? ? ?
? ? ? ? ? ? ? return range
? ? ? ? ? ? }
? ? ? }
接下來體驗(yàn)的 NSRegularExpression 猾封,有用到上面的 Util 方法澄耍。
NSRegularExpression?和 NSTextCheckingResult
之前學(xué)習(xí)了在字符串中找出第一個(gè)匹配到的數(shù)據(jù),與匹配到的數(shù)據(jù)之間的替換晌缘。復(fù)雜些的情況齐莲,就要用到 NSRegularExpression 了。先造一個(gè)簡單的文本各式匹配 miniPattern 磷箕,找出文本中的 *bold* 和 _italic_
造一個(gè)?NSRegularExpression 對象选酗,要傳入一個(gè)匹配規(guī)則的字符串 pattern ,還有一些選項(xiàng)可以設(shè)置岳枷。miniPattern 用星號?* 或 下劃線?_ 開始查找匹配的單詞芒填。找到星號或下劃線后,就匹配一個(gè)到多個(gè)字符的格式空繁,用找到的第一個(gè)匹配的字符再次match終止一次查找殿衰。匹配到的首字母和文本,都會被保存到查詢結(jié)果中盛泡。
OC 代碼
NSString * miniPattern = @"([*_])(.+?)\\1";
NSError * error = nil;
NSRegularExpression * miniFormatter = [ NSRegularExpression regularExpressionWithPattern: miniPattern options: NSRegularExpressionDotMatchesLineSeparators error: &error];
Swift 代碼
let miniPattern = "([*_])(.+?)\\1"
let miniFormatter = try! NSRegularExpression(pattern: miniPattern, options: .dotMatchesLineSeparators)
// 如果 miniPattern?有誤闷祥, NSRegularExpression 初始化就會拋異常。
如果 pattern有誤傲诵,?NSRegularExpression 初始化就會拋異常凯砍。一旦 NSRegularExpression 對象建好了,就可以用它處理不同的字符串拴竹。
{
說明:?
"([*_])(.+?)\\1"? 悟衩,?這個(gè)正則表達(dá)式? 分三段,
第一段?([*_]) 栓拜,匹配 中括號 中的 任意一個(gè)字符座泳, 就是 * 或者 _ ;
第二段?(.+?) 斑响, 匹配 長度大于1的 任意字符串;
第三段?\\1钳榨, 有一個(gè)轉(zhuǎn)義字符, 匹配之前獲取到的第一個(gè)同等字符串
}
OC 代碼
NSString * text = @"MiniFormatter handles *bold* and _italic_ text.";
NSArray?* matches = [miniFormatter matchesInString: text options: kNilOptions range:? NSMakeRange(0,text.length)];
// matches.count == 2
Swift 代碼
let text = "MiniFormatter handles *bold* and _italic_ text."
let matches = miniFormatter.matches(in: text, options: [], range: text.nsrange )
// matches.count == 2
調(diào)用?matches(in:options:range:) 方法纽门,可以取出包含 NSTextCheckingResult 元素的數(shù)組薛耻。 多種文本處理類都有用到NSTextCheckingResult 類型,譬如?NSDataDetector?和 NSSpellChecker . 返回的數(shù)組中赏陵,一個(gè)匹配有一個(gè)NSTextCheckingResult .
通常要取得的是匹配到的范圍饼齿,就在每個(gè)結(jié)果的range屬性里面。通常要取得的還有蝙搔,正則表達(dá)式中任意匹配到的范圍缕溉。 可以通過?numberOfRanges 屬性 和?rangeAt(_:) 方法,找出指定的范圍吃型。
range(at:)
Returns the result type that the range represents.
range(at:) 方法证鸥, 返回的結(jié)果就是對應(yīng)的范圍
Discussion
A result must have at least one range, but may optionally have more (for example, to represent regular expression capture groups).
Passing?range(at:)?the value?0?always returns the value of the the?range?property. Additional ranges, if any, will have indexes from?1?to?numberOfRanges-1.
討論下,
返回的結(jié)果勤晚,至少有一個(gè)范圍枉层。往往有更多,可選的赐写。( 正則表達(dá)式捕獲組鸟蜡,對應(yīng)的)
?range(at:) 方法返回的第一個(gè)結(jié)果,就是 range 屬性的值挺邀。如果有額外的揉忘,返回的結(jié)果對應(yīng)的索引就是從 1 到?numberOfRanges-1? ?
引用下?蘋果文檔?,
?range 0 是完全匹配到的范圍端铛,也是肯定能取到的泣矛。
然后從第1個(gè)到 第(numberOfRanges - 1)個(gè)的 ranges 數(shù)組中的值,就是分段沦补,對應(yīng)每一段正則匹配的結(jié)果乳蓄。
使用之前給出的NSRange的取子串方法,就可以用 range 來取出匹配到的結(jié)果夕膀。
OC 代碼
for match in matches {
? ? ? ?let stringToFormat = text.substring(with: match.rangeAt(2))!
? ? ? ?switch text.substring(with: match.rangeAt(1))! {?
? ? ? ?case "*" :
? ? ? ? ?????????print("Make bold: '\(stringToFormat)'")
????????case "_" :
????????????????print("Make italic: '\(stringToFormat)'")
????????default :
????????????????break
????????????????}
}
// 打印出
// Make bold: 'bold'
// Make italic: 'italic'
Swift 代碼
for match in matches {
????let stringToFormat = text.substring(with:?match.range(at: 2) )!
????switch text.substring(with:?match.range(at: 1)? )! {
????case "*" :
????????????print("Make bold: '\(stringToFormat)'")
????case "_":
????????????print("Make italic: '\(stringToFormat)'")
????default: break
????}
}
// 打印出
// Make bold: 'bold'
// Make italic: 'italic'
對于基礎(chǔ)的替換虚倒,直接用??stringByReplacingMatches(in:options:range:with:) 方法,?String.replacingOccurences(of:with:options:) 的加強(qiáng)版 产舞。上例中魂奥,不同的正則匹配 (?bold , italic)易猫,用不同的替換模版耻煤。
按照倒敘,循環(huán)訪問這些匹配結(jié)果,這樣就不會把后面的 match 范圍搞亂哈蝇。
var formattedText = text
Format:?
for match inmatches.reversed () {
????let template: String
????switch text.substring(with:?match.range(at:1)? ) ?? ""{
????case "*":
?????????template? = "<strong>$2</strong>"
????case "_":?
????????template = "<em>$2</em>"????
????default:????break Format
????}
????let matchRange = formattedText.range(from:match.range)! ????????// see above?
????let replacement = miniFormatter.replacementString( for: match, in: formattedText, offset: 0, template: template)
????formattedText.replaceSubrange( matchRange , with: replacement)
}
// 'formattedText' is now:
// "MiniFormatter handles bold and italic text."
通過自定義的模版棺妓,調(diào)用?miniFormatter.replacementString(for:in:...) 方法, 然后呢炮赦,每一個(gè)NSTextCheckingResult 實(shí)例會隨之產(chǎn)生一個(gè)對應(yīng)的替換字符串怜跑。
Expression and Matching Options? ?, 表達(dá)式與匹配選項(xiàng)
NSRegularExpression 是高度可配置的吠勘。弄一個(gè)實(shí)例性芬,或者調(diào)用執(zhí)行正則匹配的方法,都可以傳不同選項(xiàng)的組合剧防。
NSRegularExpression.Options
* .caseInsensitive : 字母大小寫忽略植锉。 開啟字母大小寫忽略的匹配,就是 i 標(biāo)記
*?.allowCommentsAndWhitespace : 允許注釋峭拘、空格俊庇。 忽略 # 和句尾間任意的空格和注釋。所以所以你可以嘗試格式化和記錄正則匹配鸡挠,有了注釋和空格暇赤,正則會好讀一點(diǎn)。 等價(jià)于 x 標(biāo)記
*?.ignoreMetacharacters: 忽略元符號宵凌,忽略關(guān)鍵字鞋囊。String.range(of:options:) 方法中的去正則化,與 .regularExpression 正則選項(xiàng)相反瞎惫。這實(shí)際上就是正則變?yōu)楹唵蔚奈谋舅阉髁锔雎运械恼齽t關(guān)鍵字和運(yùn)算符。
*?.dotMatchesLineSeparators: 句點(diǎn)分行匹配瓜喇。允許 , 關(guān)鍵字匹配換行符以及其他字符挺益。就是 s 標(biāo)記。
*?.anchorsMatchLines: 句中錨點(diǎn)匹配乘寒。允許 ^ (開始)和 $ (結(jié)束)關(guān)鍵字望众,匹配句中的開始和結(jié)束。而不僅僅是輸入的整段的開始和結(jié)尾伞辛。就是 m 標(biāo)記
*?.useUnixLineSeparators,?.useUnicodeWordBoundaries:? 最后兩項(xiàng)優(yōu)化了更多特定的行和字的邊界處理烂翰。Unix 行分隔符。
NSRegularExpression.MatchingOptions? ? ?正則表達(dá)式的匹配選項(xiàng)
一個(gè) NSRegularExpression 正則表達(dá)式實(shí)例中蚤氏,可以傳入選項(xiàng)來調(diào)整匹配的方法甘耿。
*?.anchored:? ?錨定的。僅匹配搜索范圍的開頭第一段竿滨。
*?.withTransparentBounds: 超過界限佳恬。允許正則在搜索范圍前捏境,向前查找。反之毁葱,向后查找垫言。還有單詞的邊界。(盡管不適用于倾剿,實(shí)際的匹配字符)
static var?withTransparentBounds: NSRegularExpression.MatchingOptions
Specifies that matching may examine parts of the string beyond the bounds of the search range, for purposes such as word boundary detection, lookahead, etc. This constant has no effect if the search range contains the entire string. See?enumerateMatches(in:options:range:using:)?for a description of the constant in context.
蘋果 鏈接:? https://developer.apple.com/documentation/foundation/nsregularexpression.matchingoptions
* .withoutAnchoringBounds : 無錨定界限骏掀。 讓 ^ 和 $ 關(guān)鍵字僅匹配字符串的開始和結(jié)尾,而不是搜索范圍的開始和結(jié)束柱告。
*?.reportCompletion ( 報(bào)告完成 ) ,?.reportProgress ( 報(bào)告進(jìn)度 ):? 這些參數(shù)選項(xiàng)僅在下節(jié)講的部分匹配方法中有用。當(dāng)正則查找完成了笑陈,或者是耗時(shí)的匹配上有進(jìn)度际度,相應(yīng)選項(xiàng)會通知?NSRegular?Expression 傳入附加時(shí)間,調(diào)用枚舉塊涵妥。
Partial Matching? ? ? ?部分匹配
最后乖菱,?NSRegular?Expression 最強(qiáng)大的特性之一是,僅掃描字符串中需要的部分蓬网。處理長文本窒所,挺有用的。處理耗資源的正則匹配帆锋,也是吵取。
不要用這兩個(gè)方法?firstMatch(in:...) 和 matches(in:...) , 調(diào)用?enumerateMatches(in:options:range:using:) 锯厢,用閉包處理對應(yīng)的匹配皮官。
func? enumerateMatches( instring :?String, options:NSRegularExpression.MatchingOptions= [], range:NSRange,? usingblock:? (NSTextCheckingResult?,NSRegularExpression.MatchingFlags,UnsafeMutablePointer<ObjCBool>) ->Void)
蘋果鏈接:?https://developer.apple.com/documentation/foundation/nsregularexpression/1409687-enumeratematches
這個(gè)閉包接收三個(gè)參數(shù),匹配的正則結(jié)果实辑,一組標(biāo)志選項(xiàng)捺氢, 一個(gè)布爾指針。 這個(gè) bool 指針是一個(gè)只出參數(shù)剪撬,可以通過它在設(shè)定的時(shí)機(jī)停止處理摄乒。
可以用這個(gè)方法在?Dostoevsky 的?Karamazov?兄弟一書中, 查找開始的幾個(gè)名字。名字遵從的規(guī)則是残黑,首名馍佑,中間父姓 ( 例如: “Ivan Fyodorovitch” )
OC 代碼
NSString * namePattern = @"([A-Z]\\S+)\\s+([A-Z]\\S+(vitch|vna))";
NSRegularExpression * nameRegex = [NSRegularExpression regularExpressionWithPattern: namePatternoptions: kNilOptionserror: &error];
NSString * bookString = ...
NSMutableSet * name = [NSMutableSet set ];
[nameRegex enumerateMatchesInString: bookString options: kNilOptions range: NSMakeRange(0 , [ bookString length ] ) usingBlock: ^( NSTextCheckingResult * result , NSMatchingFlags flags , BOOL * stop ){
????if ( result == nil ) return;
????NSString * name = [nameRegex replacementStringForResult : resultinString : bookStringoffset : 0 template: @"$1 $2" ];
????[names addObject: name ];
????// stop once we've found six unique names
????*stop = ( names.count==6);
} ] ;
Swift 代碼
let nameRegex = try! NSRegularExpression( pattern: "([A-Z]\\S+)\\s+([A-Z]\\S+(vitch|vna))" )
let bookString = ...
var names:Set?= []
nameRegex.enumerateMatches( in: bookString, range: bookString.nsrange ){
????( result , _ , stopPointer )? ? ? in
????guard let result = result else { return }
????let name = nameRegex.replacementString( for: result , in:? bookString , offset : 0 , template: "$1 $2" )
????names.insert(name)
????// stop once we've found six unique names ,通過?Set 確保梨水,6個(gè)不一樣的名字文本?
????stopPointer.pointee = ObjCBool( names.count==6 )
}
// names.sorted():
// ["Adela?da Ivanovna", "Alexey Fyodorovitch", "Dmitri Fyodorovitch",
// "Fyodor Pavlovitch", "Pyotr Alexandrovitch", "Sofya Ivanovna"]
通過這種途徑挤茄,我們只需查找前 45 個(gè)匹配,而不是把全書中接近1300個(gè)名字都找一遍冰木。性能顯著提高穷劈。
一旦有所認(rèn)識笼恰,NSRegularExpression 就會超級有用。除了 NSRegularExpression , 還有一個(gè)類?NSDataDetector?.?NSDataDetector?是一個(gè)用于識別有用信息的類歇终,可以用來處理用戶相關(guān)的文本社证,查找日期,地址與手機(jī)號碼评凝。通過Fundation 框架處理文本追葡,NSRegularExpression 強(qiáng)大,健壯奕短,有簡潔的接口宜肉,也有深入
說明: 為了有意思一些, 我采取了意譯翎碑。并加入了一些 Regex 細(xì)節(jié)與擴(kuò)展資料谬返。
文中 出現(xiàn)的 Swift 代碼, 已校正到 Swift 4 .
PS: 參考資料
文中的 Swift 代碼日杈,github 地址:?https://github.com/dengV/regex_001
熟悉的 ray wenderlich tutorial?: 我升級了對應(yīng)的Swift代碼遣铝,?github 鏈接