今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

Usage of MagicalRecord

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:

image1

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);

image2

Next, the 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.
image3

image4

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, while 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. However, when I was splitting into a complete MVC, I always hoped that I could directly store the model and retrieve it as a model for direct use, instead of assigning values to each attribute again. So I wanted to try CoreData; but the native version was 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 will share 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 rootViewController of the window
    [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,

image5

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 after
    NSMutableArray *resultArray = [NSMutableArray arrayWithObjects: sendArray, receiveArray, nil];
    return resultArray;
}

References#

  1. In-depth MagicalRecord, this blog explains in detail, from CoreData to MagicalRecord.

  2. RayWenderlich's MagicalRecord Tutorial, this tutorial can be followed along for practice.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.