Подтвердить что ты не робот

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

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

for i in CustomClass {
    someFunction(i)
}

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

4b9b3361

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

}