You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:23:20