Ios 如何获取在NSManagedObjectContext中获取的模型对象的快照,以便在释放上下文后这些对象可用?

Ios 如何获取在NSManagedObjectContext中获取的模型对象的快照,以便在释放上下文后这些对象可用?,ios,core-data,Ios,Core Data,我对编写一个方法感兴趣,根据对文档名称的查询,该方法返回类Document的对象 Document发生在子类NSManagedObject上,但消费者并不真正关心这一点。文档对象在方法内部已成功出错,其属性可访问。但是,一旦该方法被保留,并且用于获取的NSManagedObjectContext被释放,该对象再次变为故障 有没有办法将NSManagedObject与其上下文分离,这样它就不会变成错误?当然,这不允许保存对对象的更改,但我对此不感兴趣 我不想诉诸以下任何一种方法: 编写反映文档的非

我对编写一个方法感兴趣,根据对文档名称的查询,该方法返回类
Document
的对象

Document
发生在子类
NSManagedObject
上,但消费者并不真正关心这一点。
文档
对象在方法内部已成功出错,其属性可访问。但是,一旦该方法被保留,并且用于获取的
NSManagedObjectContext
被释放,该对象再次变为故障

有没有办法将
NSManagedObject
与其上下文分离,这样它就不会变成错误?当然,这不允许保存对对象的更改,但我对此不感兴趣

我不想诉诸以下任何一种方法:

  • 编写反映
    文档的
    非管理文档
  • 只要我对文档感兴趣,就使用
    NSManagedObjectContext
    标记。消费者不在乎,也不必知道
    文档
    子类
    NSManagedObject
  • 注意:


    我想补充一点,即使我能够将
    文档
    保留在
    NSManagedObjectContext
    之后,这也不是一个好的解决方案,因为消费者可以作为
    NSManagedObjectContext
    子类访问
    文档
    的接口,这是不可取的,因为它可能导致意外的bug。但是,手动为数据库中存储的每个模型对象编写冻干包装的替代方法非常不吸引人。

    NSManagedObject
    s与核心数据堆栈紧密绑定。实体不一定在内存中完全加载,关系也不一定完全加载。那么,据我所知,没有支持的方式来满足你的要求

    你很好地描述了你的备选方案。很遗憾听到你讨厌他们

    也就是说,可以在运行时生成镜像类,而不必手动编写和维护每个实体。ObjC运行时允许我们做一些奇特的事情


    编辑:指向NSDictionary快照的一些代码段

    NSManagedObject* any; // let's imagine you have an instance at hand
    NSAttributeDescription* entityDescription = any.entity;
    
    // Attributes
    NSDictionary* attributeDescriptions = entityDescription.attributesByName;
    for ( NSAttributeDescription* attributeDescription in attributeDescriptions.allValues )
    {
        NSString* attributeName = attributeDescription.name;
        NSAttributeType attributeType = attributeDescription.attributeType;
        Class attributeClass = NSClassFromString( attributeDescription.attributeValueClassName );
    
        …
        id attributePrimeValue = [any primitiveValueForKey: attributeName];
        id attributeValue = [any valueForKey: attributeName];
        …
    
    }
    
    // Relationships
    NSDictionary* relationshipDescriptions = entityDescription.relationshipsByName;
    for ( NSRelationshipDescription* relationshipDescription in relationshipDescriptions.allValues )
    {
        …
    }
    

    以此为起点,您应该能够迭代属性,获取它们的名称、类型和值,并创建基于NSDictionary的快照。关系的迭代方式是相同的,但当然,您将如何处理关系与您的需求紧密相关。

    为什么不隐藏
    NSManagedObjectContext
    ?您的消费者可能不关心is是
    NSManagedObject
    ,但他们不需要使用
    NSManagedObject上下文,这是您的实现细节。您不需要公开NSManagedObjectContext,只需对所有文档使用一个即可

    我不确定您为什么要取消分配托管对象上下文。为什么不在app delegate中设置核心数据堆栈,以便您可以从程序中的任何位置访问NSManagedObjects

    如果需要执行后台任务,请确保在主线程(应用程序内委托)上创建一个NSManagedObjectContext,并在后台线程中创建另一个NSManagedObjectContext


    你可能想查看魔法记录(https://github.com/magicalpanda/MagicalRecord/)帮助大大简化核心数据的使用,并在多线程上正确管理上下文。

    我真的不理解您的问题。如果您公开了一个从NSManagedObject继承的类,那么该类的用户将面临该强制以及伴随的问题

    如果将类作为组合公开,则托管对象的详细信息就在类中,并且这些详细信息很容易隐藏

    你当然可以在你想要的时候创建/销毁MOC,它们非常便宜。然而,这似乎不是非常有益的

    我建议保留用于接口的单个托管对象上下文。您提到担心多个线程调用MOC。这是一个值得关注的问题,但通过使用带有NSPrivateQueueConcurrencyType的MOC可以轻松有效地解决

    然后,无论何时需要获取对象,只需执行以下操作即可

    [moc performBlockAndWait:^{
        // Fetch the object from the MOC
    }];
    
    调用同步版本时要小心,因为可能会出现死锁

    或者,您仍然可以使用为每个提取创建唯一上下文的选项

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
    moc.parentContext = myPrivateQueueContext;
    // Now, you can fetch from this MOC, and do what you want with the object
    // and when you are done, you just let the MOC dealloc.
    
    您可能需要查看NSFetchRequest的选项,以及MOC本身的设置

    refreshObject:mergeChanges:
    retainsRegisteredObjects
    
    如果您告诉myPrivateQueueContext保留对象,它会将它们保留在其本地MOC中。然后,当您执行fetch时,您可以告诉fetch更喜欢MOC中的缓存版本,它甚至永远不会进入磁盘

    如果存储托管对象的objectID,只需调用objectRegisteredForID:即可查找已注册的对象,则可以获得更多优势。 谁否决了答案 然后,您可以手动清理缓存,这样它就不会变得太大,如果需要的话

    或者,您可以调用existingObjectWithID:error:这将永远不会返回错误。结果将始终是一个已实现对象或零

    不管怎样,我认为你想做的事情可以通过多种方式轻松解决。不过,我要提醒你,不要过度设计你的解决方案


    找出要公开的接口,并实现它。如果以后发现性能问题,请在该时间处理

    **编辑**

    特别是针对Danra的评论(以及否决答案的人)。我从未打算让您直接从私人MOC访问数据。如果您想访问专用主运行中心,请使用
    @implementation NSManagedObjectContext (PerThreadContext)
    
    +(NSManagedObjectContext*)contextForCurrentThread
    {
        static OSSpinLock lock = OS_SPINLOCK_INIT ;
        @try 
        {
            OSSpinLockLock( & lock ) ;
    
            NSDictionary * info = [ NSThread currentThread ].threadDictionary ;
            NSManagedObjectContext * context = [ info valueForKey:@"managedObjectContext" ] ;
            if ( !context )
            {
                context = [ [ NSManagedObjectContext alloc ] initWithConcurrencyType:NSConfinementConcurrencyType ] ;
                context.persistentStoreCoordinator = [ NSPersistentStoreCoordinator sharedCoordinator ] ; // perhaps a category we added to NSPersistentStoreCoordinator? Or use your app delegate, etc...
                [ info setObject:context forKey:@"managedObjectContext" ] ;
            }
        }
        @finally 
        {
            OSSpinLockUnlock( & lock ) ;
        }
        return context ;
    }
    
    @end
    
    -(NSManagedObject*)getSomeObject
    {
        NSManagedObjectContext * context = [ NSManagedObjectContext contextForCurrentThread ] ;
    
        NSManagedObject * result = ....code...
        return result ;
    }
    
    @interface NSManagedObject (Freezing)
    @property ( nonatomic, readonly ) BOOL frozen ;
    -(void)freeze ;
    @end
    
    @implementation NSManagedObject (Freezing)
    
    -(void)setFrozen:(BOOL)f
    {
        objc_setAssociatedObject( self, "_frozen", [ NSNumber numberWithBool:f ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
    }
    
    -(BOOL)frozen
    {
       return [ objc_getAssociatedObject( self, "_frozen" ) boolValue ] ;
    }
    
    -(void)freeze
    {
        [ self setFrozen:YES ] ;
    }
    
    -(BOOL)validateForUpdate:(NSError**)error
    {
        if ( self.frozen ) 
        { 
            if ( error ) { *error = [ NSError ... ] ; }
            return NO ; 
        }
    
        return [ super validateForUpdate:error ] ;
    }
    
    -(BOOL)validateForDelete:(NSError**)error
    {
        if ( self.frozen ) 
        { 
            if ( error ) { *error = [ NSError ... ] ; }
            return NO ; 
        }
    
        return [ super validateForDelete:error ] ;
    }
    
    @end