You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift UIKit闭包顺序问题:API请求后UI更新遇隐式解包nil错误求助

问题分析与修复方案

核心问题

  1. 隐式解包崩溃:selectedMovie被声明为隐式解包可选类型(MovieDetail!),但API请求是异步操作,请求完成前访问该变量会触发Unexpectedly found nil错误。
  2. Completion未调用:loadImage方法定义了completion闭包,但内部未执行该闭包,导致后续UI更新代码永远无法运行。
  3. 异步流程与线程问题:现有回调嵌套逻辑存在顺序漏洞,且部分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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 07:51:11