【问题标题】:Add "for in" support to iterate over Swift custom classes添加“for in”支持以迭代 Swift 自定义类
【发布时间】:2014-06-07 16:23:45
【问题描述】:

我们知道,我们可以使用for..in 循环遍历ArraysDictionaries。但是,我想像这样迭代我自己的CustomClass

for i in CustomClass {
    someFunction(i)
}

CustomClass 必须支持哪些操作/协议才能实现这一点?

【问题讨论】:

    标签: swift


    【解决方案1】:

    假设您有一个“汽车”类,您希望能够使用 for..in 循环对其进行迭代:

    let cars = Cars()
    
    for car in cars {
        println(car.name)
    }
    

    最简单的方法是将 AnyGenerator 与这样的类一起使用:

    class Car {
        var name : String
        init(name : String) {
            self.name = name
        }
    }
    
    class Cars : SequenceType {
    
        var carList : [Car] = []
    
        func generate() -> AnyGenerator<Car> {
            // keep the index of the next car in the iteration
            var nextIndex = carList.count-1
    
            // Construct a AnyGenerator<Car> instance, passing a closure that returns the next car in the iteration
            return anyGenerator {
                if (nextIndex < 0) {
                    return nil
                }
                return self.carList[nextIndex--]
            }
        }
    }
    

    要尝试一个完整的工作示例,请添加上面的两个类,然后尝试像这样使用它们,添加几个测试项:

        let cars = Cars()
    
        cars.carList.append(Car(name: "Honda"))
        cars.carList.append(Car(name: "Toyota"))
    
        for car in cars {
            println(car.name)
        }
    


    就是这样,简单。

    更多信息:http://lillylabs.no/2014/09/30/make-iterable-swift-collection-type-sequencetype

    【讨论】:

    • 现在我们在 Swift 2.0 中有协议扩展,这有点不同。与其遵循 SequenceType,不如简单地继承 AnyGenerator 并重写 next() 方法以在迭代中返回正确的项目(在这种情况下,next() –> Car?)
    • 更新到 Swift 2 语法。这有点为时过早,因为这仍然是测试版,但答案将是正确的更长。如果您需要 Swift 1.2,请查看历史记录。
    • @metatheoretic,我不相信有任何理由继承AnyGenerator。你从哪里得到的? SequenceType 仍然是 for...in 的协议。
    • 这是一个在正向而不是反向迭代的替代镜头(也使用保护等):gist.github.com/cellularmitosis/64d4a2d2d6dfa33916fc
    【解决方案2】:

    以上所有答案都可能有点棘手。如果你的类中有一个数组,你想对其进行迭代(就像在@Lee Whitney 的回答中一样),那么有一种更简单的方法来实现它。你有以下类,CustomClass:

    class CustomClass: SequenceType {
        let array: [String]
    
        init(array: [String]) {
            self.array = array
        }
    
        func generate() -> IndexingGenerator<[String]> {
            return array.generate()
        }
    }
    

    就这么简单。经测试可在最新的 Xcode 版本(撰写本文时为 6.1)和 iOS 8.1.2 中工作。不过,这段代码在未来的版本中应该是稳定的。

    附:使用泛型,您可以按照这种模式轻松地制作自己的 Array 副本,并且只实现您想要的方法。

    【讨论】:

    • 我正要发布这个。如果您的目的是让您的类在符合 Generator 的字段上可迭代,则可以直接公开该字段的 Generator。
    【解决方案3】:

    @Matt Gibson 是正确的。不过,我想添加更多信息以供将来参考。

    来自高级 Swift:

    这段代码:

    for x in someSequence {
        ...
    }
    

    转换成这个:

    var __g = someSequence.generate()
    while let x = __g.next() {
        ...
    }
    

    因此,必须采用序列,它给出了generate()next() 类。以下是这些协议:

    protocol Generator {
        typealias Element
        mutating func next() -> Element?
    }
    protocol Sequence {
        typealias GeneratorType : Generator
        func generate() -> GeneratorType
    }
    

    【讨论】:

    • For in 语句循环遍历GeneratorType,它可以选择从SequenceType 检索它。所以从技术上讲,您需要为它提供GeneratorType SequenceType
    【解决方案4】:

    这将是SequenceType 协议及其相关的Generator 协议。

    SequenceType 协议表示该类必须实现generate(),它返回符合Generator 协议的内容,这是实际工作的位; Generator 协议是具有最重要的next() 方法的协议。

    WWDC 2014 video“Advanced Swift”中有一个实现它以允许for..in 的示例(在泛型示例“A Simple Generic Stack”中,从幻灯片 183 开始。)

    关于为for..in 实现哪个协议的基本信息在文档的Statements 部分,其中简要概述了SequenceTypeGenerator

    【讨论】:

    • 请注意,Sequence 在 Swift 1.0 中已重命名为 SequenceType
    【解决方案5】:

    注意 AnyGeneratorSequenceType 是新版本中不存在的旧类型。你现在需要实现Sequence 协议。

    有两种方法可以实现Sequence

    1. 只需实现next()方法即可同时符合SequenceIteratorProtocol。这种方法是最简单的,并且在标题中有一个示例。见Sequence.swift。同时实现这两种协议可能是不现实的或无法满足您的需求。 (它可能会阻止同时迭代两个不同的实例等)

    2. 符合Sequence并返回一个实现IteratorProtocol的对象。我认为这是现实世界课程中最常见的情况(当事情变得有点复杂时,不是 Hello Worlds)。 Sequence.swift中也有例子

    下面是方法 2 的示例。一个可迭代的自定义类(链表):

    /// Linked List Node:
    public class LinkedListNode <T> {
    
        public internal(set) var value: T
    
        public internal(set) var next: LinkedListNode<T>?
    
        internal init(_ value: T) {
            self.value = value
            self.next = nil
        }
    }
    
    /// Linked List with append method only.
    public class LinkedList<T> {
    
        public internal(set) var first: LinkedListNode<T>? = nil
    
        public internal(set) var last: LinkedListNode<T>? = nil
    
        /// Appends a new node.
        public func append(_ value: T) {
            if first == nil {
                first = LinkedListNode(value)
                last = first
            } else {
                last.next = LinkedListNode(value)
                last = last.next
            }
        }
    }
    

    最后是序列实现

    /// Sequence protocol adoption. It allows `for ... in` and a bunch of other methods too.
    extension LinkedList: Sequence {
    
        /// Iterator implementation
        public class Iterator<T>: IteratorProtocol {
    
            /// Maintain a ref to current element so next element can be reached
            var cur: LinkedListNode<T>?
    
            /// IteratorProtocol protocol requirement
            public func next() -> T? {
                let res = cur?.value
                cur = cur?.next
                return res
            }
        }
    
        /// Sequence protocol requirement
        public func makeIterator() -> Iterator<T> {
            let g = LinkedList.Iterator()
            g.cur = first
            return g
        }
    }
    

    用法:

    let linkedList = LinkedList<Int>()
    linkedList.append(3)
    linkedList.append(6)
    linkedList.append(9)
    linkedList.append(12)
    for element in linkedList {
        print(element)
    }
    
    let odds = linkedList.filter { return $0 % 2 == 0 }
    print(odds)
    

    【讨论】:

      【解决方案6】:

      接受的答案是正确的,直到最近才被接受的方法来解决这个问题。但是,鉴于在 Swift 2.0 中引入了协议扩展,而不是遵循 SequenceType 并实现 func generate() -&gt; GeneratorOf&lt;Car&gt;,现在有一个抽象基类为您处理此功能的实现,称为 AnyGenerator&lt;T&gt; (see Apple docs),因为GeneratorOf&lt;T&gt; 不再存在。

      这意味着您可以简单地继承这个抽象基类,并通过这样做继承上述协议一致性的所有功能:

      class Cars: AnyGenerator<Car> {
      
           private var carList = [Car]()
           private var currentIndex:Int
      
           ...
      
      }
      

      然后只需要覆盖GeneratorType 协议(AnyGenerator&lt;T&gt; 也符合)声明的next() 方法即可定义所需的迭代行为:

      class Cars: AnyGenerator<Car> {
      
           private var carList = [Car]()
           private var currentIndex:Int
      
           override func next() -> Car? {
      
               if (currentIndex < self.carList.count) {
                  currentIndex++
                  return self.carList[currentIndex-1]
               } else {
                  currentIndex = 0;
                  return nil
               }
           }
      
      }
      

      【讨论】:

      • 您说:“现在有一个抽象基类为您处理此功能的实现,称为AnyGenerator”。我没有看到 AnyGenerator 为您做任何事情。我不明白他们为什么做出改变,但没有提供任何指导或解释。
      • 我不相信以上是真的。没有必要为此继承AnyGenerator。查看对已接受答案的编辑。他们只是将 GeneratorOf 重命名为 AnyGenerator,并将 GeneratorOf(next:) 移至新函数 anyGenerator()
      • 这绝对是错误的。 AnyGenerator 是 Generator 的类型擦除版本,用于非泛型用例。它不是一个基类,这里的例子是你永远不想做的。
      猜你喜欢
      • 2013-10-15
      • 1970-01-01
      • 1970-01-01
      • 2021-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多