This is for Swift Version 2.2 & earlier. I will be adding the snippet of code for the changes the Swift 3.x have introduced.
What are the Collection Type & Sequence Type Protocols?
Theย Collection Type, Sequence Type & Generator Typeย Protocols define rules that govern how different data structures or collections of data can be used, interacted with and operated within the Swift programming language. Theย CollectionType is a special case of theย SequenceType.
Why do we need such Protocols?
Lets take the example of the Swiftย For-Loop.
var arrOfStrings : [String] = [String]()
arrOfStrings.append("Jill")
arrOfStrings.append("Jack")
arrOfStrings.append("John")
arrOfStrings.append("Jane")
for name in arrOfString
{
ย ย ย print("The name is \(name)")
}
Now, if we have created our own data type. We would not be able to use the aboveย for-loop as it would not conform to the … type protocols. Theย for-loop is expecting a data structure that acts and behaves in a way that is governed by the … protocols.
Just like theย for-loop example above there are many other features within the Swift Programming Language that expect data structures to act and behave in a particular way. By designing our data structures to conform to these protocols we can make the easily compatible with the existing code and language features out there.
How do we use these protocols for our own data structures?
First we need to decide what kind of collection are we making. For the sake of this example I will create a Custom Stack.
class CustomStack<Element>
{
ย ย var data : [Element] = [Element]()
ย ย func push(Element newElement : Element)
ย ย {
ย ย ย data.append(newElement)
ย ย }
ย ย func pop() -> Element
ย ย {
ย ย ย ย return data.removeLast()
ย ย }
}
The above code is very simple for the purpose of this exercise. Its a stack. Which is internally really an Array. It has functions to push data and pop data. We are now going to convert this type to a collection to conform to theย CollectionType protocol.
Implementing the Indexable Protocol methods
As a first step we are going to make our CustomStack conform to the Indexable Protocol.
extension CustomStack : Indexable
{
ย ย //INDEXABLE PROTOCOLS
ย ย typealias Index = Int
ย ย var startIndex : Int
ย ย {
ย ย ย ย return 0
ย ย }
ย ย var endIndex: Int
ย ย {
ย ย ย ย return (data.count - 1)
ย ย }
ย ย subscript (position : Int) -> Element
ย ย {
ย ย ย ย return data[position]
ย ย }
}
The above change makes the data structure conform to theย Indexable protocol. This is a requirement for it to be of type CollectionType.ย In order to conform to theย Indexable protocol we need to implement a few computed properties. Let us look at the changes
typealias Index = Int
This line informs the system that the Indexing type for my data structure is anย Int.
var startIndex : Int
{
ย ย return 0
}
var endIndex: Int
{
ย ย return (data.count - 1)
}
The next 2 are computed properties. Each provides the implementation of theย startIndexย andย endIndex properties. Note that the type for both isย Int as we have declared the Index type earlier asย Int.
subscript (position : Int) -> Element
{
ย ย return data[position]
}
The last implementation is of subscript. This provides the implementation to access an Element from the Stack using the Subscript operator.
Implementing the Sequence Type Protocol
Next we will implement the Sequence Type Protocol methods.
extension CustomStack : SequenceType
{
ย ย typealias Generator = AnyGenerator<Element>
ย ย ย
ย ย func generate() -> Generator
ย ย {
ย ย ย ย var index = 0
ย ย ย ย ย
ย ย ย ย return AnyGenerator(body: {() -> Element? in
ย ย ย ย ย ย if index < self.data.count
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย let res =ย self.data[index]
ย ย ย ย ย ย ย ย index += 1
ย ย ย ย ย ย ย ย return res
ย ย ย ย ย ย }
ย ย ย ย ย ย return nil
ย ย ย ย })
ย ย }
}
Let us examine this code line by line.
typealias Generator = AnyGenerator<Element>
Objects of type Generator allow us to navigate through our collection. Quite like howย iteratorsย work in C++. This line specifies the type to beย AnyGenerator for Elements.
func generate() -> Generator
Next we start the implementation of theย generate function. This is required as part of theย SequenceTypeย protocol.
var index = 0
This index variable is used to track the element that is currently being accessed.
return AnyGenerator(body: {() -> Element? in
ย ย ย ย ย ย if index < self.data.count
ย ย ย ย ย ย {
ย ย ย ย ย ย ย ย let res =ย self.data[index]
ย ย ย ย ย ย ย ย index += 1
ย ย ย ย ย ย ย ย return res
ย ย ย ย ย ย }
ย ย ย ย ย ย return nil
ย ย ย ย })
Theย return statement is the main statement. Here we are creating an object of typeย AnyGenerator. As an argument to the constructor call we are passing in a closure that will be used to iterate through the sequence. Note that the closure captures theย index variable and holds a reference to its value even though we have left the original function.
Implementing the Collectionย Type Protocol
Next we will implement the Collectionย Type Protocol methods. We don’t really need to implement a lot in order to conform to theย CollectionType protocol. In fact, if we just conform to theย CollectionType protocol and use the implementations of the previous 2 extensions we should be just fine. However, for the sake of demonstration we are implementing theย subscript functionality within theย CollectionType.
extension CustomStack : CollectionType
{
ย ย typealias SubSequence = CustomStack<Element>
ย ย ย
ย ย subscript (bounds: Range<CustomStack.Index>) -> CustomStack.SubSequence
ย ย {
ย ย ย ย let newStack : CustomStack<Element> = CustomStack<Element>()
ย ย ย ย ย
ย ย ย ย for i in bounds.startIndex...bounds.endIndex
ย ย ย ย {
ย ย ย ย ย ย newStack.push(Element: data[i])
ย ย ย ย }
ย ย ย ย return newStack
ย ย }
}
Let us look at the code line by line.
typealias SubSequence = CustomStack<Element>
Again, as before this line indicates that theย SubSequence type is actually aย CustomStack.
subscript (bounds: Range<CustomStack.Index>) -> CustomStack.SubSequence
Here we start the implementation of the subscript functionality.
let newStack : CustomStack<Element> = CustomStack<Element>()
ย ย ย ย ย
for i in bounds.startIndex...bounds.endIndex
{
newStack.push(Element: data[i])
}
return newStack
The rest of the code is the implementation of the subscript range behaviour. One can have different implementations to achieve the same result.
CollectionType Video
Conclusion
As we can see, by designing our data structure to conform to a particular set of protocols. We have made it possible for our data structure to take advantages of the different features, functionalities and even API’s available within the Swift Language and the Frameworks used as a part of iOS, macOS, watchOS & tvOS development.