tripleCC的技术博客

ʕ•̫͡•ʔ-̫͡-ʕ•͓͡•ʔ-̫͡-ʕ•̫͡•ʔ-̫͡-ʕ•͓͡•ʔ-̫͡-ʔ

Swift2.0中的case匹配

Swift在2.0版本之后,对if、guard、for的匹配进行了一定的加强,其中case匹配模式感觉还是挺新奇的。

参照Swift官方手册,可以知道,这种模式在针对可选值进行处理时,可以获得额外的便利:

1
2
3
4
5
6
7
8
9
10
let someOptional: Int? = 42
// Match using an enumeration case pattern
if case .Some(let x) = someOptional {
    print(x)
}

// Match using an optional pattern
if case let x? = someOptional {
    print(x)
}

x?是.Some(let x)的简写方式。单从以上代码段,可能还看不出有什么特别之处,相反还比以前的实现繁琐:

1
2
3
if let x = someOptional {
    print(x)
}

不过官方手册体现其便利的是for关键字,if还需要另一种场景来体现其带来的便利:

1
2
3
4
5
6
7
8
9
10
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
// Match only non-nil values
for case let number? in arrayOfOptionalInts {
    print("Found a \(number)")
}

// 输出
// Found a 2
// Found a 3
// Found a 5

可以看到,在遍历可选值数组的场景下,这种方式确实减少了一些代码,要是以前,我可能会这样实现:

1
2
3
4
5
6
7
8
9
// 1
for x in arrayOfOptionalInts {
    if let x = x {
        print(x)
    }
}

// 2
arrayOfOptionalInts.flatMap{$0}.map{ print($0) }

Kingfisher、Alarmfire以及Swift开源Foundation的NSSet类中,都使用到了这个特性:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Kingfisher
if let transitionItem = optionsInfo?.kf_firstMatchIgnoringAssociatedValue(.Transition(.None)),
    case .Transition(let transition) = transitionItem where cacheType == .None {

        UIView.transitionWithView(sSelf, duration: 0.0, options: [],
            animations: {
                indicator?.stopAnimating()
            },
            completion: { finished in
                UIView.transitionWithView(sSelf, duration: transition.duration,
                    options: transition.animationOptions,
                    animations: {
                        transition.animations?(sSelf, image)
                    },
                    completion: { finished in
                        transition.completion?(finished)
                        completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
                    }
                )
            }
        )
} else {
    indicator?.stopAnimating()
    sSelf.image = image
    completionHandler?(image: image, error: error, cacheType: cacheType, imageURL: imageURL)
}


// Alarmfire
public enum ValidationResult {
    case Success
    case Failure(NSError)
}
public func validate(validation: Validation) -> Self {
    delegate.queue.addOperationWithBlock {
        if let
            response = self.response where self.delegate.error == nil,
            case let .Failure(error) = validation(self.request, response)
        {
            self.delegate.error = error
        }
    }

    return self
}

// NSSet 
public func isSubsetOfSet(otherSet: Set<NSObject>) -> Bool {
    for case let obj as NSObject in allObjects where !otherSet.contains(obj) {
        return false
    }
    return true
}

按照我的思路编写的话,在老版本中,我会这样实现后面两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Alarmfire
public func wvalidate(validation: Validation) -> Self {
    delegate.queue.addOperationWithBlock {
        if let response = self.response where self.delegate.error == nil {
            switch validation(self.request, response) {
            case let .Failure(error):
                self.delegate.error = error
            default :
                break
            }
        }
    }
    return self
}

// NSSet
public func isSubsetOfSet(otherSet: Set<NSObject>) -> Bool {
    for obj in allObjects where !otherSet.contains(obj) {
        if let obj = obj as? NSObject {
            return false
        }
    }
    return true
}

在第一个实现中,我不得不添加了default分支,即使在这个分支里面不进行任何操作,从而可见case匹配模式可以在这类场景下简化switch语句(如果只需要确认enum中的一个类型,就可以选择性地用if-case替换switch)。
在第二个实现中,因为编译器的原因,我不得不将一般转换as改成可选转换as?,然后增加if语句进行判断。(针对类型转换,if-case可以缩减代码量)

case匹配模式在针对值绑定元组类型转换都带来了一定便利,Match Me if you can: Swift Pattern Matching in Detail.这篇文章中,对这几种情况进行了非常详细的讲解,并列举了一些实际应用的例子,推荐阅读。

Comments