Добавьте поддержку "for in" для итерации по пользовательским классам Swift

Как известно, мы можем использовать цикл for..in для итерации по Arrays или Dictionaries. Тем не менее, я хотел бы перебрать мой собственный CustomClass следующим образом:

for i in CustomClass {
    someFunction(i)
}

Какие операции/протоколы CustomClass должны поддерживать, чтобы это было возможно?

Ответ 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

Ответ 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. Однако этот код должен быть стабильным в будущих версиях.

P.S. С помощью дженериков вы можете легко сделать свою собственную реплику Array, следуя этому шаблону и только реализуя методы, которые вы хотите.

Ответ 3

@Матт Гибсон прав. Однако я хотел бы добавить дополнительную информацию для дальнейшего использования.

Из Advanced 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
}

Ответ 4

Это будет протокол SequenceType и связанный с ним протокол Generator.

Протокол SequenceType говорит, что класс должен реализовать generate(), который возвращает что-то, что соответствует протоколу Generator, который является битом, который выполняет фактическую работу; протокол Generator - это протокол со всеми важными next() методами.

Вот пример реализации этого, чтобы разрешить for..in в видео WWDC 2014 "Advanced Swift" (в примере generics "A Simple Generic Stack", начиная со слайда 183.)

Основная информация о том, какой протокол реализовать для for..in находится в разделе Statements документации, в котором дается краткий обзор SequenceType и Generator

Ответ 5

ПРИМЕЧАНИЕ AnyGenerator и SequenceType - это старые типы, которых нет в последних версиях. Вам нужно реализовать протокол Sequence сейчас.

Существует два способа реализации Sequence.

  1. В соответствии с Sequence, IteratorProtocol одновременно, просто реализуя метод next(). Этот подход является самым простым и есть пример в заголовках. Смотрите Sequence.swift. Реализация обоих протоколов одновременно может быть нереалистичной или не соответствовать вашим потребностям. (Это может предотвратить одновременное повторение двух разных экземпляров и т.д.)

  2. Соответствует Sequence и возвращает объект, который реализует IteratorProtocol. Я думаю, что это наиболее распространенный случай в классах реального мира (когда все становится немного сложнее, а не Hello Worlds). Есть также пример в Sequence.swift

Ниже приведен пример подхода 2. Пользовательский класс (Linked List), который можно повторять:

/// 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() -> GeneratorOf<Car>, теперь существует абстрактный базовый класс, который обрабатывает реализацию этой функции для вас под названием AnyGenerator<T> (см. документы Apple), поскольку GeneratorOf<T> больше не существует.

Это означает, что вы можете просто подклассифицировать этот абстрактный базовый класс и, на самом деле, наследовать все функциональные возможности вышеупомянутого соответствия протокола:

class Cars: AnyGenerator<Car> {

     private var carList = [Car]()
     private var currentIndex:Int

     ...

}

Затем нужно только переопределить метод next(), объявленный протоколом GeneratorType (который также соответствует AnyGenerator<T>), чтобы определить желаемое поведение итерации:

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
         }
     }

}