Swift使用delegate与protocol跨控制器传值时遇到可选值解包为nil问题求助
崩溃根因排查与修复方案
核心问题定位
你遇到的found nil while unwrapping an Optional value报错,和异步网络请求、封装的ArtistViewModel本身没有关系——既然你已经验证单独打印artistViewModel?.artistName能正常获取数据,说明艺人数据是完整、非空的。
99%的触发原因是你对delegate的持有关系、赋值时机理解错位,调用的delegate属性本身是未赋值的nil,被隐式强解包触发崩溃。
分步排查逻辑
- 第一步:定位崩溃的精确对象
把触发崩溃的那行delegate.loadArtistViewModel(data:)代码拆开,单独打断点打印delegate本身的值:如果控制台输出nil,就能100%确认崩溃源——不是传的数据有问题,是delegate引用本身根本没有被正确赋值。
很多新手初学delegate时的典型错误写法如下:
这种写法下,哪怕你把VM打印的再正确,只要delegate引用为空,调用方法必然崩溃。// 主列表VC的错误实现 class ArtistSearchListVC: UIViewController { // 错误:声明了隐式解包的delegate属性,全程没有任何地方给它赋值 var artistViewModelDelegate: ArtistViewModelDelegate! func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let currentArtist = fetchedList[indexPath.row] let artistVM = ArtistViewModel(artist: currentArtist) // 此时delegate本身是nil,强解包直接触发崩溃 artistViewModelDelegate.loadArtistViewModel(data: artistVM) } } - 第二步:纠正delegate的正确持有关系
delegate的核心逻辑是:持有delegate属性的对象是事件发起方,它通过自己持有的delegate引用,向遵守协议的对象索要数据、传递事件;而不是遵守协议的对象,主动调用自己持有的delegate方法去传值。
对应你当前从列表页跳转到ArtistViewController的正向传值场景,正确的结构应该是:- 在ViewModel文件里定义协议,协议必须继承
AnyObject,方便后面用weak修饰避免循环引用:
protocol ArtistViewModelDelegate: AnyObject { func loadArtistViewModel(data: ArtistViewModel) }- 在
ArtistViewController内部声明delegate属性,绝对不要写在列表页里:
class ArtistViewController: UIViewController { // 必须用weak修饰,类型用可选值,不要用隐式解包 weak var delegate: ArtistViewModelDelegate? }- 列表页遵守
ArtistViewModelDelegate协议,在跳转的准备环节(也就是prepare(for:sender:)方法)把自己赋值给ArtistViewController的delegate属性,等详情页加载完成后,详情页再通过delegate方法向列表页请求对应艺人数据。
- 在ViewModel文件里定义协议,协议必须继承
- 第三步:排查异步请求的关联影响
你怀疑的异步Alamofire请求问题,在当前场景下不会触发这个崩溃。如果真的是网络回调未完成、数据没加载好,你打印artistName的时候就会返回nil,和你现在“能正常获取VM数据”的现象矛盾。
唯一需要确认的是:Alamofire请求完成、刷新tableView的操作要切回主线程,避免出现cell的indexPath错位,但这个问题不会导致delegate引用为nil。 - 第四步:优化delegate声明,避免隐式解包崩溃
把所有delegate属性的隐式解包(以!结尾的类型)改成可选值(以?结尾的类型),调用时用可选链语法,哪怕真的出现未赋值的情况,也会跳过方法调用而不是直接崩溃:
错误写法:
正确写法:var delegate: ArtistViewModelDelegate! delegate.loadArtistViewModel(data: vm)weak var delegate: ArtistViewModelDelegate? delegate?.loadArtistViewModel(data: vm)
常见踩坑提醒
- 不要给delegate不加
weak修饰,否则会出现两个页面互相强引用的循环内存泄漏,甚至因为页面提前释放导致delegate引用变成nil - 如果是用Storyboard的Segue做页面跳转,一定要在
prepare(for:sender:)方法里完成delegate的赋值,不要等页面跳转完成、详情页已经出现在屏幕上之后再回头赋值,那个时候赋值时机太晚,也会拿到nil的delegate引用 - 对于当前“列表页传数据给详情页”的简单正向场景,其实可以直接把
ArtistViewModel作为详情页的公开属性,在跳转前直接赋值,比绕一圈用delegate更简单。delegate更适合用在详情页反过来向列表页传交互事件的反向传值场景。
内容的提问来源于stack exchange,提问作者Xavier Johanis
相关产品推荐
相关产品推荐

