使用MagicalRecord + AFNetworking导入

时间:2013-08-30 17:22:54

标签: core-data afnetworking magicalrecord

我正在使用AFNetworking和MagicalRecord(当前的开发分支),我正在试图找出如何导入大量相互依赖的对象。每个资源/实体都有多个下载页面。我有一个类管理给定实体的下载并使用MagicalDataImport保存它们(这一直很棒)。

我认为我的问题是导入不会发生在同一个线程上。所以我认为发生的事情是:

  • 在一个帖子中,EntityA正在妥善保存并传播到父实体。
  • 然后在另一个线程中,EntityB正在被保存,并且与它一起构建它与EntityA的关系。这意味着正在创建一个空白(故障?)对象。然后当它传播到父实体时,我相信EntityA会覆盖那里的EntityA。因此,我留下了一些没有所有属性的对象。

至少,我认为正在发生的事情。我通过UI看到的实际上是实体之间的关系并不总是正确构建。

我的最终目标是让整个下载/导入过程在后台完成,而不会影响用户界面。

这是我的AFJSONRequest:

AFJSONRequestOperation *operation = [AFJSONRequestOperation
     JSONRequestOperationWithRequest:request
     success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
     {
         [self saveResources:[JSON objectForKey:@"data"]];
     }
     failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
     {
         DLog(@"%@",error.userInfo);
         [self.webService command:self didFail:error.localizedDescription];
     }];

[operation setQueuePriority:self.priority];

它调用saveResources:

- (void)saveResources:(NSArray*)resources {
    BOOL stopDownloads = [self stopDownloadsBasedOnDate:resources];
    if ([resources count] > 0 && !stopDownloads){
        self.offset = @([offset intValue] + [resources count]);
        [self send];
    }

    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *blockLocalContext) {
        [self.classRef MR_importFromArray:resources inContext:blockLocalContext];
    } completion:^(BOOL success, NSError *error) {
        if (error){
            // ... handle errors
        }
        else {
            // ... handle callbacks
        }
    }];
}

这将启动另一次下载([self send]),然后保存对象。

我知道默认AFNetworking在主队列中调用回调,我尝试将SuccessCallbackQueue / FailureCallbackQueue设置为我的后台线程,但这似乎并没有解决所有问题,我仍然有一些关系会发生故障的对象,但我认为我确实需要这样做以保持所有内容都在后台线程中。

为了将这些更改正确地传播到主要上下文,还有什么我需要调用的吗?或者是否有不同的方法我需要设置它以确保正确保存所有对象并正确构建关系?

更新 我已经重写了这个问题,试图对问题进行更多的澄清。

更新

如果您需要更多代码我created a gist(我相信)所有内容。

2 个答案:

答案 0 :(得分:2)

几天前我最终遇到了同样的问题。我的问题是我收到了来自我的API和AFNetworking的客户记录。该客户可能有宠物,但此时我没有petTypes对应客户的宠物记录。

我为解决这个问题所做的是使用NSArray创建一个可转换的属性,该属性会临时存储我的宠物,直到我的petTypes被导入。在输入petTypes后,我触发了NSNotificationCenter postNotification(或者您可以在完成时进行宠物导入)。

我枚举了存储我的宠物记录的临时可转换属性,然后将其与petType

相关联

此外,我发现你正在保存处理程序中进行导入。这不是必需的。执行MR_importFromArray将自动保存。如果您没有使用MR_import方法,那么您将使用saveToPersistentStore方法。

有一件事是我没有看到你在哪里关联关系。 EntityBEntityA的关联EntityA是否与EntityB的{​​{1}}对象存在,EntityA是否与EntityB的关系?

如果是这样,那么这就是关系变得混乱的地方,因为它正在创建/覆盖NSArray *petFactors = [responseObject valueForKeyPath:@"details.items"]; NSManagedObjectContext *currentContext = [NSManagedObjectContext MR_context]; Pets *pet = [Pets MR_findFirstByAttribute:@"id" withValue:petId inContext:currentContext]; pet.petFactors = nil; for (id factor in petFactors) { [pet addPetFactorsObject:[PetFactors MR_findFirstByAttribute:@"id" withValue:[factor valueForKey:@"factorId"]]]; } [currentContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) { if (success) { NSLog(@"SAVED PET FACTORS"); [[NSNotificationCenter defaultCenter] postNotificationName:kPetFactorsSavedSuccessfully object:nil]; } else { NSLog(@"Error: %@", error); } }]; 中提供的现有{{1}}。我的建议是做这样的事情。

{{1}}

答案 1 :(得分:1)

我将此作为答案,但我不能100%确定这是否是您的问题。我认为问题源于您的localContext。以下是我们编写的使用数据导入的应用程序的示例Web请求方法,您可以使用它作为示例来使您的工作。

请注意,AFNetworking在主线程上执行完成块,然后MagicalRecord saveInBackground方法切换回后台线程进行导入和处理,然后最终的MR完成块再次在主线程上执行处理程序块。用于导入的localContext由saveInBackground方法创建/管理。该方法完成后,将保存上下文并与应用程序的主要上下文合并,然后可以访问所有数据。

- (void)listWithCompletionHandler:(void (^)(BOOL success))handler{
    [[MyAPIClient sharedClient] getPath:@"list.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject){
        NSString *statusString = [responseObject objectForKey:@"status"];

        // Handle an error response
        if(![statusString isKindOfClass:[NSString class]] || ![statusString isEqualToString:@"success"]){
            // Request failure
            NSLog(@"List Request Error: %@", statusString);
            NSLog(@"%@", [responseObject objectForKey:@"message"]);
            if(handler)
                handler(NO);
            return;
        }

        NSArray *itemsArray = [responseObject objectForKey:@"items"];

        [MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
            // Load into internal database
            NSArray *fetchedItems = [Item importFromArray:itemsArray inContext:localContext];

            NSLog(@"Loaded %d Items", [fetchedItems count]);
        } completion:^{
            if(handler)
                handler(YES);
        }];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error){
        NSLog(@"Fail: %@", error);
        if(handler)
            handler(NO);
    }];
}