Creating a Database#
1. First, analyze what you need#
My goal is to cache the letter list interface, and the model for the new list interface is letter, so I need an entity for Letter. What attributes does this Letter have? The sender (sender), the content of the letter (content), the time of the letter (dateString), the read/unread status (isRead), and whether it is incoming or outgoing (incoming); so after creating my Letter entity, it looks like this:
Is that enough? Not really. If it were a normal display interface, having just the letter entity would be sufficient; however, for my project, which displays letters and is quite personal, the cached letter list should only be visible to me. What if someone else logs into my phone? Since I haven't made a distinction, when they log in, they can see my data because the data is cached on the phone!
Therefore, I need a User entity. The purpose of this User entity is to bind with Letter, ensuring that everyone sees what they should see; the user has two attributes: account and writeName (normally it should be uid, but mine is simpler, so there is no uid);
The next question is the relationship between Letter and User: is it one-to-one or one-to-many? Each letter should have one user and only one user, but one user should have many letters. When I retrieve letters from the database, I am actually looking them up using the user. If the relationship between user and letter is one-to-one, I can only retrieve one letter, which is clearly incorrect.
Conclusion: My database has two entities, one is Letter and the other is User; the relationship type between Letter and User is one-to-one, and the relationship type between User and Letter is one-to-many;
2. Then import MagicalRecord#
I have always used FMDB for caching data and have never used CoreData, but when I was splitting into a complete MVC, I always hoped I could directly store the model and retrieve it as a model for direct use, rather than assigning values to each attribute again. So I wanted to try CoreData; however, the native version is too complex, so I chose MagicalRecord.
Usage of MagicalRecord: There are only method usages on GitHub, but I couldn't find a demo, so I am sharing how I used it for reference.
In the pch file, import the header file
a. Initialize in applicationDidFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[MagicalRecord setupAutoMigratingCoreDataStack];
// Set the window's rootViewController
[self setupWindowRootViewController];
return YES;
}
b. Call the cleanup method when the application stops
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[MagicalRecord cleanUp];
}
c. Generate model classes
Select the .xcdatamodeld file, CMD+N,
Check all, then generate the model classes.
d. Save data after a successful network request
// Save letter to the database
- (void)saveLetterWithLetterEntity:(LetterEntity *)tempLetter {
// MagicalRecord save method, not on the main thread
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// First, query by pkNumber to get the letter
// If the letter does not exist, create it
// Assign and save, note that user.letters is NSSet because
Letter *letter = [Letter MR_findFirstByAttribute:@"pkNumber" withValue:tempLetter.pkNumber inContext:localContext];
if (!letter) {
// Create letter
letter = [Letter MR_createEntityInContext:localContext];
User *user = [User MR_createEntityInContext:localContext];
letter.user = user;
}
letter.dateString = tempLetter.letterDateString;
letter.content = tempLetter.letterContent;
letter.sender = tempLetter.letterSender;
letter.pkNumber = tempLetter.pkNumber;
letter.incoming = [NSNumber numberWithBool:tempLetter.incoming];
// Incoming letters are unread by default, outgoing letters are read by default
if (tempLetter.incoming) {
// Incoming letter
letter.isRead = [NSNumber numberWithBool:NO];
}
else {
letter.isRead = [NSNumber numberWithBool:YES];
}
letter.user.writeName = [[NSUserDefaults standardUserDefaults] objectForKey:k_WRITENAME];
letter.user.account = [[NSUserDefaults standardUserDefaults] objectForKey:k_USERNAME];
if (_letters == nil) {
_letters = [NSMutableArray array];
}
[_letters addObject:letter];
letter.user.letters = [NSSet setWithArray:_letters];
} completion:^(BOOL contextDidSave, NSError *error) {
DLog(@"=-===%@", (contextDidSave ? @"saveSuccessed" : @"saveFailure"));
}];
}
e. Retrieve data from the database when the network fails
- (NSMutableArray *)lettersFromDataBase {
NSMutableArray *receiveArray = [NSMutableArray array];
NSMutableArray *sendArray = [NSMutableArray array];
NSString *account = [[NSUserDefaults standardUserDefaults] objectForKey:k_USERNAME];
User *user = [[User MR_findByAttribute:@"account" withValue:account] firstObject];
// NSPredicate *receivePredicate = [NSPredicate predicateWithFormat:@"incoming == %@ && user == %@", [NSNumber numberWithBool:YES], user];
// NSPredicate *sendPredicate = [NSPredicate predicateWithFormat:@"incoming == %@ && user == %@", [NSNumber numberWithBool:NO], user];
// receiveArray = [NSMutableArray arrayWithArray:[Letter MR_findAllWithPredicate:receivePredicate]];
// sendArray = [NSMutableArray arrayWithArray:[Letter MR_findAllWithPredicate:sendPredicate]];
NSArray *userLetters = [Letter MR_findByAttribute:@"user" withValue:user];
if (userLetters) {
for (int i = 0; i < userLetters.count; i++) {
Letter *tempLetter = userLetters[i];
LetterEntity *tempEntity = [[LetterEntity alloc] init];
tempEntity.letterContent = tempLetter.content;
tempEntity.letterDateString = tempLetter.dateString;
tempEntity.letterSender = tempLetter.sender;
tempEntity.pkNumber = tempLetter.pkNumber;
tempEntity.incoming = [tempLetter.incoming boolValue];
if ([tempLetter.incoming boolValue]) {
[receiveArray addObject:tempEntity];
}
else {
[sendArray addObject:tempEntity];
}
}
}
// The order here cannot be wrong, sendArray first, receiveArray later
NSMutableArray *resultArray = [NSMutableArray arrayWithObjects: sendArray, receiveArray, nil];
return resultArray;
}
References#
-
In-depth MagicalRecord, this blog explains in detail, from CoreData to MagicalRecord.
-
RayWenderlich's MagicalRecord Tutorial, this is a tutorial that you can follow along with to practice.