How to use guard or if statement to avoid: Fatal error: Index out of range?

    var find: MovieMDB?
    SearchMDB.movie(query: (movie?.title!.components(separatedBy: "[").first)!, language: "en", page: 1, includeAdult: true, year: nil, primaryReleaseYear: nil, completion: {
            data, movies in
        
        guard movies != nil else { return }
    
        find = movies?[0] <-- Thread 1: Fatal error: Index out of range
        
    })
    
    if find != nil {
        
        print(find!.title)
        print(find!.overview)
    }

Assuming movies is an array:

let indexWeCareAbout = 0
guard let unwrappedMovies = movies, // Cancels if nil.
    unwrappedMovies.indices.contains(indexWeCareAbout) else { // Cancels if the index is out of bounds.
    return
}
find = unwrappedMovies[indexWeCareAbout]
1 Like

To clarify, 0 would be out of bounds if the array is empty.

If you will only ever care about index 0, you could do this instead:

find = movies?.first

It is working perfect, thank you very much!!

but for some reason I still cannot manage to archive my goal.

override func viewDidLoad() {
    super.viewDidLoad()
    
    var find: MovieMDB?
     SearchMDB.movie(query: (movie?.title!.components(separatedBy: "[").first)!, language: "en", page: 1, includeAdult: true, year: nil, primaryReleaseYear: nil){
     data, movies in
     
        let indexWeCareAbout = 0
        guard let unwrappedMovies = movies, // Cancels if nil.
            unwrappedMovies.indices.contains(indexWeCareAbout) else { // Cancels if the index is out of bounds.
                return

        }

        find = movies?.first
        print(find!.title) <- prints well if I use here
        print(find!.overview) <- prints well if I use here

    }


    print(find!.title) <- Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value (If I use it here)
    print(find!.overview)


}

I suspect SearchMDB.movie(query: ...) leaves the main thread and performs its query in the background.

That means when you call it, it starts, but the code does not wait for it to finish, instead continuing to the next line. You can try swapping both print statements for simple strings (print("Running closure...") and print("Continuing on...")) so that you can see if that is happening. If so, then the latter lines are asking for the search result before the search in the closure has finished, which is why it finds nil.

The fix will be to either do your logic inside the closure, or put it in a separate method somewhere that the closure can call. You wonā€™t be able to use the result in viewDidLoad(). While you could technically force it to stall and wait for it to complete before continuing, you probably donā€™t want to freeze the interface while you wait for the response. You will also want to check the documentation for SearchMDB to see whether it runs the closure on the main thread, or if you have to reā€enter the main thread yourself.

You are entering advanced territory though, so it would probably be good to find yourself a tutorial on concurrency if you arenā€™t already familiar with it.

1 Like

If you only ever care about the first movie in the collection, you can unwrap first inside the guard, removing the messy index checks and removing the ? and ! in the later lines as it will be already unwrapped. The body of the closure can therefore be simplified to:

guard let movie = movies?.first else { return } // if we have at least one movie, continue by setting the variable `movie` to the element at index 0 (the `first` one), else return and escape the scope

print(movie.title) // `movie` is now unwrapped so we do not need to use the ! operator anymore
print(movie.overview)
1 Like