多Target的SPM包加载Nib时出现类型转换失败问题
我创建了一个包含两个Target的SPM包,第二个Target依赖第一个Target,Package的描述代码如下:
let package = Package( name: "MyPackage", platforms: [.iOS(.v15)], products: [ .library( name: "MyPackage", targets: ["MyPackage"]), .library( name: "MyPackage2", targets: ["MyPackage2"]) ], dependencies: [ .package(name: "A", path: "../A"), .package(name: "B", path: "../B"), .package(name: "C", path: "../C"), .package(name: "D", path: "../D"), .package(name: "E", path: "../E"), .package(name: "F", path: "../F") ], targets: [ .target( name: "MyPackage", dependencies: [ "A", "B", "C", "D", "E", "F" ], resources: [ .process("Resources") ] ), .testTarget( name: "MyPackageTests", dependencies: [ "MyPackage" ] ), .target( name: "MyPackage2", dependencies: [ "MyPackage" ], resources: [ .process("Resources") ] ) ] )
在第一个Target中加载内部Nib文件时,出现类型转换失败问题。加载Nib的代码如下:
public final class func loadFrom(_ bundle: Bundle) -> Self { let nibName = String(describing: self) return bundle.loadNibNamed(nibName, owner: nil, options: nil)?.first as! Self }
注:Nib加载本身并未失败,打印bundle.loadNibNamed(nibName, owner: nil, options: nil)?.first能得到正确结果,但执行as! Self时失败,错误信息为:
Could not cast value of type 'X' (0x1109e0c98) to 'X' (0x103db62e8).
该问题在添加第二个Target后出现,请问原因是什么?
原因与解决方案
核心原因
这个问题的本质是同一个类被重复加载,内存中存在两个独立的类元数据实例。当你同时在Package中暴露MyPackage和MyPackage2两个产品库,且主项目同时引入这两个库时,MyPackage中的代码会被链接两次:一次直接来自MyPackage库,另一次通过MyPackage2的依赖间接引入。这就导致内存中出现两个完全不同的X类定义,Nib加载的实例属于其中一个版本,而代码中要转换的Self是另一个版本,类型转换必然失败。
解决办法
调整Package产品暴露策略:
不要将MyPackage作为独立产品对外暴露,只提供MyPackage2作为唯一对外产品,因为MyPackage2已经依赖MyPackage,内部会包含其代码。修改Package的products部分:products: [ .library( name: "MyPackage2", targets: ["MyPackage2"]) ],避免主项目重复引入:
确保主项目只引入一个产品库——要么只引入MyPackage,要么只引入MyPackage2,不要同时添加两个,从根源上避免重复链接。规范资源配置:
两个Target都配置了.process("Resources"),如果两者的Resources目录存在同名Nib文件,可能会混淆资源加载的归属。建议给每个Target的资源目录设置唯一名称(比如MyPackageResources和MyPackage2Resources),或者只在MyPackage中处理自身资源,MyPackage2仅处理专属资源。
内容的提问来源于stack exchange,提问作者Nimrod Shai

