jjzjj

ios - 在validateForInsert中执行获取请求过于昂贵

coder 2024-01-24 原文

我最近在我的核心数据模型中进行了重构,并且正在从这里使用多层托管对象上下文模型:http://www.cocoanetics.com/2012/07/multi-context-coredata/

我已经成功地隔离了所有核心数据解析,以便将新的托管对象解析并插入到后台线程的子MOC中,并且这些更改最终将批量保存到父/主MOC中,然后最终写入持久存储协调员通过其父/写者MOC。

由于以前在父/主MOC上进行了大批量写入并锁定了UI线程,因此这在某种程度上显着改善了我的UI响应能力。

我想进一步改善我们的对象插入和验证。每次打开应用程序时,都会以一定的规则间隔打开一个配置文件请求,在此期间,将使用新值向下发送数十或数百个对象。我选择只为所有这些对象创建NSManagedObjects,将它们插入到子MOC中,并允许进行验证以消除重复项。

我的问题是,是否在每次对NSFetchRequest的调用中都执行validateForInsert:对于NSManagedObject来说代价很高。我在StackOverflow上看到了几个似乎正在使用此模式的答案,例如:https://stackoverflow.com/a/2245699/2184893。我要执行此操作而不是在创建实体之前进行验证,因为如果两个线程同时在同一时间创建同一对象,则会同时创建两个对象,并且验证必须在父线程的插入/合并时进行。

那么,使用这种方法是否昂贵?这是惯例吗?另外,使用validateForInsertvalidate有区别吗?

-(BOOL)validateUniqueField:(id *)ioValue error:(NSError * __autoreleasing *)outError{

    // The property being validated must not already exist

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([self class])];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueField == %@", *ioValue];

    int count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
    if (count > 0) {
        if (outError != NULL) {
            NSString *errorString = NSLocalizedString(
                                                      @"Object must have unique value for property",
                                                      @"validation: nonunique property");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
            *outError = [[NSError alloc] initWithDomain:nil
                                                   code:0
                                               userInfo:userInfoDict];
        }
        return NO;
    }
    return YES;
}

例如,如果我异步请求一个区域中的所有用户,这些区域中的两个重叠并且大约在同一时间给我相同的用户对象,那么我可能会使用多个线程来创建同一对象的用例使用自己的context. findOrCreate的同一用户将无法验证该对象是否已在其他线程/上下文中创建。目前,我正在通过检查validateForInsert来处理此问题。

最佳答案

正在获取验证方法?
您的问题很聪明,因为它隐藏了几个问题!
那么,使用这种方法是否昂贵?
这可能非常昂贵,因为在保存过程中,您至少要对要验证的每个对象进行提取(在保存过程中会自动调用验证)。
这是惯例吗?
我真的希望不要!我以前只看过一次,结果却不好(继续阅读)。
另外,使用validateForInsert和validate是否有区别?
我不确定您在这里的意思。受管对象具有以下验证方法:validateForInsertvalidateForUpdatevalidateForDelete。它们每个都执行自己的规则,并为各个属性调用validateValue:forKey:error:,依次调用validate<Key>:error:模式的任何实现。例如,validateForInsert将在调用其他验证方法之前执行托管对象模型中定义的任何插入验证规则(例如,将模型属性在模型编辑器中标记为非可选是插入验证规则)。
保存上下文时会自动调用验证,但是您可以随时调用它。如果您想显示必须纠正以完成保存的用户错误等,这将很有用。
也就是说,请继续阅读以获取您似乎要解决的问题的解决方案。
关于在验证方法中获取...
在验证方法中访问对象图是不明智的。执行提取时,您正在该上下文中更改对象图-访问对象,引发错误等。保存过程中会自动进行验证,并且此时会更改内存中的对象图-即使您没有直接更改属性值-可能会有一些戏剧性且难以预测的副作用。这不是快乐的时光。
唯一性的正确解决方案:查找或创建
您似乎在尝试确保托管对象是唯一的。核心数据没有为此提供内置机制,但是有一种建议的实现方式:“查找或创建”。这是在访问对象时完成的,而不是在验证或保存对象时完成的。
确定是什么使该实体唯一。这可以是单个属性值(在您的情况下,它似乎是单个属性),也可以是多个属性的组合(例如,“firstName”和“lastName”一起使“person”具有唯一性)。基于该唯一性条件,您可以在上下文中查询现有的对象匹配项。如果找到匹配项,则返回它们,否则使用这些值创建一个对象。
这是一个基于您问题代码的示例。这将使用“uniqueField”的值作为唯一性标准,显然,如果您拥有多个使您的实体唯一的属性,这将变得更加复杂。
例:

// I am using NSValue here, as your example doesn't indicate a type.
+ (void) findOrCreateWithUniqueValue:(NSValue *)value inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext completion:(void (^)(NSArray *results, NSError *error))completion {

    [managedObjectContext performBlock:^{
        NSError             *error      = nil;
        NSEntityDescription *entity     = [NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:managedObjectContext];
        NSFetchRequest *fetchRequest    = [[NSFetchRequest alloc] init];
        fetchRequest.entity = entity;
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueField == %@", value];

        NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
        if ([results count] == 0){
            // No matches found, create a new object
            NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:managedObjectContext];
            object.uniqueField = value;
            results = [NSArray arrayWithObject:object];
        }

        completion(results, error);
    }];

}
这将成为您获取对象的主要方法。在问题描述的场景中,您会定期从必须应用于托管对象的某个来源获取数据。使用以上方法,该过程将类似于...。
[MyEntityClass findOrCreateWithUniqueValue:value completion:^(NSArray *results, NSError *error){
    if ([results count] > 0){
        for (NSManagedObject *object in results){
            // Set your new values.
            object.someValue = newValue;
        }
    } else {
        // No results, check the error and handle here!
    }
}];
可以高效,高效地完成并具有适当的数据完整性。如果您愿意接受内存不足的问题,则可以在获取实现中使用批处理错误等。对所有传入数据执行上述操作后,即可保存上下文,并将对象及其值有效地推送到父存储。
这是使用Core Data实现唯一性的首选方法。这是在Core Data Programming Guide中非常简短地提到的。
对此进行扩展...
必须进行“批量”查找或创建操作并不少见。在您的方案中,您将获取需要应用于托管对象的更新列表,如果不存在新对象,则创建它们。显然,上面的示例“查找或创建”方法可以做到这一点,但是您也可以更有效地做到这一点。
核心数据的概念是“批量故障”。如果您知道要使用多个对象,那么可以一次批处理所有对象,而不是在访问每个对象时分别对每个对象进行故障处理。这意味着更少的磁盘访问次数和更好的性能。
批量查找或创建方法可以利用此优势。请注意,由于所有这些对象现在都将“触发”故障,因此将使用更多的内存,但不会比在每个对象上调用上述单个查找或创建时要多。
我将重复以下所有方法,而不是重复所有前面的方法:
 // 'values' is a collection of your unique identifiers.
+ (void) findOrCreateWithUniqueValues:(id <NSFastEnumeration>)values inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext completion:(void (^)(NSArray *results, NSError *error))completion {
    ...
    // Effective use of IN will ensure a batch fault
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"SELF.uniqueField IN %@", values];
    // returnsObjectsAsFaults works inconsistently across versions.
    fetchRequest.returnsObjectsAsFaults = NO;
    ...
    NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    // uniqueField values we initially wanted
    NSSet   *wanted = [NSSet setWithArray:values];
    // uniqueField values we got from the fetch
    NSMutableSet    *got    = [NSMutableSet setWithArray:[results valueForKeyPath:@"uniqueField"]];
    // uniqueField values we will need to create, the different between want and got
    NSMutableSet    *need   = nil;

    if ([got count]> 0){
        need = [NSMutableSet setWithSet:wanted];
        [need minusSet:got];
    }

    NSMutableSet *resultSet = [NSMutableSet setWithArray:fetchedResults];
    // At this point, walk the values in need, insert new objects and set uniqueField values, add to resultSet
    ...
    // And then pass [resultSet allObjects] to the completion block.

}
对于同时处理多个对象的任何应用程序而言,有效使用批处理故障可能会极大地促进其发展。与往常一样,用仪器进行轮廓分析。不幸的是,在不同的核心数据版本之间,故障行为已发生很大变化。在较早的发行版中,使用托管对象ID进行额外的获取更为有益。你的旅费可能会改变。

关于ios - 在validateForInsert中执行获取请求过于昂贵,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25944579/

有关ios - 在validateForInsert中执行获取请求过于昂贵的更多相关文章

  1. 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

  2. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  3. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  4. 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

  5. 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返回它复制的字节数,但是当我还没有下

  6. 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

  7. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  8. 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

  9. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  10. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

随机推荐