如何使用NSCoder保存RoomPlan的CapturedRoom对象?
解决RoomPlan中CapturedRoom无法通过NSCoding保存的问题
问题背景
开发房间平面图APP时,原方案用ARWorldMap搭配ARPlaneAnchors实现,切换到RoomPlan Beta版的CapturedRoom后,沿用之前基于NSCoding保存ARWorldMap的方式,抛出错误:
-[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x141c18110
原CapturedRoom编码类代码:
import RoomPlan class RoomPlanScan: NSObject, NSCoding { var capturedRoom: CapturedRoom var title: String var notes: String init(capturedRoom: CapturedRoom, title: String, notes: String) { self.capturedRoom = capturedRoom self.title = title self.notes = notes } required convenience init?(coder: NSCoder) { guard let capturedRoom = coder.decodeObject(forKey: "capturedRoom") as? CapturedRoom, let title = coder.decodeObject(forKey: "title") as? String, let notes = coder.decodeObject(forKey: "notes") as? String else { return nil } self.init( capturedRoom: capturedRoom, title: title, notes: notes ) } func encode(with coder: NSCoder) { coder.encode(capturedRoom, forKey: "capturedRoom") coder.encode(title, forKey: "title") coder.encode(notes, forKey: "notes") } }
对比之下,使用ARWorldMap的代码可正常运行:
import RoomPlan class RoomPlanScan: NSObject, NSCoding { var worldMap: ARWorldMap var title: String var notes: String init(worldMap: ARWorldMap, title: String, notes: String) { self.worldMap = worldMap self.title = title self.notes = notes } required convenience init?(coder: NSCoder) { guard let worldMap = coder.decodeObject(forKey: "worldMap") as? ARWorldMap, let title = coder.decodeObject(forKey: "title") as? String, let notes = coder.decodeObject(forKey: "notes") as? String else { return nil } self.init( worldMap: worldMap, title: title, notes: notes ) } func encode(with coder: NSCoder) { coder.encode(worldMap, forKey: "worldMap") coder.encode(title, forKey: "title") coder.encode(notes, forKey: "notes") } }
解决方案
CapturedRoom并未实现NSCoding协议,而是遵循Swift原生的Codable协议。因此需要先将CapturedRoom序列化为Data,再通过NSCoder保存该数据;解码时反向操作,先取出Data再反序列化为CapturedRoom。
修改后的代码如下:
import RoomPlan class RoomPlanScan: NSObject, NSCoding { var capturedRoom: CapturedRoom var title: String var notes: String init(capturedRoom: CapturedRoom, title: String, notes: String) { self.capturedRoom = capturedRoom self.title = title self.notes = notes } required convenience init?(coder: NSCoder) { // 先解码出CapturedRoom对应的Data guard let roomData = coder.decodeObject(forKey: "capturedRoomData") as? Data, let title = coder.decodeObject(forKey: "title") as? String, let notes = coder.decodeObject(forKey: "notes") as? String else { return nil } // 将Data反序列化为CapturedRoom guard let capturedRoom = try? JSONDecoder().decode(CapturedRoom.self, from: roomData) else { return nil } self.init( capturedRoom: capturedRoom, title: title, notes: notes ) } func encode(with coder: NSCoder) { // 将CapturedRoom序列化为Data if let roomData = try? JSONEncoder().encode(capturedRoom) { coder.encode(roomData, forKey: "capturedRoomData") } coder.encode(title, forKey: "title") coder.encode(notes, forKey: "notes") } }
原理说明
ARWorldMap是Objective-C兼容类型,实现了NSCoding协议,因此可直接通过NSCoder编码解码;CapturedRoom是纯Swift类型,仅遵循Codable协议,没有实现encodeWithCoder:方法,直接调用coder.encode(capturedRoom)会触发未识别选择器的错误;- 通过
JSONEncoder/JSONDecoder将CapturedRoom转为Data后,即可用NSCoder保存该二进制数据,同时保留原有的NSCoding结构。
内容的提问来源于stack exchange,提问作者Dennis
相关产品推荐
相关产品推荐

