Last active
September 18, 2020 01:53
-
-
Save lamprosg/b8dfe0ac82d878d70252fafce8bd8764 to your computer and use it in GitHub Desktop.
(iOS) Conforming to Sequence & Collection protocols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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 | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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