在Swift中,我们可以使用range operators(...
和..<
)来循环范围:
for i in 0...4 { ... }
假设我有很多颜色:
let red: MyColor
let orange: MyColor
let yellow: MyColor
let green: MyColor
let blue: MyColor
let indigo: MyColor
let violet: MyColor
let black: MyColor
let brown: MyColor
我如何支持范围运算符,以便我可以按彩虹顺序循环颜色?
for rainbowColor in red...violet { ... }
答案 0 :(得分:2)
(请注意,这是Swift 3.如果你想要Swift 2.2,请参阅Senseful的答案,这与旧协议基本相同。)
您希望Color
Strideable
Stride
Int
。然后您可以免费获得...
和..<
。我们这样做。
首先,我要假设你的类型是这样的。这只是一个例子。只要您可以实现Strideable
,就可以完成所有种的事情。你似乎想要结构而不是枚举(这很好),所以让我们做结构。这意味着任何人都可以添加其他颜色,您必须在设计中考虑这些颜色。颜色需要一些东西来区分它们,所以我们给它们一个名字。如果他们是班级,那么这也可以让他们被区分。
struct Color {
let name: String
static let red = Color(name: "red")
static let orange = Color(name: "orange")
static let yellow = Color(name: "yellow")
static let green = Color(name: "green")
static let blue = Color(name: "blue")
static let indigo = Color(name: "indigo")
static let violet = Color(name: "violet")
}
Strideable的第一条规则是Equatable。
extension Color: Equatable {
static func == (lhs: Color, rhs: Color) -> Bool {
return lhs.name == rhs.name
}
}
现在它可能等同,我们应该想出“秩序”的意思。这是一种方式。由于结构可能会产生意想不到的值,我已经说过这些都是平等的并且在我的彩虹颜色之前排序。如果这是一个枚举,我们就不必担心这种情况。
extension Color {
private static let _order: [Color] = [red, orange, yellow, green, blue, indigo, violet]
private var _indexValue: Int {
return Color._order.index(of: self) ?? Int.min
}
}
因此,我们可以回答问题#2:可比较:
extension Color: Comparable {
static func < (lhs: Color, rhs: Color) -> Bool {
return lhs._indexValue < rhs._indexValue
}
}
再向Strideable迈出一小步:
extension Color: Strideable {
func advanced(by n: Int) -> Color {
return Color._order[self._indexValue + n]
}
func distance(to other: Color) -> Int {
return other._indexValue - _indexValue
}
}
并且ta da,你的...
:
for rainbowColor: Color in .red ... .violet { print(rainbowColor) }
现在我碰巧很喜欢枚举,如果你试图用enum实现它,它们碰巧会有一些技巧,所以值得注意。例如,假设你有:
enum Color {
case red
case orange
case yellow
case green
case blue
case indigo
case violet
}
事实证明,您无法使用index(of:)
来计算_indexValue
。你进入一个无限循环,因为它在默认的distance(to:)
实现中调用==
(我实际上并不确定为什么会发生这种情况)。所以你必须以这种方式实现_indexValue
:
private var _indexValue: Int {
switch self {
case .red: return 0
case .orange: return 1
case .yellow: return 2
case .green: return 3
case .blue: return 4
case .indigo: return 5
case .violet: return 6
}
}
咦;看起来很像原始价值。是啊。整个方法基本上是从侧面附加原始值而无需修改类型本身。所以这就是它看起来非常相似的原因。其余部分与结构相同,并且该方法可以应用于您可以弄清楚如何跨越的任何其他方法。
答案 1 :(得分:2)
(注意:这适用于Swift 2.2。如果您需要Swift 3版本,请参阅Rob's answer。)
Swift已经定义了以下功能:
/// Forms a closed range that contains both `start` and `end`.
/// - Requires: `start <= end`.
@warn_unused_result
public func ...<Pos : ForwardIndexType where Pos : Comparable>(start: Pos, end: Pos) -> Range<Pos>
/// Forms a closed range that contains both `minimum` and `maximum`.
@warn_unused_result
public func ...<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos>
/// Returns a closed interval from `start` through `end`.
@warn_unused_result
public func ...<Bound : Comparable>(start: Bound, end: Bound) -> ClosedInterval<Bound>
这意味着我们真正需要做的就是遵守ForwardIndexType
。正如Rob所说,它不会使用Comparable
协议,因为它返回一个Interval,我们需要一个for/in
循环的范围。
假设这是我们的共享代码:
import UIKit
struct MyColor {
let title: String
init(title: String) {
self.title = title
}
}
let red = MyColor(title: "Red")
let orange = MyColor(title: "Orange")
let yellow = MyColor(title: "Yellow")
let green = MyColor(title: "Green")
let blue = MyColor(title: "Blue")
let indigo = MyColor(title: "Indigo")
let violet = MyColor(title: "Violet")
let white = MyColor(title: "White")
let lightGray = MyColor(title: "Light Gray")
let darkGray = MyColor(title: "Dark Gray")
let black = MyColor(title: "Black")
// TODO: insert protocol conformance here!
print("Rainbow colors:")
for color in red...violet {
print(color.title)
}
print("")
print("Grayscale colors:")
for color in white...black {
print(color.title)
}
输出为:
Rainbow colors:
Red
Orange
Yellow
Green
Blue
Indigo
Violet
Grayscale colors:
White
Light Gray
Dark Gray
Black
我们可以通过符合...
协议使ForwardIndexType
工作:
let nullColor = MyColor(title: "Null")
extension MyColor: Equatable {}
@warn_unused_result
func ==(lhs: MyColor, rhs: MyColor) -> Bool {
return lhs.title == rhs.title
}
extension MyColor: ForwardIndexType {
@warn_unused_result
func successor() -> MyColor {
switch self {
case red: return orange
case orange: return yellow
case yellow: return green
case green: return blue
case blue: return indigo
case indigo: return violet
case violet: return nullColor
case white: return lightGray
case lightGray: return darkGray
case darkGray: return black
case black: return nullColor
case nullColor: fatalError()
default: fatalError()
}
}
}
注意:即使您只从nullColor
循环,也需要red...violet
(或某些终结符)。即使范围明确地在violet
结束,系统似乎也会实际执行violet
案例。
另请注意,这仅适用于struct,enums或final类。如果您尝试将上述结构更改为类,则会收到以下错误:
execution failed: protocol 'ForwardIndexType' requirement '_failEarlyRangeCheck(_:bounds:)' cannot be satisfied by a non-final class ('MyColor') because it uses 'Self' in a non-parameter, non-result type positionmethod 'advancedBy' in non-final class 'MyColor' must return `Self` to conform to protocol 'ForwardIndexType'method 'advancedBy(_:limit:)' in non-final class 'MyColor' must return `Self` to conform to protocol 'ForwardIndexType'
error: method 'successor()' in non-final class 'MyColor' must return `Self` to conform to protocol '_Incrementable'
func successor() -> MyColor {
^
有关为何无法运作的详情,请参阅Rob's answer here。
理论上你也应该通过实现 Comparable
协议来实现它,但是当我尝试时,我得到了:
error: binary operator '...' cannot be applied to two 'MyColor' operands
for color in red...violet {
~~~^ ~~~~~~
note: overloads for '...' exist with these partially matching parameter lists: (Bound, Bound), (Pos, Pos)
for color in red...violet {
^
答案 2 :(得分:1)
所以......你可以对任何类型进行for
- in
循环。您所要做的就是实现符合SequenceType
的内容。然后,一旦您实现了该类或结构,就像创建一个返回...
的{{1}}运算符一样简单。
假设如下:
SequenceType
我们可以创建一个struct RainbowColor {
let name: String
}
let red: RainbowColor = RainbowColor(name: "red")
let orange: RainbowColor = RainbowColor(name: "orange")
let yellow: RainbowColor = RainbowColor(name: "yellow")
let green: RainbowColor = RainbowColor(name: "green")
let blue: RainbowColor = RainbowColor(name: "blue")
let indigo: RainbowColor = RainbowColor(name: "indigo")
let violet: RainbowColor = RainbowColor(name: "violet")
:
RainbowColorSequence
(注意,从中省略了返回颜色的顺序以及序列结束时的实际逻辑...因为它很混乱。请阅读底部以获取此实现可能看起来像的示例但是。)
获得此struct RainbowColorSequence: SequenceType {
let startColor: RainbowColor
let endColor: RainbowColor
init(start: RainbowColor, end: RainbowColor) {
startColor = start
endColor = end
}
func generate() -> AnyGenerator<RainbowColor> {
return AnyGenerator<RainbowColor> {
// TODO: Implement your generator logic.
return nil
}
}
}
后,RainbowColorSequence
功能非常简单:
...
现在,您可以编写此代码:
func ...(lhs: RainbowColor, rhs: RainbowColor) -> RainbowColorSequence {
return RainbowColorSequence(start: lhs, end: rhs)
}
有关生成器逻辑可能的示例,请查看this question。我可以很容易地将for color in red...violet {
print(color.name)
}
运算符添加到该链接后面的代码中,因为其余逻辑已经存在,...
实际上只是...
初始化的轻量级包装器。 }。
重要的是,您将不得不自己编写序列的逻辑。鉴于你的条件,编译器没有简单的方法来推断序列中的内容或序列的顺序。这个逻辑可能会变得非常混乱,具体取决于你想要迭代的类型。