How to reference array elements safely in Swift

Arrays are a frequently used data structure type to store related objects together and the values are fetched from them using indexes. So we do this a lot

struct Book {
var title: String
}
var library: [Book] = [Book(title: "hello"), Book(title: "world")]

And then we subsequently access them like:


libarary[0] or library[1]

these sort of access can frequently lead to Fatal error: Index out of range

To avoid this a safer way to access array elements in Swift can be to leverage the Arrays.counts property before doing an access. So the modified access can look like:

return library.count > index ? library[index] : nil

This logic is certainly safer and will not lead to the Fatal error: Index out of range exceptions. 
But the disadvantage of this approach can be the nil checks and handling it via optional chaining.

A better scalable approach could be to leverage the Iterator and Sequence protocol to access it like :

for book in library {
print("title", book.title)
}

To leverage that we have to add more structure to the code and we will need some more classes/structs. 
Lets create our iterator class which stores this data object in an array:

struct Library {
var books: [Book]
}

Lets go ahead and implement an iteratorProtocol for the Library.

class LibraryIterator: IteratorProtocol {
var array: [Book]
var current: Int = 0
init(_ array: [Book]) {
self.array = array
}

func next() -> Book? {
defer {
current += 1
}
return array.count > current ? array[current] : nil
}
}

Now this iterator protocol implementation can be used to implement the sequence protocol for Library.


extension Library: Sequence {
func makeIterator() -> LibraryIterator {
return LibraryIterator(self.books)
}
}

and now we can safely use it:


let library = Library(books: [Book(title: “hello”), Book(title: “World”)])
for eachBook in library {
print(eachBook.title)
}

Looks good, right!
But this is still not ideal. A drawback of this approach is the amount of structure needed every time we have to create a library. In this case Library is strongly tied with Book type but libraries can store other types apart from Books, for example: Movies, Images, etc. 
So lets leverage Swift Generics to make this work for all data objects types. It only requires a small amount of tweaks and the final code will look something like this:


struct Library<T> {
var array: [T]
}
class LibraryIterator<T>: IteratorProtocol {
var array: [T]
var current: Int = 0
init(_ array: [T]) {
self.array = array
}

func next() -> T? {
defer {
current += 1
}
return array.count > current ? array[current] : nil
}
}
extension Library: Sequence {
func makeIterator() -> LibraryIterator <T> {
return LibraryIterator(self.array)
}
}

Its very similar to our initial draft. Now we can create Library with as many Data types and use them as sequence.


struct Media {
var mediaName: String
}
struct Images {
var imageName: String
}
let mediaLibrary = Library(array: [Media(mediaName: "test1"), Media(mediaName: "test2")])
let imageLibrary = Library(array: [Image(imageName: "format1"), Image(imageName: "format2")])
for eachMedia in mediaLibrary {
    print(eachMedia.mediaName)
}
for eachImage in imageLibrary {
    print(eachImage.imageName)
}

This just showcases how we can use Sequence and Iterator protocol along with Array and Generics to make our code cleaner and remove exceptions while accessing elements stored in an array.