Skip to content

Instantly share code, notes, and snippets.

@serefercelik
Forked from lamprosg/1.SequenceExample.swift
Created September 18, 2020 01:53
Show Gist options
  • Save serefercelik/129a5912a5a09abb18474e79c9f9e24c to your computer and use it in GitHub Desktop.
Save serefercelik/129a5912a5a09abb18474e79c9f9e24c to your computer and use it in GitHub Desktop.
(iOS) Conforming to Sequence & Collection protocols
//A sequence is a list of values that you can step through one at a time.
//The most common way to iterate over the elements of a sequence is to use a for-in loop:
/*
Potentially our database could contain a large set of records,
and for each model we need to hit the disk to actually load its data,
so we don’t want to load everything at once.
To make this happen, we’re going to replace the array return type with our own custom sequence.
*/
//https://www.swiftbysundell.com/articles/swift-sequences-the-art-of-being-lazy/
//All we need to do to conform to Sequence,
//is to be able to act as a factory for creating iterators.
//An iterator is what Swift actually uses to iterate over our sequence, like in a for-loop or forEach() call.
struct ModelSequence: Sequence {
func makeIterator() -> ModelIterator {
return ModelIterator()
}
}
/*
For our iterator, we’re going to keep loading a model from disk, until one couldn’t be found anymore,
in which case we’ll return nil.
Returning nil from an iterator’s next() method signals that the sequence has come to an end
and the iteration will stop.
*/
struct ModelIterator: IteratorProtocol {
private let database: Database
private var index = 0
init(database: Database = .shared) {
self.database = database
}
mutating func next() -> Model? {
let model = database.model(at: index)
index += 1
return model
}
}
//BONUS
//There is AnySequence which
//has a closure-based API that you can use to quickly implement simple sequences
class ModelLoader {
func loadAllModels() -> AnySequence<model> {
return AnySequence { () -> AnyIterator<model> in
var index = 0
return AnyIterator {
let model = database.model(at: index)
index += 1
return model
}
}
}
}
func searchForModel(matching query: String) -> Model? {
for model in ModelSequence() {
if model.title.contains(query) {
return model
}
}
return nil
}
//Don’t assume that multiple for-in loops on a sequence will either resume iteration or restart from the beginning
/*
A conforming sequence that is not a collection is allowed to produce an arbitrary sequence of elements
in the second for-in loop.
*/
//To establish that a type you’ve created supports nondestructive iteration,
//add conformance to the Collection protocol.
/*
All collections in the Swift standard library conform to the Collection protocol,
which in turn inherits from the Sequence protocol.
*/
//https://www.swiftbysundell.com/articles/creating-custom-collections-in-swift/
struct ProductCollection {
typealias DictionaryType = [Category : [Product]]
// Underlying, private storage, that is the same type of dictionary
// that we previously was using at the call site
private var products = DictionaryType()
// Enable our collection to be initialized with a dictionary
init(products: DictionaryType) {
self.products = products
}
}
//Conform to Collection and implemet the protocol requirements
extension ProductCollection: Collection {
// Required nested types, that tell Swift what our collection contains
typealias Index = DictionaryType.Index
typealias Element = DictionaryType.Element
// The upper and lower bounds of the collection, used in iterations
var startIndex: Index { return products.startIndex }
var endIndex: Index { return products.endIndex }
// Required subscript, based on a dictionary index
subscript(index: Index) -> Iterator.Element {
get { return products[index] }
}
// Method that returns the next index when iterating
func index(after i: Index) -> Index {
return products.index(after: i)
}
}
//Now we can do this to get the categories
let categories = productCollection.map { $0.key }
//Let's add some custon API
extension ProductCollection {
subscript(category: Category) -> [Product] {
get { return products[category] ?? [] }
set { products[category] = newValue }
}
}
extension ProductCollection {
mutating func insert(_ product: Product) {
var productsInCategory = self[product.category]
productsInCategory.append(product)
self[product.category] = productsInCategory
}
}
class ShoppingCart {
private(set) var products = ProductCollection()
func add(product: Product) {
products.insert(product)
}
}
//OR
//to initialize it with a dictionary (since we use one)
extension ProductCollection: ExpressibleByDictionaryLiteral {
typealias Key = Category
typealias Value = [Product]
init(dictionaryLiteral elements: (Category, [Product])...) {
for (category, productsInCategory) in elements {
products[category] = productsInCategory
}
}
}
let products: ProductCollection = [
.dairy: [
Product(name: "Milk", category: .dairy),
Product(name: "Butter", category: .dairy)
],
.vegetables: [
Product(name: "Cucumber", category: .vegetables),
Product(name: "Lettuce", category: .vegetables)
]
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment