错误 - 使用Alamofire和AlamofireObjectMapper的Resquest函数(swift - iOS)

时间:2017-04-26 02:04:24

标签: ios json swift runtime-error alamofire

首先,我是初学者,我正在尝试构建一个应用程序,在OMDB API上搜索电影并返回电影列表(按标题搜索时),并在按imdbID搜索时返回特定电影。我必须为api提出两种类型的请求,因为按ID搜索的结果具有与按标题搜索但具有更多详细信息相同的属性(需要此信息以显示包含此结果列表中所选电影的视图)

所以,我(这里)建议使用AlamofireObjectMapper / ObjectMapper来做得更好。我做了这样的映射:

import Foundation
import AlamofireObjectMapper

class SearchResponse: Mappable {
    var isSuccess  : String?
    var searchArray: [Movie]?
    var searchCount: String?

    required init?(map: Map) {
    }

    func mapping(map: Map) {
        isSuccess   <- map["Response"]
        searchArray <- map["Search"]
        searchCount <- map["totalResults"]
    }
}

class Movie: Mappable {

    var posterURL  : String?
    var title      : String?
    var runtime    : String?
    var director   : String?
    var actors     : String?
    var genre      : String?
    var plot       : String?
    var production : String?
    var year       : String?
    var imdbID     : String?
    var imdbRating : String?

    required init?(map: Map) {

    }

    func mapping(map: Map) {
        posterURL  <- map["Poster"]
        title      <- map["Title"]
        runtime    <- map["Runtime"]
        director   <- map["Director"]
        actors     <- map["Actors"]
        genre      <- map["Genre"]
        plot       <- map["Plot"]
        production <- map["Production"]
        year       <- map["Year"]
        imdbID     <- map["imdbID"]
        imdbRating <- map["imdbRating"]
   }
}

我想做这样的事情:

//Get movie by title - the user will enter the title on a searchbar

let url = "https:www.omdbapi.com/?s=\(imdbTitle)"

func getMoviesByTitle (imdbTitle: String) {
    /* The Alamofire function using ObjectMapper goes here */
    switch
    case .success():
        /*Something*/
        completionHandler(???)
    case .failure():
       /*Something*/
        completionHandler(???)
}


//Get movie by ID

let url = "https:www.omdbapi.com/?i=\(imdbID)"

func getMovieByID(imdbID: String) {
    /* The Alamofire function using ObjectMapper goes here */
    if let response {
       completioHandler(???)
    } /* Something like this? */
}

我需要一些指导。当我按标题搜索电影时,它返回一个带有响应,搜索(电影的“数组”)和totalResults的JSON。在这种情况下,我的Movie类只有四个映射属性(Poster,Title,Year,imdbID)。

  1. 这种映射是对的吗?
  2. 我如何针对每个案件提出这些要求?我的意思是,我该怎么回事? (因为我的getMovie函数需要一个completionHandler,对吗?)
  3. 修改

    所以,我在我的SearchTableViewController上尝试了这个:

    import UIKit
    import Alamofire
    import AlamofireObjectMapper
    import ObjectMapper
    import Kingfisher
    
    
    
    class SearchTableViewController: UITableViewController, UISearchResultsUpdating {
    
    
    @IBOutlet var searchTableView: UITableView!
    
    @IBAction func showResults(_ sender: Any) {
        let searchController = UISearchController(searchResultsController: nil)
        self.present(searchController, animated: true, completion: nil)
        searchController.searchBar.barTintColor = self.searchTableView.backgroundColor!
        searchController.searchResultsUpdater = self
    
    }
    
    
    var movies = [Movie]()
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        searchTableView.dataSource = self
        searchTableView.delegate = self
    
    }
    
    func updateSearchResults(for searchController: UISearchController) {
    
        if let searchText = searchController.searchBar.text {
    
            if searchText == "" {
                return
            }
    
            else {
                let movieSearched: String = searchText.replacingOccurrences(of: " ", with: "_")
    
    
                    // MARK: Alamofire Get by Title
    
                    let URL = "https://www.omdbapi.com/?s=\(movieSearched)&type=movie"
    
    
                    Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in
    
                        print("response is: \(response)")
    
                        switch response.result {
    
                        case .success(let value):
                            let searchResponse = value
                            self.movies = (searchResponse.searchArray)!
                            self.searchTableView.reloadData()
    
                        case .failure(let error):
                            let alert = UIAlertController(title: "Error", message: "Error 4xx / 5xx: \(error)", preferredStyle: UIAlertControllerStyle.alert)
                            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                            self.present(alert, animated: true, completion: nil)
                        }
    
                    }
    
    
    
                    DispatchQueue.main.async {
                        let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
                        spinnerActivity.label.text = "Loading";
                        spinnerActivity.detailsLabel.text = "Searching movie..."
                        spinnerActivity.isUserInteractionEnabled = false;
                    }
    
    
    
    
                    DispatchQueue.main.async {
                        MBProgressHUD.hide(for: self.view, animated: true)
                    }
    
    
    
            }
    
        }
    }
    
    
    // MARK: - Table view data source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return movies.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "SearchCellIdentifier", for: indexPath) as! SearchTableViewCell
    
        let movie = movies[indexPath.row]
    
        let imgStg: String = movie.posterURL!
        let imgURL: URL? = URL(string: imgStg)
        let imgSrc = ImageResource(downloadURL: imgURL!, cacheKey: imgStg)
    
        cell.titleLabel.text = movie.title
        cell.yearLabel.text = movie.year
    
    
        cell.posterImageView.layer.cornerRadius = cell.posterImageView.frame.size.width/2
        cell.posterImageView.clipsToBounds = true
    
        //image cache with KingFisher
        cell.posterImageView.kf.setImage(with: imgSrc)
    
    
    
        return cell
    }
    
    
    /*
    // Override to support conditional editing of the table view.
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
    */
    
    /*
    // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // Delete the row from the data source
            tableView.deleteRows(at: [indexPath], with: .fade)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }
    */
    
    /*
    // Override to support rearranging the table view.
    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
    
    }
    */
    
    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the item to be re-orderable.
        return true
    }
    */
    
    /*
    // MARK: - Navigation
    
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */
    
    }
    

    搜索工作正常,直到我键入4个字符...直到输入第3个字符,表格视图实时显示结果,但是当我输入第4个字符时,应用程序崩溃。错误是这样的:

    error with self.movies = (searchResponse.searchArray)! line

1 个答案:

答案 0 :(得分:1)

你得到的错误是由于强行打开(!)。输入第4个字符后,searchResponse.searchArray可能会返回空白状态。

您的movies var应该是可选的 - 这意味着它可能是nil

var movies:[Movie]?

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if let safeMovies = movies {
        return safeMovies.count
    } else {
        return 0 //no movies were returned.  you could eventually show an error here
    }
}

在你的Alamofire responseObject闭包中(只显示更新的部分)

case .success(let value):
    let searchResponse = value
    self.movies = searchResponse.searchArray
    self.searchTableView.reloadData()

关于updateSearchResults方法的一些其他想法。您可以使用guard打开并检查searchText,这样您就不需要大量的其他{}语句。 Alamofire完成后,您还应该删除MBProgressHUD叠加层,否则您将同时显示然后隐藏。

func updateSearchResults(for searchController: UISearchController) {

    guard let searchText = searchController.searchBar.text, searchText != "" else {
        return
    }

    let movieSearched: String = searchText.replacingOccurrences(of: " ", with: "_")
    // MARK: Alamofire Get by Title
    let URL = "https://www.omdbapi.com/?s=\(movieSearched)&type=movie"
    Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in
        print("response is: \(response)")
        DispatchQueue.main.async {
            MBProgressHUD.hide(for: self.view, animated: true)
        }
        switch response.result {
        case .success(let value):
            let searchResponse = value
            self.movies = (searchResponse.searchArray)!
            self.searchTableView.reloadData()

        case .failure(let error):
            let alert = UIAlertController(title: "Error", message: "Error 4xx / 5xx: \(error)", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }

    DispatchQueue.main.async {
        let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
        spinnerActivity.label.text = "Loading";
        spinnerActivity.detailsLabel.text = "Searching movie..."
        spinnerActivity.isUserInteractionEnabled = false;
    }
}