Introduction to the most complete iOS data storage methods: FMDB, SQLite3, Core Data, Plist, Preference preference settings, NSKeyed Archiver archiving

For reproducing, please indicate the address of this article: http://www.jianshu.com/p/e88880be794f

objective

The project is going to use Core Data for local data storage. It was originally intended to write only about Core Data, but now that it comes to data storage, it's just a summary of the data storage base. This article will describe the following modules.

  1. Sandbox
  2. Plist
  3. Preference preference settings
  4. NSKeyed Archiver Archiver Archives / NSKeyed Unarchiver Archives
  5. Use of SQLite3
  6. FMDB
  7. Core Data

The following is an illustration of the Core Data stack. Here is the cover image of the article. The use of Core Data will be introduced later.


Core Data

Sand box

iOS locally stored data is stored in sandboxes, and each application sandbox is relatively independent. The sandbox file structure for each application is the same, as shown in the following figure:



Sandbox catalog

Documents: iTunes backs up the directory. It is generally used to store data that needs to be persisted.

Library/Caches: Cache, iTunes does not backup the directory. When the memory is insufficient, it will be cleared. If the application is not running, it may be cleared. General storage volume is large, do not need backup of non-important data.

Library/Preference: iTunes backs up the directory and can be used to store some preferences.

tmp: iTunes does not backup this directory to save temporary data. When the application exits, it clears the data in that directory.

How do I get the path to each folder? I'm just talking about the best way.

// This method returns an array that actually has only one element in iOS, so we can use lastObject or lastObject to get the path to the Documents directory.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// Get the path of the test.plist file in the Document directory
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

There are three parameters in the above method. Let's talk about them separately.

NSDocument Directory: The first parameter represents which file to look for. It's an enumeration. Click in and see if there are many choices. In order to find the Documents directory in the sandbox directly, we usually use NSDocument Directory.

NSUserDomainMask: Also an enumeration indicating that the search scope is limited to the sandbox directory currently used. We usually choose NSUser Domain Mask.

YES (expandTilde): The third parameter is a BOOL value. The full-written form of the home directory in iOS is / User/userName, which means full-written when YES is filled in, and NO is written as'~'. We usually fill in YES.

According to the above text, you can know how to get the file path in the Library/Caches directory. Yes, that's it:

//Get the Library/Caches directory path
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; 
//Get the test.data file path in the Library/Caches directory
NSString *filePath = [path stringByAppendingPathComponent:@"test.data"];
//Get temp path
NSString *tmp= NSTemporaryDirectory();
//Get the path of the test.data file under temp
NSString *filePath = [tmp stringByAppendingPathComponent:@"test.data"];

So, if there is data that needs to be persisted for a long time in the program, choose Documents. If there is large but not important data, you can choose to give it to the Library, and temporarily useless data is of course put in temp. As for Preference, it can be used to save some settings class information. The usage of preference settings will be discussed later.

Recommend a useful set of classification tools WHKit You can get the file path directly by using the classification method.

//One line of code
NSString *filePath = [@"test.plist" appendDocumentPath];

//Using WHKit, the above line of code is equivalent to the following two lines.

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

Plist Storage

The Type of Plist file can be a dictionary NSDictionary or an array NSArray, that is, a dictionary or an array can be written directly to the file.
Types such as NSString, NSData, NSNumber, etc. can also be written directly to a file using the writeToFile:atomically: method, except that the Type is empty.

Let's take an example to see how to use Plist to store data.

//Prepare the data to be saved
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"first" forKey:@"1"];

//Get the path, you can also use the above to say that WHKit is more elegant to get the path
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//Write data
[dict writeToFile:filePath atomically:YES];

After the above code runs, a plist file is created in the Documents of the application sandbox and written to data storage.



Write to plist

The data is stored, so how to read it?

//To get the plist file path, you can also use the more elegant access path mentioned above.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

//The result of log is first, read successfully.
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"1"];
NSLog(@"%@",result);

The above code reads the plist data.

Preference preference setting

Preference settings are very convenient and fast to use. We usually use it to record some settings, such as user name, whether the switch is on or not.
Preference is used through NSUserDefaults and records settings through key-value pairs. Here's an example.

Use NSUser Defaults to determine if APP is first started.

// When starting, judge whether the key has value or not. If so, it means that it has been started. If not, it means that it is the first time to start.
if (![[NSUserDefaults standardUserDefaults] valueForKey:@"first"]) {
        
    //If it's the first time you start, use preference settings to set a value for key
    [[NSUserDefaults standardUserDefaults] setValue:@"start" forKey:@"first"];
    NSLog(@"It's the first time to start");
        
} else {
    NSLog(@"Not for the first time");
}

The data is saved very easy by means of key-value pairs.

Note: Data types that NSUserDefaults can store include: NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary. If you want to store other types, you need to convert to the previous type in order to store with NSUserDefaults.

The following example uses NSUserDefaults to store images, which need to be converted to NSData type first.

UIImage *image=[UIImage imageNamed:@"photo"];
//Converting UIImage Objects into NSData
NSData *imageData = UIImageJPEGRepresentation(image, 100);

//Preference settings can save NSData, but not UI image
[[NSUserDefaults standardUserDefaults] setObject:imageData forKey:@"image"];
    
// Read data
NSData *getImageData = [[NSUserDefaults standardUserDefaults] dataForKey:@"image"];
//Converting NSData to UIImage
UIImage *Image = [UIImage imageWithData:imageData];

NSUser Defaults is not very useful! But do you find it a little annoying to write [NSUser Defaults standardUser Defaults] every time? Then it comes to the recommendation section, a useful classification tool. WHKit There are many useful macro definitions in this tool, one of which is KUSERDEFAULT, which is equivalent to [NSUser Defaults standardUser Defaults].
WHKit There are more useful macro definitions waiting for you!

IV. NSKeyed Archiver Archiver/NSKeyed Unarchiver Archives

Archiving and unfiling will serialize and deserialize the data before it is written and read out, and the security of the data is relatively high.
NSKeyed Archiver can have three usage scenarios.

1. Archiving/Unarchiving a Single Simple Object

Similar to plist, simple data is archived and written directly to the file path.

//Get the archive file path
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

//File the string @"test" and write it to filePath
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];

//Unarchive data according to the path filePath saves the data
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
//The log structure is @"test", which is the data archived above.
NSLog(@"%@",result);

Of course, objects such as NSArray, NSDictionary can also be stored.

2. Archiving/Unarchiving Multiple Objects

In this case, multiple different types of data can be saved at one time, and ultimately the same writeToFile:(NSString*) path atomically:(BOOL) useAuxiliary File as plist is used to write data.

    //Get the archive path
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];
    
    //NSMutable Data for carrying data
    NSMutableData *data = [[NSMutableData alloc] init];
    //Archive Object
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    
    //Three data to be saved
    NSString *name = @"jack";
    int age = 17;
    double height = 1.78;
    
    //Using encodeObject: Method to Archive Data
    [archiver encodeObject:name forKey:@"name"];
    [archiver encodeInt:age forKey:@"age"];
    [archiver encodeDouble:height forKey:@"height"];
    //End archiving
    [archiver finishEncoding];
    //Write data (store data)
    [data writeToFile:filePath atomically:YES];
    
    
    //NSMutable Data is used to carry unarchived data
    NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
    //File object
    NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];
    
    //Unarchive three data separately
    NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
    int resultAge = [unArchiver decodeIntForKey:@"age"];
    double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
    //End file
    [unArchiver finishDecoding];
    //Successfully print out the results, indicating successful filing and archiving
    NSLog(@"name = %@, age = %d, height = %.2f",resultName,resultAge,resultHeight);

3. Archiving and saving custom objects

I think this is the most commonly used scenario for filing.
Define a Person class. If you want to archive and archive a person, first let the Person comply with the < NSCoding > protocol.

/********Person.h*********/

#import <Foundation/Foundation.h>

//Compliance with NSCoding Protocol
@interface Person : NSObject<NSCoding>

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

//Customized method of archiving and saving data
+(void)savePerson:(Person *)person;

//Customized reading of unfiled data in sandbox
+(Person *)getPerson;

@end

NSCoding protocol has two methods:

  • (void)encodeWithCoder:(NSCoder *)aCoder
    This method is called when archiving, and encodeObject: for Key: archive variables are used in the method.
  • (instancetype)initWithCoder:(NSCoder *)aDecoder
    This method is called when unpacking, in which the oil decodeObject:forKey reads out the variables.
/******Person.m*******/

#import "Person.h"

@implementation Person

//For archiving, Key suggests using macros instead, which is not used here.
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeInteger:self.age forKey:@"age"];
}

//File release
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self=[super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
        self.age = [aDecoder decodeIntegerForKey:@"age"];
    }
    return self;
}

//Class method, using NSKeyed Archiver to archive data
+(void)savePerson:(Person *)person {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    [NSKeyedArchiver archiveRootObject:person toFile:path];
}

//Class method, using NSKeyedUnarchiver to unarchive data
+(Person *)getPerson {
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"Person.plist"];
    Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return person;
}

@end

Now you can archive or unarchive Person objects where you need them.

/*******ViewController.m*******/

    //Create Person Objects
    Person *person = [Person new];
    person.name = @"jack";
    person.age = 17;
    //File and save data
    [Person savePerson:person];
    //Unarchiving to get data
    Person *resultPerson = [Person getPerson];
    //Print out the results to prove the success of archiving
    NSLog(@"name = %@, age = %ld",resultPerson.name,resultPerson.age);

5. Use of SQLite3

1. First you need to add the library file libsqlite3.0.tbd
2. Import header file # import < sqlite3.h >
3. Open the database
4. Create tables
5. Adding, deleting, altering and checking data tables
6. Close the database

There are some questions you need to understand before you go to the code.

  • SQLite is case-insensitive, but there are also points that need to be noted, such as GLOB and glob have different roles.

  • There are five basic data types in SQLite3: text, integer, float, boolean, blob

  • SQLite3 is typeless. You can create it without declaring the type of field, but it is recommended to add data type.

 create table t_student(name, age);
 create table t_student(name text, age integer);

The following code is the basic usage of SQLite3 with detailed annotations.
The code uses a Student class, which has two properties, name and age.

/****************sqliteTest.m****************/

#import "sqliteTest.h"
//1. First import the header file
#import <sqlite3.h>

//2. database
static sqlite3 *db;

@implementation sqliteTest

/** 3.Open the database */
+ (void)openSqlite {
    
    //The database has been opened
    if (db != nil) {
        NSLog(@"The database has been opened");
        return;
    }
    
    //Create data file paths
    NSString *string = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *path = [string stringByAppendingPathComponent:@"Student.sqlite"];
    NSLog(@"%@",path);
    
    //Open the database
    int result = sqlite3_open(path.UTF8String, &db);
    
    if (result == SQLITE_OK) {
        NSLog(@"Successful database opening");
    } else {
        NSLog(@"Failed to open database");
    }
}

/** 4.Create table */
+ (void)createTable {
    
    //The SQLite statement that creates the table, where id is the primary key, and not null indicates that these fields cannot be NULL when records are created in the table
    NSString *sqlite = [NSString stringWithFormat:@"create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)"];
    
    //Used to record error messages
    char *error = NULL;
    
    //Execute the SQLite statement
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"Successful table creation");
    }else {
        NSLog(@"create table failed");
    }
}

/** 5.Add data */
+ (void)addStudent:(Student *)stu {
    
    //SQLite statement for adding data
    NSString *sqlite = [NSString stringWithFormat:@"insert into t_student (name,age) values ('%@','%ld')",stu.name,stu.age];
    char *error = NULL;
    
    int result = sqlite3_exec(db, [sqlite UTF8String], nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"Successful addition of data");
    } else {
        NSLog(@"Failed to add data");
    }
}

/** 6.Delete data */
+ (void)deleteStuWithName:(NSString *)name {
    
    //Delete SQLite statements for specific data
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student where name = '%@'",name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"Successful deletion of data");
    } else {
        NSLog(@"Failed to delete data");
    }
}

/** 7.change data */
+ (void)upDateWithStudent:(Student *)stu WhereName:(NSString *)name {
    
    //Update SQLLite statements for specific fields
    NSString *sqlite = [NSString stringWithFormat:@"update t_student set name = '%@', age = '%ld' where name = '%@'",stu.name,stu.age,name];
    char *error = NULL;
    
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    
    if (result == SQLITE_OK) {
        NSLog(@"Successful revision of data");
    } else {
        NSLog(@"Failed to modify data");
    }
}

/** 8.Query according to conditions */
+ (NSMutableArray *)selectWithAge:(NSInteger)age {
    
    //Variable arrays to store queried data
    NSMutableArray *array = [NSMutableArray array];
    
    //SQLite statement for querying all data
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student where age = '%ld'",age];
    
    //Define a stmt to store result sets
    sqlite3_stmt *stmt = NULL;
    
    //implement
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"query was successful");
        
        //Traverse through all the data queried and add it to the above array
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //Get the name of column 1, column 0 is id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //Age for column 2
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"Query failed");
    }
    
    //Destroy stmt to prevent memory leaks
    sqlite3_finalize(stmt);
    return array;
}

/** 9.Query all data */
+ (NSMutableArray *)selectStudent {
    
    //Variable arrays to store queried data
    NSMutableArray *array = [NSMutableArray array];
    
    //SQLite statement for querying all data
    NSString *sqlite = [NSString stringWithFormat:@"select * from t_student"];
    
    //Define a stmt to store result sets
    sqlite3_stmt *stmt = NULL;
    
    //implement
    int result = sqlite3_prepare(db, sqlite.UTF8String, -1, &stmt, NULL);
    
    if (result == SQLITE_OK) {
        NSLog(@"query was successful");
        
        //Traverse through all the data queried and add it to the above array
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            Student *stu = [[Student alloc] init];
            //Get the name of column 1, column 0 is id
            stu.name = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(stmt, 1)];
            //Age for column 2
            stu.age = sqlite3_column_int(stmt, 2);
            [array addObject:stu];
        }
    } else {
        NSLog(@"Query failed");
    }
    
    //Destroy stmt to prevent memory leaks
    sqlite3_finalize(stmt);
    return array;
}

/** 10.Delete all data in the table */
+ (void)deleteAllData {
    NSString *sqlite = [NSString stringWithFormat:@"delete from t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"Successful database clearance");
    } else {
        NSLog(@"Clearing database failed");
    }
}

/** 11.Delete table */
+ (void)dropTable {
    NSString *sqlite = [NSString stringWithFormat:@"drop table if exists t_student"];
    char *error = NULL;
    int result = sqlite3_exec(db, sqlite.UTF8String, nil, nil, &error);
    if (result == SQLITE_OK) {
        NSLog(@"Successful deletion of tables");
    } else {
        NSLog(@"Delete table failed");
    }
}

/** 12.close database */
+ (void)closeSqlite {
    int result = sqlite3_close(db);
    if (result == SQLITE_OK) {
        NSLog(@"Successful database closure");
    } else {
        NSLog(@"Database shutdown failed");
    }
}

Attach the basic statement of SQLite

  • Create table:
    create table if not exists table name (field name 1, field name 2...);

create table if not exists t_student (id integer primary key autoincrement, name text not null, age integer)

  • Add data:
    insert into table name (field name 1, field name 2,...) values (value of field 1, value of field 2,...);

insert into t_student (name,age) values (@"Jack",@17);

  • Delete data according to conditions:
    delete from table name where condition;

delete from t_student where name = @"Jack";

  • Delete all data in the table:
    delete from table name

delete from t_student

  • Change a data according to conditions:
    update table name set field 1 = value 1', field 2 = value 2'where field 1 = current value of field 1'

update t_student set name = 'lily', age = '16' where name = 'Jack'

  • Search according to conditions:
    select * from table name where field 1 = value of field 1'

select * from t_student where age = '16'

  • Find all data:
    select * from table name

select * from t_student

  • Delete table:
    drop table table table name

drop table t_student

  • Sort lookup:
    select * from table name order by field

select * from t_student order by age asc (ascending order, default)
select * from t_student order by age desc (descending order)

  • Restrictions:
    select * from table name limit value 1, value 2

select * from t_student limit 5, 10 (skip 5, take 10 data altogether)

SQLite3 also has transactional usage, which is not explained here. Transactional usage will occur in FMDB below.

Six, FMDB

FMDB encapsulates the C language API of SQLite and is more object-oriented.
The first thing to be clear is the three classes in FMDB.

FMDatabase: It can be understood as a database.

FMResultSet: The result set of the query.

FM Database Queue: With multithreading, multiple queries and updates can be executed. Thread safety.

FMDB Basic Grammar

Query: executeQuery: SQLite statement command.

[db executeQuery:@"select id, name, age from t_person"]

The rest of the operations are "updates": executeUpdate: SQLite statement commands.

// CREATE, UPDATE, INSERT, DELETE, DROP, all use executeUpdte
[db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"]

Basic Use of FMDB

Import FMDB framework and sqlite3.0.tbd into the project and import header files.

1. Open the database and create tables

Initialize FMDatabase: FMDatabase * DB = [FMDatabase database WithPath: filePath];
filePath is the path that is prepared in advance to store data.

Open the database: [db open]

Create a data table: [db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)];

#import "ViewController.h"
#import <FMDB.h>

@interface ViewController ()

@end

@implementation ViewController{
    FMDatabase *db;
}

- (void)openCreateDB {

    //Path to store data
    NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [path stringByAppendingPathComponent:@"person.sqlite"];
    
    //Initialize FMDatabase
    db = [FMDatabase databaseWithPath:filePath];
    
    //Open the database and create a person table with the primary key id, name, and age in the person
    if ([db open]) {
        BOOL success = [db executeUpdate:@"create table if not exists t_person (id integer primary key autoincrement, name text, age integer)"];
        if (success) {
            NSLog(@"Create success");
        }else {
            NSLog(@"create table failed");
        }
    } else {
        NSLog(@"Open failure");
    }
}

2. Insert data

Use the executeUpdate method to execute the insertion data commands: [db executeUpdate:@ "insert into t_person (name, age) values (?,?),@"jack", @17]

-(void)insertData {
    BOOL success = [db executeUpdate:@"insert into t_person(name,age) values(?,?)",@"jack",@17];
    if (success) {
        NSLog(@"Successful addition of data");
    } else {
        NSLog(@"Failed to add data");
    }
}

3. Delete data

Delete data with the name lily: [db executeUpdate:@ "delete from t_person where name = lily'"]

-(void)deleteData {
    BOOL success = [db executeUpdate:@"delete from t_person where name = 'lily'"];
    if (success) {
        NSLog(@"Successful deletion of data");
    } else {
        NSLog(@"Failed to delete data");
    }
}

4. Modifying data

Change the name to lily: [db executeUpdate:@ "update t_person set name = lily'where age = 17"]

-(void)updateData {
    BOOL success = [db executeUpdate:@"update t_person set name = 'lily' where age = 17"];
    if (success) {
        NSLog(@"Successful update of data");
    } else {
        NSLog(@"Failure to update data");
    }
}

5. Query data

Execute the query statement and receive the query result with FMResultSet: FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"]

Traversing query results: [set next]

Get the name of each number: NSString * name = set string ForColumn Index: 1;

You can also get the name of each piece of data in this way: NSString * name = result string ForColumn:@ "name";

    FMResultSet *set = [db executeQuery:@"select id, name, age from t_person"];
    while ([set next]) {
        int ID = [set intForColumnIndex:0];
        NSString *name = [set stringForColumnIndex:1];
        int age = [set intForColumnIndex:2];
        NSLog(@"%d,%@,%d",ID,name,age);
    }
}

6. delete table

Delete the specified table: [db executeUpdate:@"drop table if exists t_person"]

-(void)dropTable {
    BOOL success = [db executeUpdate:@"drop table if exists t_person"];
    if (success) {
        NSLog(@"Successful deletion of tables");
    } else {
        NSLog(@"Delete table failed");
    }
}

Basic use of FM Database Queue

FM Database is thread insecure, and FM Database Queue comes in handy when FMDB data storage wants to use multithreading.

The method of initializing FMDatabase Queue is similar to FMDatabase.

//Data file path
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    
//Initialize FM Database Queue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];

It is also very convenient to execute commands in FM Database Queue and operate directly in a block.

-(void)FMDdatabaseQueueFunction {
    
    NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    //Data file path
    NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];
    //Initialize FM Database Queue
    FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
    
    //Execute the SQLite statement command in the block
    [dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        //Create table
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //Add data
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@17];
        [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"lily",@16];
        //Query data
        FMResultSet *set = [db executeQuery:@"select id, name, age from t_student"];
        //Traversing the queried data
        while ([set next]) {
            int ID = [set intForColumn:@"id"];
            NSString *name = [set stringForColumn:@"name"];
            int age = [set intForColumn:@"age"];
            NSLog(@"%d,%@,%d",ID,name,age);
        }
        
    }];
}

Transactions in FMDB

What is a transaction?

Transaction is an integral and indivisible operation, either executed or not executed.
For example, there are 20 children in kindergarten organized by teachers to go out for spring outing. When they return to school, everyone boards the school bus in turn. At this time, if one child does not get on the bus, the bus can not start. So even if 19 people get on the bus, it's equal to 0 people get on the bus. Twenty people are a whole.
Of course, this example may not be very accurate.

In FMDB, there are transaction rollback operations, that is to say, when a small problem occurs in the execution of an overall transaction, the rollback is performed, and then all operations in this set of transactions will be totally invalid.

In the following code, add 2000 pieces of data to the database by transaction loop. If there are some problems in the process of adding, no data in the database will appear because of the * rollback = YES rollback operation.
If there is a problem with the addition of Article 2000 data, even though 199 data have been added before, there is still no data in the database due to the rollback.

//Database Path
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"student.sqlite"];

//Initialize FM Database Queue
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];

//FM Database Queue transaction in Transaction
[dbQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
        //Create table
        [db executeUpdate:@"create table if not exists t_student (id integer primary key autoincrement, name text, age integer)"];
        //Loop 2000 data
        for (int i = 0; i < 2000; i++) {
            BOOL success = [db executeUpdate:@"insert into t_student(name,age) values(?,?)",@"jack",@(i)];
            //If there is a problem with adding data, rollback
            if (!success) {
                //Data rollback
                *rollback = YES;
                return;
            }
        }
    }];

7. Core Data

Core Data has a graphical operation interface, and is more object-oriented to manipulate model data. When you are familiar with it, I believe you will like this data storage framework.

Fast Data Storage Using Core Data

1. Graphical Modeling

When creating a project, check the Use Core Data option in the figure below, and the project will automatically create a data model file. Of course, you can also create it manually in development.


Automatically create model files

The following figure is an auto-created file


Created files

If there is no check, you can also create it manually here.


Manually create

After clicking Add Entity, it's quite a data table. The name of the table itself is defined above, note that the initials should be capitalized.
Attributes and associated attributes can also be added to the interface for data entities.


Create a data table

The data types supported by the Core Data attribute are as follows


data type

After compilation, Xcode will automatically generate Person's entity code file, and the file will not be displayed in the project. If Codegen on the right side of the following figure chooses Manual/None, Xcode will not automatically generate code, and we can generate it manually.


6.png

Manually generate the entity class code, select the CoreDataTest. xcdatamodel file, and then select Editor in the Mac menu bar, as shown in the following figure. Next is all right.
If you do not select Manual/None and still create it manually, it will conflict with the files created automatically by the system, which needs to be noted.
You can also use the system to create a good NSManagedObject without selecting Manual/None. There will also be four files, but they are not visible in the project. When you use them, you can import the header file #import "Person+CoreDataClass.h".


Manual creation of NSManagedObject

Manually created are four files


10.png

Also note the choice of programming language, Swift or OC


Programing language

2. Introduction and Use of Core Data Stack

The next step is to initialize Core Data and save local data.
There are three classes that need to be used:

Structural Information of NSManagedObjectModel Data Model
NSPersistentStoreCoordinator Data Persistence Layer and Object Model Coordinator
NSManagedObjectContext Model ManagerdObject Object Object Context

As shown in the figure below, there can be multiple model objects in a context, but in most operations there is only one context, and all objects exist in that context.
Objects and their contexts are related, each managed object knows which context it belongs to, and each context knows which objects it manages.

When Core Data is read or written from the system, there is a persistent store coordinator, which interacts with the SQLite database in the file system and also connects the Context context Context where the model is stored.


Core Data

The following is a more common way:


Core Data Stack

Now let's step by step implement the creation of the Core Data stack.

  • First, we define an NSManagedObjectModel attribute in AppDelegate. Then use lazy loading to create NSManagedObjectModel objects. And note that the suffix is momd when it is created. The code is as follows:
//Create properties
@property (nonatomic, readwrite, strong) NSManagedObjectModel *managedObjectModel;

//Lazy load
- (NSManagedObjectModel *)managedObjectModel {
    if (!_managedObjectModel) {
        //Note that the extension is momd
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    }
    return _managedObjectModel;
}
  • Create the coordinator NSPersistentStoreCoordinator. Similarly, first create an attribute in AppDelegate and then load it lazily.
//attribute
@property (nonatomic, readwrite, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;

//Lazy load
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    
    if (!_persistentStoreCoordinator) {
        
        //Model created before importing
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        
        //Specify sqlite database files
        NSURL *sqliteURL = [[self documentDirectoryURL] URLByAppendingPathComponent:@"CoreDataTest.sqlite"];
        
        //This option is for data migration. Interesting people can study it.
        NSDictionary *options=@{NSMigratePersistentStoresAutomaticallyOption:@(YES),NSInferMappingModelAutomaticallyOption:@(YES)};
        NSError *error;
        
        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                  configuration:nil
                                                            URL:sqliteURL
                                                        options:options
                                                          error:&error];
        if (error) {
            NSLog(@"Failed to create coordinator: %@", error.localizedDescription);
        }
    }
    return _persistentStoreCoordinator;
}


//Get the document directory
- (nullable NSURL *)documentDirectoryURL {
    return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
}
  • Create NSManagedObjectContext, the same is attribute + lazy loading.
//attribute
@property (nonatomic, readwrite, strong) NSManagedObjectContext *context;

//Lazy load
- (NSManagedObjectContext *)context {
    if (!_context) {
        
        _context = [[NSManagedObjectContext alloc ] initWithConcurrencyType:NSMainQueueConcurrencyType];
        
        // Designated Coordinator
        _context.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }
    return _context;
}

3. Use Core Data to add, delete and modify data

  • Add data
    Use a method of the NSEntityDesctition class to create the NSManagedObject object. The first parameter is the name of the entity class and the second parameter is the ontext created previously.
    Assign values to objects and store them.
@implementation ViewController {
    NSManagedObjectContext *context;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //Get the managed ObjectContext
    context = [AppDelegate new].context;
    
    //Using NSEntityDescription to Create NSManagedObject Objects
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    //Assignment of Objects
    person.name = @"Jack";
    person.age = 17;
    NSError *error;
    //Save to database
    [context save:&error];
}
  • Query data
    Core Data queries data from a database using three classes:

NSFetchRequest: A query request, equivalent to a select statement in SQL
NSPredicate: Predicate, specifying some query conditions, equivalent to where in SQL
NSSortDescriptor: Specifies a sort rule, equivalent to order by in SQL

There are two attributes in NSFetchRequest:

predicate: An NSPredicate object
sortDescriptors: It's an array of NSSortDescriptor s, in which the precedence is higher than the precedence. There can be multiple permutation rules.

    //More NSFetchRequest attributes
    fetchLimit: Maximum number of result sets, equivalent to limit in SQL
    fetchOffset: The offset of the query, by default, is 0
    fetchBatchSize: The size of queries processed in batches and returned in batches
    Entity Name / entity: Data table name, equivalent to from in SQL
    ProperrtiesToGroupBy: Grouping rule, equivalent to group by in SQL
    ProperrtiesToFetch: Defines the fields to be queried and defaults to query all fields

After setting up NSFetchRequest, call the executeFetchRequest method of NSManagedObjectContext and return the result set.

    //The fetchRequest method is generated by the NSFetchRequest Object created automatically by Xcode, and NSFetchRequest can be obtained directly.
    NSFetchRequest *fetchRequest = [Person fetchRequest];
    //It can also be obtained as follows: [NSFetchRequest fetch Request WithEntityName:@ "Student"];
    
    //predicate
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age == %@", @(16)];
    
    //sort
    NSArray<NSSortDescriptor *> *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
    
    fetchRequest.sortDescriptors = sortDescriptors;

    //Using ExcuteFetchRequest Method to Get Result Set
    NSArray<Person *> *personResult = [context executeFetchRequest:fetchRequest error:nil];

Xcode itself has a code snippet, "fetch" of NSFetchRequest, which appears as follows.


snippet

code snippet of NSFetchRequest: "fetch"
  • Update data
    Updating data is relatively simple. After querying the data that needs to be modified, modify the value directly, and then use context save.
for (Person *person in personResult) {
        //directly modify
        person.age = 26;
    }

//Don't forget to save it.
[context save:&error];
  • Delete data
    After querying the data that needs to be deleted, call the deleteObject method of NSManagedObjectContext.
for (Person *person in personResult) {
        //Delete data
        [context deleteObject:person];
    }
//Don't forget to save
[context save:&error];

So far, the basic content of Core Data has been finished.

Postscript:

There are many methods of data persistence in iOS. The most appropriate technology should be chosen according to the specific situation.

Recommend a simple and useful set of classifications: WHKit
github address: https://github.com/remember17

Tags: Database SQLite Attribute SQL

Posted on Mon, 24 Dec 2018 13:39:06 -0500 by phpdood