Swift UIKit闭包顺序问题:API请求后UI更新遇隐式解包nil错误求助
问题分析与修复方案
核心问题
- 隐式解包崩溃:
selectedMovie被声明为隐式解包可选类型(MovieDetail!),但API请求是异步操作,请求完成前访问该变量会触发Unexpectedly found nil错误。 - Completion未调用:
loadImage方法定义了completion闭包,但内部未执行该闭包,导致后续UI更新代码永远无法运行。 - 异步流程与线程问题:现有回调嵌套逻辑存在顺序漏洞,且部分UI更新未明确在主线程执行,可能引发界面更新异常。
修复步骤
1. 修正selectedMovie类型
将隐式解包改为普通可选类型,避免意外崩溃:
private var selectedMovie: MovieDetail?
2. 完善loadImage与图片加载扩展
给loadImage添加空值判断,并确保图片加载完成后调用completion;同时修改UIImageView的load方法,新增completion参数:
// DetailViewController中的loadImage方法 func loadImage(completion: @escaping ()->()){ guard let posterUrl = selectedMovie?.poster else { completion() return } imageMovie.load(urlString: posterUrl) { completion() } } // UIImageView扩展修改 extension UIImageView { func load(urlString : String, completion: @escaping ()->()) { guard let url = URL(string: urlString)else { completion() return } DispatchQueue.global().async { [weak self] in let image: UIImage? if let data = try? Data(contentsOf: url) { image = UIImage(data: data) } else { image = nil } DispatchQueue.main.async { self?.image = image completion() } } } }
3. 理顺异步流程与UI更新逻辑
确保API请求完成后再加载图片,最后在主线程统一更新UI,同时优化getMovieDetails的回调顺序:
// getMovieDetails方法优化 func getMovieDetails(completion : @escaping (_ movieDetail: MovieDetail) ->()) { omdbApi.getMovieDetail(imdbID: imdbID) { [weak self] movieDetail in guard let self = self, let movieDetail = movieDetail else { return } self.selectedMovie = movieDetail print("getmoviedetails ici \(self.selectedMovie!)") completion(movieDetail) } } // updateUI方法优化 func updateUI(){ getMovieDetails { [weak self] movieDetail in guard let self = self else { return } self.loadImage { DispatchQueue.main.async { self.titleLabel.text = "\(movieDetail.title)" self.yearLabel.text = "Year : \(movieDetail.year)" self.releasedLabel.text = "Released : \(movieDetail.released)" self.runtimeLabel.text = "Runtime : \(movieDetail.runtime)" self.genreLabel.text = "Genre : \(movieDetail.genre)" self.directorLabel.text = "Director : \(movieDetail.director)" self.writerLabel.text = "Writer : \(movieDetail.writer)" self.actorsLabel.text = "Actors : \(movieDetail.actors)" self.plotLabel.text = "Plot : \(movieDetail.plot)" self.languageLabel.text = "Language : \(movieDetail.language)" self.countryLabel.text = "Country : \(movieDetail.country)" self.awardsLabel.text = "Awards : \(movieDetail.awards)" self.imdbRatingLabel.text = "IMDB Rating : \(movieDetail.imdbRating)" self.imdbIDLabel.text = "IMDB ID : \(movieDetail.imdbID)" } } } }
完整修复代码
DetailViewController.swift
class DetailViewController: UIViewController { //MARK: UI Elements @IBOutlet var imageMovie: UIImageView! @IBOutlet var titleLabel: UILabel! @IBOutlet var yearLabel: UILabel! @IBOutlet var releasedLabel: UILabel! @IBOutlet var runtimeLabel: UILabel! @IBOutlet var genreLabel: UILabel! @IBOutlet var directorLabel: UILabel! @IBOutlet var writerLabel: UILabel! @IBOutlet var actorsLabel: UILabel! @IBOutlet var plotLabel: UILabel! @IBOutlet var languageLabel: UILabel! @IBOutlet var countryLabel: UILabel! @IBOutlet var awardsLabel: UILabel! @IBOutlet var imdbRatingLabel: UILabel! @IBOutlet var imdbIDLabel: UILabel! var imdbID = "" private var omdbApi = OmdbApi(apiKey: "31dd4179") private var selectedMovie: MovieDetail? override func viewDidLoad() { super.viewDidLoad() updateUI() } func getMovieDetails(completion : @escaping (_ movieDetail: MovieDetail) ->()) { omdbApi.getMovieDetail(imdbID: imdbID) { [weak self] movieDetail in guard let self = self, let movieDetail = movieDetail else { return } self.selectedMovie = movieDetail print("getmoviedetails ici \(self.selectedMovie!)") completion(movieDetail) } } func loadImage(completion: @escaping ()->()){ guard let posterUrl = selectedMovie?.poster else { completion() return } imageMovie.load(urlString: posterUrl) { completion() } } func updateUI(){ getMovieDetails { [weak self] movieDetail in guard let self = self else { return } self.loadImage { DispatchQueue.main.async { self.titleLabel.text = "\(movieDetail.title)" self.yearLabel.text = "Year : \(movieDetail.year)" self.releasedLabel.text = "Released : \(movieDetail.released)" self.runtimeLabel.text = "Runtime : \(movieDetail.runtime)" self.genreLabel.text = "Genre : \(movieDetail.genre)" self.directorLabel.text = "Director : \(movieDetail.director)" self.writerLabel.text = "Writer : \(movieDetail.writer)" self.actorsLabel.text = "Actors : \(movieDetail.actors)" self.plotLabel.text = "Plot : \(movieDetail.plot)" self.languageLabel.text = "Language : \(movieDetail.language)" self.countryLabel.text = "Country : \(movieDetail.country)" self.awardsLabel.text = "Awards : \(movieDetail.awards)" self.imdbRatingLabel.text = "IMDB Rating : \(movieDetail.imdbRating)" self.imdbIDLabel.text = "IMDB ID : \(movieDetail.imdbID)" } } } } }
UIImageView扩展
import Foundation import UIKit extension UIImageView { func load(urlString : String, completion: @escaping ()->()) { guard let url = URL(string: urlString)else { completion() return } DispatchQueue.global().async { [weak self] in let image: UIImage? if let data = try? Data(contentsOf: url) { image = UIImage(data: data) } else { image = nil } DispatchQueue.main.async { self?.image = image completion() } } } }
内容的提问来源于stack exchange,提问作者Muammer Akca
相关产品推荐
相关产品推荐

