CoreData可选Integer16属性转Int?及#keyPath使用疑问
这个场景我太熟悉了——CoreData里的可选数值属性默认是NSNumber?,但Swift里我们更想用Int?这类原生可选类型,自定义访问器确实能让代码更清爽,但#keyPath的使用确实会因为ObjC的兼容性卡壳,给你几个实用的解决办法:
1. 保留CoreData原始属性,用#keyPath指向它
CoreData的实体模型里定义的那个Integer 16可选属性,本质上在ObjC层面是NSNumber?类型,这个是#keyPath唯一能识别的“官方”属性。你的自定义访问器只是Swift层的包装,所以#keyPath依然要指向原始属性名:
比如你的实体叫MyEntity,CoreData模型里的属性是optionalCount(类型Integer 16,可选),那么ManagedObject子类里的自定义访问器可以这么写:
extension MyEntity { // Swift层的友好访问器 var optionalCountInt: Int? { get { return optionalCount?.int16Value.map(Int.init) } set { optionalCount = newValue.map(Int16.init).map(NSNumber.init) } } // 用#keyPath获取原始属性的键路径,封装成静态常量方便复用 static let optionalCountKey = #keyPath(MyEntity.optionalCount) }
之后你在需要键路径的地方,直接用MyEntity.optionalCountKey就行——既享受了#keyPath的编译时检查,又不用重复写原始属性名。
2. 不要试图让自定义Swift属性被#keyPath识别
很多人会想给自定义的Int?属性加@objc标记,让它暴露给ObjC,然后用#keyPath(MyEntity.optionalCountInt),但这行不通:
CoreData的实体模型里并没有
optionalCountInt这个属性,#keyPath虽然能编译通过,但运行时CoreData找不到对应的实体属性,会直接崩溃。自定义访问器只是Swift层的逻辑包装,并没有和CoreData的持久化模型绑定,所以不能用它的名字生成键路径。
3. 特殊场景下的替代方案
如果是在NSPredicate这类场景中使用,直接用原始属性的#keyPath配合NSNumber值即可,比如:
let predicate = NSPredicate(format: "%K == %@", MyEntity.optionalCountKey, NSNumber(value: 10))
虽然用到了NSNumber,但因为封装了键路径常量,代码依然很整洁。
总的来说,核心原则就是:#keyPath只能指向CoreData实体模型中定义的、ObjC可见的属性,自定义的Swift访问器只是语法糖,不能作为键路径的目标。通过封装原始属性的#keyPath常量,既能保持代码的可读性,又能避免编译和运行时的问题。
内容的提问来源于stack exchange,提问作者ad-johnson

