1

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

我相信我的问题是导入没有发生在同一个线程上。所以我认为正在发生的事情是:

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

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

我的最终目标是让整个下载/导入过程在后台完成,根本不影响 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为我的后台线程,但这似乎并没有解决所有问题,我仍然有一些关系到故障对象,尽管我认为我确实需要这样做以使一切都在后台线程中进行。

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

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

更新

如果您需要更多代码,我创建了一个包含(我相信)所有内容的要点。

4

2 回答 2

2

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

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

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

我还看到您正在保存处理程序中进行导入。这不是必需的。做你的MR_importFromArray将自动保存。如果您不使用MR_import方法,那么您将使用saveToPersistentStore方法。

一件事是我看不到你在哪里关联这些关系。与通过 JSON 发送与对象存在EntityB的关系是否存在?EntityAEntityAEntityB

如果是这样,那么这就是关系变得混乱的地方,因为它正在创建/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);
    }
}];
于 2013-10-18T17:03:36.287 回答
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);
    }];
}
于 2013-09-05T22:57:19.713 回答