草庐IT

ios - 允许子NSManagedObjectContext在其父上下文保存时获取的设置

coder 2023-09-14 原文

背景

Saving a large amount of data at a time is very slow.

当前设置

在我的应用中,有一个 private 队列 NSManagedObjectContext作为,它直接与NSPersistentStoreCoordinator对话以保存数据。 UI的NSTreeController使用子主队列上下文

(我的目标是防止发生沙滩球。目前,我仅通过在应用程序处于非 Activity 状态时保存数据来解决此问题。但是由于计划删除的数据尚未删除,因此仍可能出现取得结果。这是我要解决的另一个问题。)

问题

当父上下文忙于保存时,子主队列上下文仅可在获取时等待。

相关问题

  • Core Data: parent context blocks child这也许本质上是相同的问题。我注意到答案说设置本质上是错误的。然后,我想知道是否可以使用Core Data同时写和读吗?
  • Correct implementation of parent/child NSManagedObjectContext一个常见问题缺少有见解的答案(对不起...)。
  • What is the most efficient way to delete a large number (10.000+) objects in Core Data?如果无法通过NSOutlineView指定对象,我们仍然需要依靠传统的NSPredicate(也许还有Cascade删除规则)。而且,它不能通过消除UI阻塞问题。

  • 当我有更多发现时,我将更新此问题。

    最佳答案

    我猜您正在针对OS X / macOS(NSTreeControllerNSOutlineView)进行开发。我没有使用macOS的经验-我是为iOS开发的-因此您在阅读我的回复时可能需要考虑到这一点。

    我尚未切换到快捷方式-显然,我的代码是Objective-C ...

    我将从准备核心数据堆栈开始。

    我在头文件中设置了两个 public 属性:

    @property (nonatomic, strong) NSManagedObjectContext *mocPrivate;
    @property (nonatomic, strong) NSManagedObjectContext *mocMain;
    

    尽管这是不必要的,但我也更喜欢为我的Core Data对象设置 private 属性,例如:
    @property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
    

    指向模型URL,建立托管对象模型NSManagedObjectModel,指向NSPersistentStore的商店URL并建立持久存储协调器NSPersistentStoreCoordinator(PSC)之后,就设置了两个托管对象上下文(MOC)。

    在按照上一段完成代码之后,在“构建”核心数据堆栈的方法中,然后包括以下内容:
    if (!self.mocPrivate) {
        self.mocPrivate = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [self.mocPrivate setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    } else {
        // report to console the use of existing MOC
    }
    
    if (!self.mocMain) {
        self.mocMain = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [self.mocMain setParentContext:self.mocPrivate];
    } else {
        // report to console the use of existing MOC
    }
    

    (我通常在此代码中包括几条NSLog行,以向控制台报告,但为了保持代码的干净,我将其排除在外。)

    请注意此代码的两个重要方面。
  • 设置专用队列MOC与PSC进行交互;和
  • 将主队列MOC设置为专用队列MOC的子级。

  • 为什么要这样做?首先,让我们重点强调以下几点:
  • 保存到内存的速度相对较快;和
  • 保存到光盘的速度相对较慢。

  • 专用队列与主队列异步。用户界面(UI)在主队列上运行。专用队列在“后台”的单独线程上运行,以维护上下文并与PSC协调数据持久性,由Core Data和iOS完美管理。主队列通过UI在主线程上运行。

    写了另一种方式...
  • 完成专用队列中对PSC的数据持久性(保存到磁盘)的不定期(由Core Data管理)的繁重工作;和
  • 完成对MOC的常规(由开发人员管理)数据持久性(保存到内存)的轻工作已在主队列中完成。

  • 从理论上讲,这应该确保您的UI永远不会被阻塞。

    但是,此解决方案还有更多。我们如何管理“保存”过程很重要。

    我写一个 public 方法:
    - (void)saveContextAndWait:(BOOL)wait;
    

    我从需要持久化数据的任何类中调用此方法。此 public 方法的代码:
    - (void)saveContextAndWait:(BOOL)wait {
        // 1. First
        if ([self.mocMain hasChanges]) {
        // 2. Second
            [self.mocMain performBlockAndWait:^{
                NSError __autoreleasing *error;
                BOOL success;
                if (!(success = [self.mocMain save:&error])) {
                    // error handling
                } else {
                    // report success to the console
                }
            }];
        } else {
            NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext MAIN_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
        }
    
        // 3. Third
        void (^savePrivate) (void) = ^{
            NSError __autoreleasing *error;
            BOOL success;
            if (!(success = [self.mocPrivate save:&error])) {
                    // error handling
                } else {
                    // report success to the console
            }
        };
    
        // 4. Fourth
        if ([self.mocPrivate hasChanges]) {
        // 5. Fifth
            if (wait) {
                [self.mocPrivate performBlockAndWait:savePrivate];
            } else {
                [self.mocPrivate performBlock:savePrivate];
            }
        } else {
            NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext PRIVATE_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
        }
    }
    

    因此,我将通过这一过程来解释正在发生的事情。

    我为开发人员提供了保存和等待(阻止)的选项,并且根据开发人员对saveContextAndWait:wait方法的使用, private 队列MOC使用以下任一方法“保存”:
  • performBlockAndWait方法(开发人员使用wait = TRUEYES调用方法);或
  • performBlock方法(开发人员使用wait = FALSENO调用方法)。

  • 首先,该方法检查主队列MOC是否有任何更改。除非必须,否则不要做任何工作!

    其次,该方法完成对主队列MOC上的performBlockAndWait的(同步)调用。这将在代码块中执行对save方法的调用,并在等待代码完成之前等待完成。请记住,这是对小型数据集的常规“保存”。此处不需要调用performBlock的(异步)选项,并且实际上会破坏该方法的有效性,正如我在学习在代码中实现此方法时遇到的那样(由于对主队列的save调用而导致无法持久保存数据)完成 private 队列MOC上的save后,MOC尝试完成)。

    第三,我们在一个包含保存 private 队列MOC的代码的块中写一个小块。

    第四,该方法检查专用队列MOC是否有任何更改。这可能是不必要的,但将其包含在此处无害。

    第五,根据开发人员选择实施的选项(wait = YESNO),该方法在块内的块上调用performBlockAndWaitperformBlock(在上面的中的第三个下)。

    在最后一步中,无论采用哪种实现方式(wait = YES NO),从专用队列MOC到PSC的将数据持久保存到磁盘的功能都将抽象到异步线程上的专用队列到主线程中。从理论上讲,通过PSC进行“保存到磁盘”可以花费任何时间,因为它与主线程无关。 AND,因为专用队列MOC将所有数据都存储在内存中,所以主队列MOC是更改的完全自动通知,因为它是专用队列MOC的子级。

    如果您将大量数据导入到应用程序中(我正在努力实现),那么将这些数据导入 private 队列MOC是有意义的。

    专用队列MOC在这里做了两件事:
  • 使用PSC协调数据持久性(到磁盘);
  • 因为它是主队列MOC的父级(在内存中),所以将通知主队列MOC专用队列MOC中的数据更改,并且合并由Core Data管理;

  • 最后,我使用NSFetchedResultsController(FRC)来管理我的数据提取,这些数据提取均针对主队列MOC完成。这样可以保持数据层次结构。在任一上下文中对数据集进行更改时,FRC都会更新视图。

    这个解决方案很简单! (一旦我花了几周时间围绕它绞尽脑汁,又花了几周时间完善我的代码。)

    不需要监视有关MOC合并或其他更改的通知。 Core Data和iOS在后台处理一切。

    因此,如果这对您不起作用-让我知道-在一年多以前编写此代码时,我可能已经排除或忽略了某些内容。

    关于ios - 允许子NSManagedObjectContext在其父上下文保存时获取的设置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42849062/

    有关ios - 允许子NSManagedObjectContext在其父上下文保存时获取的设置的更多相关文章

    1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    2. ruby-openid:执行发现时未设置@socket - 2

      我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

    3. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    4. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    5. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

      我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

    6. ruby - 简单获取法拉第超时 - 2

      有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

    7. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    8. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

      我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

    9. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

      我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

    10. ruby - 获取模块中定义的所有常量的值 - 2

      我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

    随机推荐