遍历 [T?]

在遍历一个 Array 的时候,我们可能会用 for .. in map flatMap 或者是 forEach 等等,这里谈一谈如何更好的用 flatMap 处理 [T?] 。

可能最先想到的方法就是配合 if let 。像这样:

1
2
3
4
5
6
7
var array: [Int?] = [1, 2, 3, 4, nil, 6]

for value in array { // 这里的 value 是 Int? 类型
if let value = value {
print(value) // 这里的 value 是 Int 类型
}
}

这没什么不好的,但我们的需求是遍历所有不为 nil 的值。从代码中直接去读的话,需要读两行。试试 where

1
2
3
for value in array where value != nil { // 这里的 value 是 Int? 类型
print(value!) // 这里的 value 仍然是 Int? 类型
}

当然也可能去用 filter

1
2
3
for value in array.filter({ $0 != nil }) {
print(value!) // 这里的 value 是 Int? 类型
}

这样的代码就比较尴尬了。目前来看用 where 并不能获得正确的类型推断。

事实上是因为我们这里的 where 操作并没有进行解包

试试 withoutNil()

1
2
3
for value in array.withoutNil() { // 这里的 value 是 Int 类型
print(value) // 这里的 value 是 Int 类型
}

当然还有 filterNil()

具体实现的代码参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public protocol OptionalType {
associatedtype Wrapped
var value: Wrapped? { get }
}

extension Optional: OptionalType {
/// Cast `Optional<Wrapped>` to `Wrapped?`
public var value: Wrapped? {
return self
}
}

extension SequenceType where Generator.Element: OptionalType {

@warn_unused_result
public func filterNil() -> [Generator.Element.Wrapped] {
return self.flatMap { $0.value }
}

@warn_unused_result
public func withoutNil() -> [Generator.Element.Wrapped] {
return self.flatMap { $0.value }
}
}

其实可以看到完全是一个实现效果,只是不同的方法名有不同的适用场景,刚刚的 for in 就适合 withoutNil() 。在链式转换我们的数据时,推荐 filterNil()

遍历我们想要的数据?

比如我们需要将 view 子视图中设置所有的 UIButtonbackgroundColor

同样可以使用之前的几种方案 if let where filter

参考上面的 filterNil

1
2
3
for button in view.subviews.flatMap(type: UIButton.self) {
print(button) // yooo 直接就是 UIButton 了
}

实现过程也很简单:

1
2
3
4
5
6
7
8
9
10
extension SequenceType {
/// flatMap 出我们需要的类型
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<T>(type type: T.Type) -> [T] {
return flatMap { $0 as? T }
}
}

事实上 flatMap 还有更多更好玩的地方,我们完全可以用 flatMapmap 自由的变换出需要的数据。上面的两个情况都只是非常常见的场景,我们可以单独写一个方法。

再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Phone {
var number: String?
var originalLabel: String?

init(number: String? = nil, originalLabel: String? = nil) {
self.number = number
self.originalLabel = originalLabel
}
}

struct User {
var name: String?
var phones: [Phone]?

init(name: String? = nil, phones: [Phone]? = nil) {
self.name = name
self.phones = phones
}
}

我们需要从 [User] 中拿出符合以下条件的结果:

  • name 不是 nil ,也就是有名字的 User
  • phonenumber 不是 nil ,也就是要有手机号

拿出以上结果后,最终返回一个 ["name": "", "number": ""] 。也就是说最终的 array 是根据 number 数量决定的。

不要问我为什么会有这样奇葩的需求,iPhone 上的通讯录就是这个样子。其实这部分是我从 Yep 中抽取出来的一个场景。PS:Yep 是个很好的学习项目,可以从中学到很多有意思的东西。

这部分笔者不打算再去解释了,留给读者做个思考:

1
2
3
4
5
6
7
8
9
10
typealias UploadContact = [String: String]

users
.flatMap { user -> (name: String, numbers: [String])? in
guard let name = user.name, phones = user.phones else { return nil }
return (name: name, numbers: phones.flatMap { $0.number })
}
.flatMap { contact -> [UploadContact] in
return contact.numbers.map { ["name": contact.name, "number": $0] }
}

以上完整代码见 Gist

做个总结?

先来看一下 filter 的声明:

1
2
3
4
5
extension SequenceType {

public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]

}

通俗的说就是 [T] -> [T] 的过程,如果是 T? ,那就是 [T?] -> [T?] 。可以看到 filter 虽然是起到过滤筛选的作用,但事实上,这绝不是过滤 nil 的好方案,因为它并没有帮我们进行解包。那么 filter 的最佳使用场景是什么呢?我认为它和 where 有些相似,都是通过返回一个 Bool 去寻找我们需要的数据。类似这样:

1
[60, 70, 59].filter { $0 > 60 }
  • 过滤 nil 选择 flatMap
  • 过滤条件选择 filter