iOS 文本操作笔记——Scanner

我们在使用标准的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提供了另外一种很好很直观且安全的判断和取值方式,两个结合才能相得益彰。

以上测试内容的playground下载

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注