If there is such a requirement, I would like to be able to loop through all the attributes in a class or structure like an array. Like this:

let persion = Persion(a)for i in persion {
    print(i)
}
Copy the code

To implement this requirement, we need to make our custom types comply with the Sequence protocol.

The sequence

The Sequence protocol is fundamental in the structure of collection types. A sequence is a series of values of the same type that you can iterate over. Sequence protocol provides many powerful functions, which can be directly used by all types of the protocol. The ability to step through elements like the one above may seem simple, but it is the foundation of the power that Sequence provides.

Satisfying the Sequence protocol is straightforward. All you need to do is provide a makeIterator() method that returns an iterator:

public protocol Sequence {
    associatedtype Iterator : IteratorProtocol
    
    public func makeIterator(a) -> Self.Iterator
    
    // ...
}
Copy the code

The Sequence protocol has an associated type Iterator, and it must comply with the IteratorProtocol. From this we can see that Sequence is a type that can create iterator protocols. So before we can figure out its ability to step over elements, it’s worth knowing what an iterator is.

The iterator

Sequences provide access to elements by creating an iterator. Iterators produce the values of a sequence one at a time and manage the traversal state as the sequence is traversed. The only method in IteratorProtocol is next(), which needs to return the next value in the sequence each time it is called. Next () should return nil when the sequence is exhausted, otherwise the iterator will keep working until the resource is exhausted.

The definition of the IteratorProtocol is very simple:

public protocol IteratorProtocol {
    associatedtype Element
    
    public mutating func next(a) -> Self.Element?
}
Copy the code

The association type Element specifies the type of value produced by the iterator. Here next() is marked mutating, indicating that iterators can have mutable state. Mutating is not necessary here, and if the value returned by your iterator does not change the iterator itself, then there is no problem with no mutating. But almost all meaningful iterators require mutable state so that they can manage their current position in the sequence.

With a basic understanding of Sequence and IteratorProtocol, it’s easy to implement the requirements mentioned at the beginning. For example, if I wanted to iterate over all the attributes of a Person instance, we could do this:

struct Persion: Sequence {
    var name: String
    var age: Int
    var email: String
    
    func makeIterator(a) -> MyIterator {
        return MyIterator(obj: self)}}Copy the code

Persion complies with the Sequence protocol and returns a custom iterator. The implementation of the iterator is also simple:

struct MyIterator: IteratorProtocol {
    var children: Mirror.Children
    
    init(obj: Persion) {
        children = Mirror(reflecting: obj).children
    }
   
    mutating func next(a) -> String? {
        guard let child = children.popFirst() else { return nil }
        return "\(child.label.wrapped) is \(child.value)"}}Copy the code

Children in the iterator is the collection type AnyCollection< mirror. Child>. After each iteration returns a value, update the children state so that our iterator can continuously output the correct value. Until all values in children are printed.

Now we can use the for loop to print all property values in Persion:

for item in Persion.author {
    print(item)
}

// out put:
// name is jewelz
// age is 23
// email is [email protected]
Copy the code

Now what if there’s another structure or class that needs to iterate out as well so what about properties? This is easy to do, let our structure obey the Sequence protocol and return a custom iterator. Copying code this way does work, but if we use protocol extensions we can write more maintainable code like this:

struct _Iterator: IteratorProtocol {
    var children: Mirror.Children
    
    init(obj: Any) {
        children = Mirror(reflecting: obj).children
    }
    
    mutating func next(a) -> String? {
        guard let child = children.popFirst() else { return nil }
        return "\(child.label.wrapped) is \(child.value)"}}protocol Sequencible: Sequence {}extension Sequencible {
    func makeIterator(a) -> _Iterator {
        return _Iterator(obj: self)}}Copy the code

Here I define an empty protocol that inherits the Sequence so as not to affect the default behavior of the Sequence. Now we can use the for loop to output all of the property values of our custom class or structure as long as it complies with Sequencible. Like this:

struct Demo: Sequencible {
    var name = "Sequence"
    var author = Persion.author
}
Copy the code

Represents the type of the same sequence

Now I want to store all Sequencible compliant sequences in an array, and then loop through the elements in the array. Since all the elements in the array comply with Sequencible, I can use the for loop to print all their attributes, like this:

for obj in array {
    for item in obj {
        print(item)
    }
}
Copy the code

So what type of array should we define here? [Any] = Sequencible = Sequencible = Sequencible = Sequencible = Sequencible = Sequencible The answer is no. When this is defined, the editor displays an error like this:

Protocol 'Sequencible' can only be used as a generic constraint because it has Self or associated type requirements
Copy the code

Those familiar with Swift protocol should be familiar with this error. This means that a protocol containing Self or an association type can only be used as a generic constraint. So defining our array like this doesn’t work.

let sequencibleStore: [Sequencible] = [Persion.author, Demo()]
Copy the code

If there is such a type, wouldn’t hiding the Sequencible specific type solve the problem? This process of removing a specified type is called type erasure.

Type erasure

Recall from the Sequence protocol that we simply return an iterator through makeIterator(). We can then implement a wrapper class (struct) that stores the implementation of the iterator with a property, and then construct an iterator from the stored property in the makeIterator() method. Something like this:

func makeIterator(a) -> _AnyIterator<Element> {
    return _AnyIterator(iteratorImpl)
}
Copy the code

Our encapsulation can be defined as follows:

struct _AnySequence<Element> :Sequence {
    private var iteratorImpl: () -> Element?
}
Copy the code

The array above can be initialized like this:

let sequencibleStore: [_AnySequence<String>] = [_AnySequence(Persion.author), _AnySequence(Demo())"Copy the code

_AnySequence hides the Sequence type, and the caller only knows that the element in the array is a Sequence that can iterate over the output string type.

Now we can implement the _AnyIterator and _AnySequence above step by step. The _AnyIterator implementation is the same as the _AnySequence implementation mentioned above. Instead of storing the iterator directly, we let the wrapper class store the iterator’s next function. To do this, we must first copy the Iterator arguments into a variable so that we can call its next method. Here is the implementation:

struct _AnyIterator<Element> {
    var nextImpl: () -> Element?
}

extension _AnyIterator: IteratorProtocol {
    init<I> (_ iterator: I) where Element= =I.Element.I: IteratorProtocol {
        var mutatedIterator = iterator
        nextImpl = { mutatedIterator.next() }
    }
    
    mutating func next(a) -> Element? {
        return nextImpl()
    }
}
Copy the code

Now, in _AnyIterator, the specific type of the iterator (such as _Iterator used above) is specified only at instance creation time. After that the specific type is hidden. We can use any type of iterator to create an instance of _AnyIterator:

var iterator = _AnyIterator(_Iterator(obj: Persion.author))
while let item = iterator.next() {
    print(item)
}
// out put:
// name is jewelz
// age is 23
// email is [email protected]
Copy the code

We want to create a _AnyIterator by passing in a closure. Now add the following code:

 init(_ impl: @escaping () -> Element?). { nextImpl = impl }Copy the code

This initialization method was added to facilitate the implementation of _AnySequence later. The _AnySequence has an attribute that stores the implementation of the iterator, so our _AnyIterator can be initialized using a closure.

After the _AnyIterator implementation is complete, we can implement our _AnySequence. Here I give the code directly, students can implement their own:

struct _AnySequence<Element> {

    typealias Iterator = _AnyIterator<Element>
    
    private var iteratorImpl: () -> Element?
}

extension _AnySequence: Sequence {
    init<S> (_ base: S) where Element= =S.Iterator.Element.S: Sequence {
        var iterator = base.makeIterator()
        iteratorImpl = {
            iterator.next()
        }
    }
    
    func makeIterator(a) -> _AnyIterator<Element> {
        return _AnyIterator(iteratorImpl)
    }
}
Copy the code

The specified constructor for _AnySequence is also defined as generic, taking as a parameter any Sequence that follows the Sequence protocol, and specifying that the return type of the next() iterator for the Sequence is the same as the Element type of the generic structure we defined. This generic constraint here is where the magic comes in for type erasure. It hides the exact type of the sequence and can be used as if the values in the sequence are of the same type. For example, array can be described as “any sequence of elements whose type is String”.

let array = [_AnySequence(Persion.author), _AnySequence(Demo())"for obj in array {
    print("+ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +")
    for item in obj {
        print(item)
    }
}
// out put:
// name is jewelz
// age is 23
// email is [email protected]
// +-------------------------+
// name is Sequence
// author is Persion(name: "jewelz", age: 23, email: "[email protected]")
Copy the code

Thanks to Swift’s type inference, the array here is of type [_AnySequence

] without being explicitly typed. Click the Option key and you will see that it is of type [_AnySequence

]. That is, any sequence whose elements are only strings can be an element of an array. This is consistent with the semantics we normally use like “an array of type Int”. If you want to insert a new element into an array, you can create a sequence like this:

let s = _AnySequence { () -> _AnyIterator<String> in
    return _AnyIterator { () -> String? in
        return arc4random() % 10= =5 ? nil : String(Int(arc4random() % 10))
    }
}
array.append(s)
Copy the code

Initializing a _AnySequence with a closure is an example of how to initialize a _AnySequence.

Write in the last

In the standard library, AnyIterator and AnySequence are already provided. I haven’t seen the implementation of the standard library yet, but if you’re interested, you can check it out here. I’ve implemented my own _AnyIterator and _AnySequence here just to provide an idea for type erasure. If you frequently use protocols with associative types or Self in your projects, you are bound to run into the same problem I did. Implement a type erasure wrapper that hides the specific type and you don’t have to worry about Xcode errors.