Добавьте поддержку "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
.
-
В соответствии с Sequence
, IteratorProtocol
одновременно, просто реализуя метод next()
. Этот подход является самым простым и есть пример в заголовках. Смотрите Sequence.swift. Реализация обоих протоколов одновременно может быть нереалистичной или не соответствовать вашим потребностям. (Это может предотвратить одновременное повторение двух разных экземпляров и т.д.)
-
Соответствует 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
}
}
}