今是昨非

今是昨非

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

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

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.
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, 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,

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 later
    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 is a tutorial that you can follow along with to practice.

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