初始化带逃逸闭包的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
相关产品推荐
相关产品推荐

