When saving an NSArray to a transformable Core Data attribute, the object will not be available for access on the subsequent fetch of its entity. However, it is available on any fetch after that. What's going on?
I can set and save the Core Data entity and its attributes from one place in my iOS app. Then I go to read the most recently saved entity. All of the attributes except the transformable NSArrays are available. For some reason the arrays show up as empty (when printed in the log it looks like this: route = "(\n)"
. If the app closes and then opens again, the attribute is no longer empty. Any ideas?
I understand that saving an NSArray to a transformable attribute is not the best practice. Could you explain why this happens?
Update 1
The NSArray is filled with CLLocation objects.
There are no errors or warnings printed in the console. Nor are their any compiler warnings or errors.
Update 2
Below is an XCTest I wrote for this issue. The test does not fail until the very last assertion (as expected).
- (void)testRouteNotNil {
// This is an example of a performance test case.
NSMutableArray *route;
for (int i = 0; i < 500; i++) {
CLLocation *location = [[CLLocation alloc] initWithLatitude:18 longitude:18];
[route addObject:location];
}
NSArray *immutableRoute = route;
// Save the workout entity
// Just use placeholder values for the XCTest
// The method below works fine, as the saved object exists when it is fetched and no error is returned.
NSError *error = [self saveNewRunWithDate:@"DATE01" time:@"TIME" totalSeconds:100 distance:[NSNumber numberWithInt:100] distanceString:@"DISTANCE" calories:@"CALORIES" averageSpeed:[NSNumber numberWithInt:100] speedUnit:@"MPH" image:[UIImage imageNamed:@"Image"] splits:route andRoute:immutableRoute];
XCTAssertNil(error);
// Fetch the most recently saved workout entity
RunDataModel *workout = [[[SSCoreDataManager sharedManager] fetchEntityWithName:@"Run" withSortAttribute:@"dateObject" ascending:NO] objectAtIndex:0];
XCTAssertNotNil(workout);
// Verify that the fetched workout is the one we just saved above
XCTAssertEqual(workout.date, @"DATE01");
// Check that the any non-NSArray object stored in the entity is not nil
XCTAssertNotNil(workout.distance);
// Check that the route object is not nil
XCTAssertNotNil(workout.route);
}
Update 3
As you can see below, this is how the Core Data model is setup in Xcode. The route attribute is selected. Note that I have tried it both with and without the transient property. Do I need to add a Value Transformer Name
, what is that?
Update 4
The Core Data management code itself comes from my GitHub repo, SSCoreDataManger (which works well to my knowledge).
Here is the saveNewRunWithDate
method:
- (NSError *)saveNewRunWithDate:(NSString *)date time:(NSString *)time totalSeconds:(NSInteger)totalSeconds distance:(NSNumber *)distance distanceString:(NSString *)distanceLabel calories:(NSString *)calories averageSpeed:(NSNumber *)speed speedUnit:(NSString *)speedUnit image:(UIImage *)image splits:(NSArray *)splits andRoute:(NSArray *)route {
RunDataModel *newRun = [[SSCoreDataManager sharedManager] insertObjectForEntityWithName:@"Run"];
newRun.date = date;
newRun.dateObject = [NSDate date];
newRun.time = time;
newRun.totalSeconds = totalSeconds;
newRun.distanceLabel = distanceLabel;
newRun.distance = distance;
newRun.calories = calories;
newRun.averageSpeed = speed;
newRun.speedUnit = speedUnit;
newRun.image = image;
newRun.splits = splits; // This is also an issue
newRun.route = route; // This is an issue
return [[SSCoreDataManager sharedManager] saveObjectContext];
}
And below is the RunDataModel
NSManagedObject Interface:
/// CoreData model for run storage with CoreData
@interface RunDataModel : NSManagedObject
@property (nonatomic, assign) NSInteger totalSeconds;
// ...
// Omitted most attribute properties because they are irrelevant to the question
// ...
@property (nonatomic, strong) UIImage *image;
/// An array of CLLocation data points in order from start to end
@property (nonatomic, strong) NSArray *route;
/// An array of split markers from the run
@property (nonatomic, strong) NSArray *splits;
@end
In the implementation these properties are setup using @dynamic