我们在使用标准的JSON或者XML时可以非常容易的进行解析并获取到想要的数据,但是在对一些不那么规则或自定义的字符串进行处理时,就显得比较麻烦了,幸好iOS提供了Scanner类和对正则表达式的支持(后续在写)。Scanner类是一个类簇的抽象父类,该类簇为一个从NSString(虽然Swift中大多使用String,但这个类还保留着NSString)对象扫描值的对象提供了程序接口。
先来查看Scanner的定义:
1 2 3 4 5 6 7 8 | open class Scanner : NSObject, NSCopying { open var string: String { get } // 被扫描的字符串 open var scanLocation: Int // 扫描到了某个位置,默认0,下次扫描会从这里开始,如果超出了string的range,将会NSRangeException open var charactersToBeSkipped: CharacterSet? // 需要忽略掉的字符集,很多资料上说会自动忽略空格和换行符,但实测并不会自动忽略,所以现在大部分的博客可能都不太正确,"\n\t"这两个东西还是需要手动设置的。 open var caseSensitive: Bool // default is false, 默认不区分大小写。另外这个设置不适用于charactersToBeSkipped,例如要忽略字符所有的元音字幕,需要设置忽略"AEIOUaeiou" open var locale: Any? // 多在国际化多语言的时候使用,例如Locale(identifier: "zh_CN"), 主要对数字,日期时间等产生影响 public init(string: String) // 通过需要被扫描的字符串初始化 } |
一些个扫描的方法
这些方法都是扫描到正确的内容后返回true并设置scanLocation,否则返回false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 扫描整数 // 扫描Int32并存入相应指针, 所有结果都有效,上溢出为Int32.max, 下溢出为Int32.min open func scanInt32(_ result: UnsafeMutablePointer<Int32>?) -> Bool // 扫描Int并存入相应指针, 所有结果都有效,这里比较特殊,Swift中32位系统下Int == Int32, 64位系统下Int == Int64 open func scanInt(_ result: UnsafeMutablePointer<Int>?) -> Bool // 扫描Int64并存入相应指针,同32 open func scanInt64(_ result: UnsafeMutablePointer<Int64>?) -> Bool // 扫描UnsignedLongLong并存入相应指针 UInt64,Swift中UnsignedLongLong == UInt64 open func scanUnsignedLongLong(_ result: UnsafeMutablePointer<UInt64>?) -> Bool // 扫描浮点数, 上溢出HUGE,下溢出-HUGE,都是有效的,不会crash open func scanFloat(_ result: UnsafeMutablePointer<Float>?) -> Bool open func scanDouble(_ result: UnsafeMutablePointer<Double>?) -> Bool // 扫描十六进制数字到 整数,都可以不带"0x" 或者 "0X"前缀,以%a或者%A格式化 open func scanHexInt32(_ result: UnsafeMutablePointer<UInt32>?) -> Bool open func scanHexInt64(_ result: UnsafeMutablePointer<UInt64>?) -> Bool // 扫描十六进制数字到 浮点数,必须带上带"0x" 或者 "0X"前缀,以%a或者%A格式化 open func scanHexFloat(_ result: UnsafeMutablePointer<Float>?) -> Bool open func scanHexDouble(_ result: UnsafeMutablePointer<Double>?) -> Bool // 扫描匹配的字符串,找到第一个后会停止,并设置scanLocation open func scanString(_ string: String, into result: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool // 扫描匹配的字符集,找到第一个后会停止,并设置scanLocation open func scanCharacters(from set: CharacterSet, into result: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool // 扫描到匹配的字符串后结束,并设置scanLocation open func scanUpTo(_ string: String, into result: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool // 扫描到匹配的字符集后结束,并设置scanLocation open func scanUpToCharacters(from set: CharacterSet, into result: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool // 这个在扫描货币或者其他精度要求特别高的数字时使用,Decimal相关的使用后续再写 open func scanDecimal(_ dcm: UnsafeMutablePointer<Decimal>?) -> Bool |
说了这么多,来一些实战使用
示例1:找出一段字符中的所有数字,并用数组存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var str = "Hel2lo,playgr3ound9527 6674" let scanner = Scanner(string: str) // 创建Scanner scanner.caseSensitive = true // 区分大小写 var intResult = 0 scanner.scanInt(&intResult) // 这里由于第一个并不是数字,所有扫描不到结果 var scanLocation = 0 var resultArray: [String] = [String]() while scanner.isAtEnd != true { var stringResult: NSString? = nil if scanner.scanCharacters(from: CharacterSet.decimalDigits, into: &stringResult) { print("result", scanner.scanLocation) resultArray.append((stringResult! as String)) } else { print("no result", scanner.scanLocation) scanner.scanLocation += 1 // 这里是如果没有扫描到,就把location后移一位,继续扫描 } } print(resultArray) |
示例2:从一个非常规格式字符串中匹配出需要的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | let productAndCostStr = "\t Product: Acme Potato Peeler; Cost: 0.98 73\n\t Product: Chef Pierre Pasta Fork; Cost: 0.75 19\n\t Product: Chef Pierre Colander; Cost: 1.27 2" let PRODUCT = "Product" let COST = "Cost" let semicolon = "; " class ProductModel { public init() { } open var productName: NSString? open var productCost: NSString? open var floatValue: Double = 0.0 open var intValue: Int = 0 func description() -> String { return "productName = \(productName ?? "nil")\nproductCost = \(productCost ?? "nil")\nfloatValue = \(floatValue)\nintValue = \(intValue)" } } let tempScanner = Scanner(string: productAndCostStr) let model = ProductModel() let semicolonSet = CharacterSet(charactersIn: ";") tempScanner.charactersToBeSkipped = CharacterSet(charactersIn: " :\n\t") while tempScanner.isAtEnd == false { if tempScanner.scanString(PRODUCT, into: nil) && tempScanner.scanUpToCharacters(from: semicolonSet, into: &model.productName) && tempScanner.scanString(";", into: nil) && tempScanner.scanString(COST, into: &model.productCost) && tempScanner.scanDouble(&model.floatValue) && tempScanner.scanInt(&model.intValue){ print(model.description()) } } |
很多时候由于正则表达式在使用上的方便性,我们忽略了Scanner,但Scanner提供了另外一种很好很直观且安全的判断和取值方式,两个结合才能相得益彰。