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

初始化带逃逸闭包的ListMock时触发EXC_BAD_ACCESS崩溃

初始化含逃逸闭包的ListMock时触发EXC_BAD_ACCESS崩溃排查

问题现象

在单元测试中初始化ListMock类时,持续触发EXC_BAD_ACCESS (code=1, address=0x0)崩溃,该类包含一个逃逸闭包作为初始化参数。经调试确认问题与HashDiffable协议相关,添加Sendable一致性后问题仍未解决。环境:Swift 5.9、Xcode 15.4。

相关代码

ListMock类声明

public class ListMock<T: HashDiffable & Equatable>: UICollectionView {
    private let content: (T) -> UICollectionViewCell
    
    public init(
        collectionViewLayout: UICollectionViewLayout = UICollectionViewFlowLayout(),
        content: @escaping (T) -> UICollectionViewCell
    ) {
        self.content = content
        super.init(
            frame: .zero,
            collectionViewLayout: collectionViewLayout
        )
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

单元测试代码

internal final class ListTests: XCTestCase {
    internal func testInitList() {
        let listNode = ListMock<ListNodeTestModel> { model in
            model.generateListCellNode()
        }
    }
}

internal struct ListNodeTestModel: Equatable, HashDiffable {
    internal let id: Int
    internal var desc: String = ""

    internal static func == (lhs: ListNodeTestModel, rhs: ListNodeTestModel) -> Bool {
        return lhs.id == rhs.id && lhs.desc == rhs.desc
    }

    internal func generateListCellNode() -> UICollectionViewCell {
        return UICollectionViewCell()
    }
}

HashDiffable协议定义

public protocol HashDiffable {
    associatedtype IdentifierType: Hashable
    var id: Self.IdentifierType { get }
    func isEqual(to source: Self) -> Bool
}

public extension HashDiffable where Self: Hashable {
    /// The `self` value as an identifier for difference calculation.
    var id: Self {
        return self
    }
}

extension HashDiffable where Self: Equatable {
    public func isEqual(to source: Self) -> Bool {
        return self == source
    }
}

问题原因

崩溃的核心原因是HashDiffable协议的关联类型IdentifierType推断出现歧义:

  • ListNodeTestModel自定义了id: Int属性,但未显式声明HashDiffable的关联类型IdentifierType。
  • 协议存在两个默认扩展:一个是当Self遵守Hashable时,id默认返回Self类型;另一个是当Self遵守Equatable时提供isEqual的默认实现。
  • 由于ListNodeTestModel未明确指定IdentifierType,Swift在解析泛型约束时出现了内部逻辑冲突,导致空指针访问(EXC_BAD_ACCESS)。

解决方案

方案1:显式声明关联类型

在ListNodeTestModel中明确指定HashDiffable的关联类型为Int,消除推断歧义:

internal struct ListNodeTestModel: Equatable, HashDiffable {
    // 显式指定关联类型
    internal typealias IdentifierType = Int
    internal let id: Int
    internal var desc: String = ""

    internal static func == (lhs: ListNodeTestModel, rhs: ListNodeTestModel) -> Bool {
        return lhs.id == rhs.id && lhs.desc == rhs.desc
    }

    internal func generateListCellNode() -> UICollectionViewCell {
        return UICollectionViewCell()
    }
}

方案2:让模型遵守Hashable协议

如果业务允许,让ListNodeTestModel遵守Hashable协议(利用Swift自动合成实现),此时协议默认扩展会正确关联IdentifierType为Self,同时避免推断冲突:

internal struct ListNodeTestModel: Equatable, Hashable, HashDiffable {
    internal let id: Int
    internal var desc: String = ""

    // Swift自动合成==和hash(into:)方法,可省略自定义实现
}

额外说明

本次崩溃的根源并非UIKit控件初始化问题,而是Swift泛型协议的关联类型推断歧义导致的底层内存访问错误,与UICollectionView本身的初始化逻辑无关。

内容的提问来源于stack exchange,提问作者Dimas Prabowo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 02:05:59