转载自泊学(boxueio.com)

在日常的开发中,我们经常需要遍历一个enum类型的所有case。如果你Google一下解决办法,就会发现大多都是依赖enum数据二进制结构的“黑科技”。但是,在Swift 4.2之后的版本里,故事就不这样了,SE-0194为Swift引入了原生的case遍历支持,也就是说,编译器可以自动为我们生成一个包含所有case的集合了。

而唯一的条件,就是enum自身遵从protocol CaseIterable:

enum Shape: CaseIterable { case rectangle case circle case triangle
} Shape.allCases // [rectangle, circle, triangle] 

然后,我们再来看下CaseIterable的定义:

public protocol CaseIterable { associatedtype AllCases: Collection where AllCases.Element == Self static var allCases: AllCases { get }
}

可以看到,只是给遵从它的类型添加了一个静态属性:allCases,它是一个Collection,这个集合中的每一个元素和遵从CaseIterable的类型相同。在我们的例子中,当然就是Shape。

CaseIterable的限制

当然,CaseIterable的使用也是有限制的,例如:编译器不会为带有associated value的enum合成allCases属性。原因很简单,因为理论上说,带有associated value的enum是拥有无穷多个case的,遍历所有的case当然就无从谈起了。例如,我们把Shape定义成这样,编译器就无法合成allCases了:

enum Shape: CaseIterable { case rectangle case circle(Double) case triangle
}

自定义CaseIterable

当然无法自动合成allCases并不等于我们无法自己实现某种合成的过程,例如,对于上面这种情况,我们可以这样:

extension Shape: CaseIterable { public typealias AllCases = [Shape] public static var allCases: AllCases { return [Shape.rectangle, Shape.circle(1.0), Shape.triangle]
    }
}

在这个实现里,我们硬编码了.circle的associated value。它没什么实际的应用价值,只是为了展示自定义CaseIterable的方法。基于这种思路,有一种自定义CaseIterable的方法是有用的。

我们知道,optional类型也是通过enum实现的,但是由于它的case是带有associated value的,因此,Swift编译器无法自动为optional类型合成allCases。也就是说:Shape?.allCases是无法通过编译的的。不过没关系,我们也可以自己来:

extension Optional: CaseIterable where Wrapped: CaseIterable { public typealias AllCases = [Wrapped?] public static var allCases: AllCases { return Wrapped.allCases.map { $0 } + [nil]
    }
}

在这个实现里,我们先用了Swift 4.1中的conditional conform,约束了只有optional包装的类型自身遵从CaseIterable这种情况。在这种情况下,我们先用Wrapped.allCases.map { $0 }得到非nil值的数组,然后,再把nil硬编码到数组结尾中就好了。

有了这个扩展之后,Shape?.allCases就可以通过编译了,它的结果应该是:[.rectange, .circle, .triangle, nil](这里,假设.circle是没有associated value的)。

返回
顶部