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

Swift代理赋值self遇nil解包错误:跨类控制UIImageView与AVAudioPlayer

解决AVAudioPlayer代理设置时的nil崩溃问题

看起来你在跨类通信时遇到了代理设置的致命错误,我帮你拆解下问题所在,一步步解决:

首先看Card类里的几个关键问题

  1. 错误手动调用代理方法:你在playSound()里直接调用了audioPlayerDidFinishPlaying(player),这完全没必要——AVAudioPlayer的代理方法是当播放结束时由系统自动触发的,手动调用不仅逻辑错误,还可能在代理还没设置好的时候执行,导致打印"the delegate is not set"。
  2. 未设置AVAudioPlayer的代理:你注释掉了player?.delegate = self,这会导致播放结束时系统根本不会通知Card类,自然也不会触发代理回调给ViewController。
  3. 代理未使用weak引用:当前delegate是强引用,如果ViewController持有Card实例,会形成循环引用,导致内存泄漏,而且协议没有类限制,可能引发不必要的问题。

修复Card类的代码

先把Card类调整正确:

import Foundation
import UIKit
import AVFoundation

// 加上class限制,确保只有类能遵守,同时代理用weak避免循环引用
protocol isOnProtocol: class { 
    func isOn() 
}

class Card: NSObject {
    weak var delegate: isOnProtocol? // 使用weak
    var player: AVAudioPlayer?
    var image: UIImage
    var soundUrl: String
    
    init(image: UIImage, soundUrl: String, isActive: Bool = true) {
        self.image = image
        self.soundUrl = soundUrl
        super.init() // 显式调用NSObject的初始化方法,更安全
    }
    
    func playSound() {
        guard let url = Bundle.main.url(forResource: self.soundUrl, withExtension: "m4a") else {
            print("找不到音频文件:\(soundUrl).m4a")
            // 播放失败时恢复imageView状态,避免用户无法操作
            delegate?.isOn()
            return
        }
        
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback)
            try AVAudioSession.sharedInstance().setActive(true)
            
            player = try AVAudioPlayer(contentsOf: url)
            player?.delegate = self // 打开代理设置
            guard let player = player else { return }
            
            player.prepareToPlay()
            player.play()
            print("开始播放音频")
        } catch let error {
            print("播放失败:\(error.localizedDescription)")
            delegate?.isOn()
        }
    }
}

extension Card: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        // 系统触发的代理方法,带成功标志更严谨
        if flag {
            print("播放完成")
        } else {
            print("播放未成功完成")
        }
        delegate?.isOn()
    }
}

ViewController里的正确实现

接下来确保ViewController里的Card实例初始化和代理设置正确,避免nil崩溃:

class ViewController: UIViewController, isOnProtocol {
    // 确保imageView和card都正确初始化
    var card: Card!
    var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化imageView(示例代码,根据你的实际布局调整)
        imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
        imageView.image = UIImage(named: "your_card_image")
        imageView.isUserInteractionEnabled = true
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewTapped))
        imageView.addGestureRecognizer(tapGesture)
        view.addSubview(imageView)
        
        // 初始化Card实例,确保图片和音频文件名正确
        guard let cardImage = UIImage(named: "your_card_image") else {
            print("找不到卡片图片")
            return
        }
        card = Card(image: cardImage, soundUrl: "your_audio_file_name")
        card.delegate = self // 此时card已初始化,不会出现nil崩溃
    }
    
    // 点击imageView时禁用它并播放声音
    @objc func imageViewTapped() {
        // 禁用imageView的交互和视觉反馈
        imageView.isUserInteractionEnabled = false
        imageView.alpha = 0.5
        
        // 播放音频
        card.playSound()
    }
    
    // 实现代理方法,播放完成后恢复imageView
    func isOn() {
        DispatchQueue.main.async { // 确保在主线程更新UI
            self.imageView.isUserInteractionEnabled = true
            self.imageView.alpha = 1.0
            print("恢复imageView可操作状态")
        }
    }
}

排查nil崩溃的核心要点

如果还是出现崩溃,检查这几点:

  • 确保card在设置card.delegate = self之前已经被完全初始化,不要用未初始化的隐式可选类型(Card!)直接设置代理。
  • 检查音频文件是否真的存在于项目Bundle中,并且文件名和soundUrl完全一致(iOS区分文件名大小写)。
  • 确保ViewController遵守了isOnProtocol协议,并且实现了isOn()方法,否则编译器会报错,但运行时的nil崩溃大概率是Card实例未初始化导致的。

内容的提问来源于stack exchange,提问作者Anthony Rubin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 03:56:41