Realm Swift多对多关联对象重启App后无法持久化求助
我开发了一款从UI获取iOS联系人并创建ProductionContact对象的应用,目标是将这些联系人分配给多个已有的Shoot和Production对象。首次操作一切正常:将新对象写入Shoot和Production的productionContacts列表属性后,Realm写入数据正常,在应用内导航时数据也能保留。
重启App后,ProductionContact的所有属性和对象都完好,但Shoot和Production对象中的productionContacts列表属性为空,关联关系丢失。排查代码后确认没有其他地方修改这两个对象的productionContacts列表,想知道为何重启后这些列表属性无法持久化?
对象模型代码
class Production: Object { @objc dynamic var id: String = NSUUID().uuidString override class func primaryKey() -> String? { return "id" } // each production can have many shoots let shoots = List<Shoot>() // each Production can have many ProductionContacts var productionContacts = List<ProductionContact>() } class Shoot: Object { @objc dynamic var id: String = NSUUID().uuidString override class func primaryKey() -> String? { return "id" } // each shoot has only one production @objc dynamic var production: Production? var productions = LinkingObjects(fromType: Production.self, property: "shoots") // each shoot can have many ProductionContacts var productionContacts = List<ProductionContact>() } class ProductionContact: Object { @objc dynamic var id: String = NSUUID().uuidString // inverse of List<ProductionContact>() relationship in Shoot object var shoots = LinkingObjects(fromType: Shoot.self, property: "productionContacts") // inverse of List<ProductionContact>() relationship in Production object var productions = LinkingObjects(fromType: Production.self, property: "productionContacts") @objc dynamic var firstName: String = "" @objc dynamic var lastName: String = "" @objc dynamic var title: String = "" @objc dynamic var phone: String = "" @objc dynamic var email: String = "" @objc dynamic var url: String = "" @objc dynamic var socialPlatform: String = "" @objc dynamic var socialHandle: String = "" @objc dynamic var IMDbUrl: String = "" @objc dynamic var notes: String = "" }
核心函数代码
func fetchContactImageUrls(from contacts: [CNContact], completion: @escaping(Bool?) -> Void) { let dispatchGroup = DispatchGroup() for contact in contacts { dispatchGroup.enter() if contact.imageDataAvailable, let imageData = contact.imageData, let image = UIImage(data: imageData) { // image found, save to camera roll and update image url let identifier = contact.identifier ImageManager.instance.createAlbum(image: image) { localID in guard let localID = localID else { return } print(localID) self.imageUrls[identifier] = localID dispatchGroup.leave() } } else { // no image found dispatchGroup.leave() } } dispatchGroup.notify(queue: DispatchQueue.main, execute: { print("Fetched image urls if there were any") if self.imageUrls.count > 0 { completion(true) } else { completion(false) } }) } func createProductionContactsFromiOSContacts(_ contacts: [CNContact]) -> [ProductionContact] { var localPCs = [ProductionContact]() contacts.forEach({ let pC = ProductionContact() pC.iOSContactIDKey = $0.identifier pC.firstName = $0.givenName.isEmpty ? "No First Name" : $0.givenName pC.lastName = $0.familyName.isEmpty ? "No Last Name" : $0.familyName pC.title = $0.jobTitle.isEmpty ? "No Job Title" : $0.jobTitle pC.email = ($0.emailAddresses.first?.value as? String) ?? "" pC.phone = ($0.phoneNumbers.first?.value as? CNPhoneNumber)?.stringValue ?? "" pC.socialHandle = ($0.socialProfiles.first?.value as? CNSocialProfile)?.username ?? "" pC.socialPlatform = ($0.socialProfiles.first?.label as? String) ?? "" pC.image = imageUrls[$0.identifier] ?? "" localPCs.append(pC) }) print("created \(localPCs.count) Production Contacts from iOS contacts") return localPCs } func addContactsToRealm(_ contacts: [CNContact]) { fetchContactImageUrls(from: contacts) { listHasImages in guard let listHasImages = listHasImages else { return } self.addPCsToRealm() } } func addPCsToRealm() { do { try realm.write { self.pCs = self.createProductionContactsFromiOSContacts(contacts) realm.add(self.pCs) guard let shoot = self.selectedShoot else { return } guard let production = shoot.production else { return } shoot.productionContacts.append(objectsIn: self.pCs) production.productionContacts.append(objectsIn: self.pCs) } } catch { print("error adding contact to production or shoot. Not sure which tho.") } guard let sid = id else { return } self.reloadOnSet(id: sid) } func contactPicker(didSelect contacts: [CNContact]) { self.contacts.append(contentsOf: contacts) self.addContactsToRealm(contacts) }
问题出在Realm对象属性的定义上:Production和Shoot类中的productionContacts属性用var声明了,但Realm要求所有List类型属性必须用let声明。
List本身是一个可变容器,Realm会在对象写入数据库时接管该容器的内存管理。如果用var声明,Realm无法保证该属性的持久化——虽然内存中能看到关联关系,但重启后Realm无法正确恢复这个var指向的List实例,导致列表为空。
修改方法很简单,把两个类中的productionContacts属性改成let:
// Production类中修改 let productionContacts = List<ProductionContact>() // Shoot类中修改 let productionContacts = List<ProductionContact>()
另外补充一个潜在注意点:确保addPCsToRealm函数中的selectedShoot和production是Realm中的持久化对象(不是游离态),不过从你描述的首次操作正常来看,这两个对象的获取是没问题的,所以核心问题还是var声明List的问题。
修改后重新运行,重启App后关联关系就能正常持久化了。
内容的提问来源于stack exchange,提问作者MischkaTheBear

