神奇的记录 将大量数据保存到数据库

Posted

技术标签:

【中文标题】神奇的记录 将大量数据保存到数据库【英文标题】:Magical record Saving big amount of data to DB 【发布时间】:2014-08-03 09:48:39 【问题描述】:

我正在使用神奇的记录将大量数据保存到我的数据库中。我想将它保存在后台,但它冻结了 UI。我正在使用

保存它
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) 
     completion:^(BOOL success, NSError *error) 
    ];

所以它不应该阻塞用户界面。但我发现它出于某些原因使用主线程来合并更改或类似的东西。如果有人可以提出如何提高效率的解决方案,我将不胜感激。

我正在使用的代码:

- (void)saveRidesForUser:(User *)user fromResponseData:(id)responseData


    if (![[[responseData valueForKey:@"rides"] class] isSubclassOfClass:[NSArray class]]) 
        return;
    
    NSArray *rides = [responseData valueForKey:@"rides"];

    if (!rides)
        return;

    NSMutableArray *photoCacheArray = [[NSMutableArray alloc] init];

    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) 

        User *rideUser = [user MR_inContext:localContext];

        for (NSDictionary *rideDictionary in rides) 

                Ride *ride = [Ride MR_createEntityInContext:localContext];
                ride.title = ([rideDictionary objectForKey:@"title"]) ? [rideDictionary valueForKey:@"title"] : @"";

                NSNumber *rideTimeStamp =[rideDictionary objectForKey:@"start_date"];
                if (rideTimeStamp)
                    ride.startDate = [NSDate dateWithTimeIntervalSince1970:[rideTimeStamp doubleValue]];

                NSNumber *rideEndTimeStamp =[rideDictionary objectForKey:@"end_date"];
                if (rideEndTimeStamp)
                    ride.endDate = [NSDate dateWithTimeIntervalSince1970:[rideEndTimeStamp doubleValue]];

                ride.serverID = [NSNumber numberWithInt:[[rideDictionary objectForKey:@"id"] intValue]];
                ride.isPopular = [NSNumber numberWithBool:NO];

                if ([rideDictionary objectForKey:@"intervals"]) 
                    NSArray *intervals = [rideDictionary objectForKey:@"intervals"];

                    for (NSDictionary *intervalDictionary in intervals) 

                        RideInterval *interval = [RideInterval MR_createEntityInContext:localContext];

                        interval.startDate = [NSDate dateWithTimeIntervalSince1970:[[intervalDictionary objectForKey:@"start_date"] integerValue]];
                        interval.endDate = [NSDate dateWithTimeIntervalSince1970:[[intervalDictionary objectForKey:@"end_date"] integerValue]];

                        interval.ride = ride;

                        if ([intervalDictionary objectForKey:@"points"] && ![[[intervalDictionary objectForKey:@"points"] class] isSubclassOfClass:[NSString class]]) 
                            NSArray *points = [intervalDictionary objectForKey:@"points"];

                            for (NSDictionary *pointDict in points) 

                                NSDictionary *pointDictionary = [pointDict dictionaryByReplacingNullsWithStrings];

                                RidePoint *point = [RidePoint MR_createEntityInContext:localContext];
                                point.timestamp = [NSDate dateWithTimeIntervalSince1970:[[pointDictionary objectForKey:@"timestamp"] integerValue]];

                                point.latitude = [NSNumber numberWithDouble:[[pointDictionary objectForKey:@"latitude"] doubleValue]];
                                point.longitude = [NSNumber numberWithDouble:[[pointDictionary objectForKey:@"longitude"] doubleValue]];
                                point.distanceFromPrevious = [NSNumber numberWithDouble:[[pointDictionary objectForKey:@"distance_from_previous"] doubleValue]];
                                point.interval = interval;
                            
                        
                    
                

                if ([rideDictionary valueForKey:@"photos"] && [[[rideDictionary valueForKey:@"photos"] class] isSubclassOfClass:[NSArray class]]) 
                    NSArray *photos = [rideDictionary objectForKey:@"photos"];

                    for (NSDictionary *photoDict in photos) 

                        NSDictionary *photoDictionary = [photoDict dictionaryByReplacingNullsWithStrings];
                        RidePhoto *photo = [RidePhoto MR_createEntityInContext:localContext];

                        photo.caption = [photoDictionary valueForKey:@"caption"];

                        NSNumber *timeStamp =[photoDictionary objectForKey:@"timestamp"];
                        if (timeStamp)
                            photo.timestamp = [NSDate dateWithTimeIntervalSince1970:[timeStamp doubleValue]];

                        photo.locationLatitude = [NSNumber numberWithDouble:[[photoDictionary objectForKey:@"location_latitude"] doubleValue]];
                        photo.locationLongitude = [NSNumber numberWithDouble:[[photoDictionary objectForKey:@"location_longitude"] doubleValue]];
                        photo.serverPath = [photoDictionary objectForKey:@"url"];
                        photo.imagePath = [NSString getUUID];
                        photo.ride = ride;

                        [photoCacheArray addObject:photo];
                    
                

                ride.user = rideUser;
            

     completion:^(BOOL success, NSError *error) 

        [[NSNotificationCenter defaultCenter] postNotificationName:kLTRideServise_RoutesDownloadedNotification object:nil userInfo:nil];

    ];


提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

您是否将 MR_defaultContext 用于所有其他主线程任务?

我正在使用 saveWithBlock: 方法进行后台保存, MR_defaultContext 用于主线程操作,它工作正常。据我所知,MagicalRecord 在保存操作结束时将 localContext 与 defaultContext 合并。

示例代码:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.0 * NSEC_PER_SEC),      
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) 
        // your loop here in background thread
     completion:^(BOOL success, NSError *error) 
        dispatch_async(dispatch_get_main_queue(), ^
            // completion on main thread
        );
    ];
);

【讨论】:

是的,我就是这样做的。我保存了很多数据,这会是问题吗? 检查 UI 何时开始冻结 - 在“for”循环期间、循环之后或整个保存块之后。 用户界面在 for 循环之后立即冻结。关于如何解决这个问题的任何想法? 我已经为您添加了带有 GCD 的示例代码。这应该可以完全消除您的流程中的冻结。 不幸的是它完全一样。我读过 saveWithBlock 不应该这样使用。好吧,无论如何,我已经尝试过,但没有任何改变。还有其他解决方案吗?【参考方案2】:

[[NSNotificationCenter defaultCenter] postNotificationName:kLTRideServise_RoutesDownloadedNotification object:nil userInfo:nil];

==> 完成后检查您正在进行的处理。此通知在主线程上调用,可能是您正在进行大量处理,导致主线程忙,导致 UI 冻结。

还要检查您是否已从任何上下文注册 NSManagedObjectContextDidSaveNotification,并查看您是否正在那里进行任何处理。

【讨论】:

【参考方案3】:

我正在使用

[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:aCallback];

将上下文保存到数据库中。

它是这样记录的

/// \brief 将当前上下文中的更改异步保存到持久存储 /// \param completion 保存完成后调用的完成块。如果发生错误,该块将作为“BOOL”和“NSError”实例传递成功状态。总是在主队列上调用。 /// \discussion 在当前上下文和任何祖先上执行异步保存,直到更改被持久化到分配的持久存储。完成块将始终在主队列上调用。

这对我来说很好,没有任何冻结。

【讨论】:

以上是关于神奇的记录 将大量数据保存到数据库的主要内容,如果未能解决你的问题,请参考以下文章

将大量数据保存到核心数据会导致应用程序因内存问题而终止

Expo/React Native 可以将大量加密数据保存到 iCloud 吗?

如何使用实体框架将大量数据延迟加载到 GridView [关闭]

无法跨域传递大量数据

我正在使用大量数据进行比较..有没有办法将数据分页到数据表

如何使用 JDBC 将大量数据加载到文件中而不会耗尽内存?