[[141204]] Introduction The so-called persistence is to save data to the hard disk so that the previously saved data can be accessed after the application or machine is restarted. In iOS development, there are many data persistence solutions. Next, I will try to introduce 5 solutions: - plist file (property list)
- preference
- NSKeyedArchiver
- SQLite 3
- CoreData
Sandbox Before introducing various storage methods, it is necessary to explain the following sandbox mechanism. By default, iOS programs can only access their own directory, which is called the "sandbox". 1. Structure Since the sandbox is a folder, let's take a look at what's inside. The directory structure of the sandbox is as follows: - "Application Package"
- Documents
- Library
- Caches
- Preferences
- tmp
2. Directory Features Although there are so many folders in the sandbox, all folders are different and have their own characteristics. So when choosing a storage directory, you must carefully choose a suitable directory. "Application package": This contains the source files of the application, including resource files and executable files. - NSString *path = [[NSBundle mainBundle] bundlePath];
- NSLog(@ "%@" , path);
Documents: The most commonly used directory. When iTunes syncs the app, it will sync the contents of this folder. It is suitable for storing important data. - NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
- NSLog(@ "%@" , path);
Library/Caches: iTunes will not synchronize this folder. It is suitable for storing large and non-important data that does not need to be backed up. - NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
- NSLog(@ "%@" , path);
Library/Preferences: When iTunes syncs the app, it will sync the contents of this folder, which usually stores the app's settings. tmp: iTunes will not synchronize this folder. The system may delete the files in this directory when the application is not running. Therefore, this directory is suitable for storing some temporary files in the application and deleting them after use. - NSString *path = NSTemporaryDirectory();
- NSLog(@ "%@" , path);
plist file The plist file saves certain specific classes in a directory in the form of XML files. The only types that can be serialized are: - NSArray;
- NSMutableArray;
- NSDictionary;
- NSMutableDictionary;
- NSData;
- NSMutableData;
- NSString;
- NSMutableString;
- NSNumber;
- NSDate;
1. Get the file path - NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
- NSString *fileName = [path stringByAppendingPathComponent:@ "123.plist" ];
2. Storage - NSArray *array = @[@ "123" , @ "456" , @ "789" ];
- [array writeToFile:fileName atomically:YES];
3. Read - NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
- NSLog(@ "%@" , result);
4. Note - Only the types listed above can be stored using plist files.
- When storing, use the writeToFile: atomically: method. Atomically indicates whether to write to an auxiliary file first and then copy the auxiliary file to the target file address. This is a safer way to write to a file, and is generally written as YES.
- Use the arrayWithContentsOfFile: method when reading.
Preference 1. How to use -
- NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
-
- [userDefaults setObject:@ "AAA" forKey:@ "a" ];
- [userDefaults setBool:YES forKey:@ "sex" ];
- [userDefaults setInteger: 21 forKey:@ "age" ];
-
- [userDefaults synchronize];
-
- NSString *name = [userDefaults objectForKey:@ "a" ];
- BOOL sex = [userDefaults boolForKey:@ "sex" ];
- NSInteger age = [userDefaults integerForKey:@ "age" ];
- NSLog(@ "%@, %d, %ld" , name, sex, age);
2. Note - Preferences are specifically used to save application configuration information. Generally, do not save other data in preferences.
- If the synchronize method is not called, the system will save it to the file at an indefinite time according to the I/O situation. Therefore, if you need to write to the file immediately, you must call the synchronize method.
- Preferences will save all data to the same file, which is a plist file named after the application package name in the preference directory.
#p# NSKeyedArchiver Archiving is another form of serialization in iOS. Any object that follows the NSCoding protocol can be serialized through it. Since most Foundation and Cocoa Touch classes that support data storage follow the NSCoding protocol, archiving is relatively easy to implement for most classes. 1. Follow the NSCoding protocol The NSCoding protocol declares two methods, both of which must be implemented: one that describes how to encode an object into an archive, and the other that describes how to unarchive it to get a new object. - Conforming to protocols and setting properties
-
- @interface Person : NSObject
- @property (strong, nonatomic) UIImage *avatar;
- @property (copy, nonatomic) NSString *name;
- @property (assign, nonatomic) NSInteger age;
- @end
- Implementing protocol methods
-
- - (id)initWithCoder:(NSCoder *)aDecoder {
- if ([ super init]) {
- self.avatar = [aDecoder decodeObjectForKey:@ "avatar" ];
- self.name = [aDecoder decodeObjectForKey:@ "name" ];
- self.age = [aDecoder decodeIntegerForKey:@ "age" ];
- }
- return self;
- }
-
- - ( void )encodeWithCoder:(NSCoder *)aCoder {
- [aCoder encodeObject:self.avatar forKey:@ "avatar" ];
- [aCoder encodeObject:self.name forKey:@ "name" ];
- [aCoder encodeInteger:self.age forKey:@ "age" ];
- }
If the class to be archived is a subclass of a custom class, you need to implement the archiving and unarchiving methods of the parent class before archiving and unarchiving, that is, the [super encodeWithCoder:aCoder] and [super initWithCoder:aDecoder] methods; 2. Use To archive an object, call the NSKeyedArchiver factory method archiveRootObject: toFile: . - NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.data" ];
- Person *person = [[Person alloc] init];
- person.avatar = self.avatarView.image;
- person.name = self.nameField.text;
- person.age = [self.ageField.text integerValue];
- [NSKeyedArchiver archiveRootObject:person toFile:file];
If you need to unarchive an object from a file, just call a factory method of NSKeyedUnarchiver, unarchiveObjectWithFile:. - NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.data" ];
- Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
- if (person) {
- self.avatarView.image = person.avatar;
- self.nameField.text = person.name;
- self.ageField.text = [NSString stringWithFormat:@ "%ld" , person.age];
- }
3. Note Must follow and implement the NSCoding protocol The extension of the saved file can be specified arbitrarily When inheriting, you must first call the parent class's archiving and unarchiving methods
SQLite3 All previous storage methods are overwriting storage. If you want to add a piece of data, you must read the entire file, modify the data, and then overwrite the entire content into the file. Therefore, they are not suitable for storing large amounts of content. 1. Field Type On the surface, SQLite divides data into the following types: - integer : integer
- real : real number (floating point number)
- text : text string
- blob: binary data, such as files, pictures, etc.
In fact, SQLite is typeless. That is, no matter what field type you specify when creating a table, the storage can still store any type of data. And you don't have to specify the field type when creating a table. The reason why SQLite has types is for good programming standards and to facilitate communication between developers, so it is best to set the correct field type when using it! The primary key must be set to integer 2. Preparation The preparation work is to import the dependent library. To use SQLite3 in iOS, you need to add the library file: libsqlite3.dylib and import the main header file. This is a C language library, so it is quite troublesome to use SQLite3 directly. 3. Use - Create a database and open it
Before operating the database, you must first specify the database file and the table to be operated. Therefore, when using SQLite3, you must first open the database file and then specify or create a table. -
-
-
- - ( void )openDatabase {
-
- NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.db" ];
-
- NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
- if (result == SQLITE_OK) {
- NSLog(@ "Open database successfully!" );
-
- char *errmsg = NULL;
- sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)" , NULL, NULL, &errmsg);
- if (errmsg) {
- NSLog(@ "Error:%s" , errmsg);
- } else {
- NSLog(@ "Table creation successful!" );
- }
- } else {
- NSLog(@ "Failed to open database!" );
- }
- }
The sqlite3_exec() method can be used to execute any SQL statement, such as table creation, update, insert, and delete operations. However, it is generally not used to execute query statements because it does not return the queried data. -
-
-
- - ( void )insertData {
- NSString *nameStr;
- NSInteger age;
- for (NSInteger i = 0 ; i < 1000 ; i++) {
- nameStr = [NSString stringWithFormat:@ "Bourne-%d" , arc4random_uniform( 10000 )];
- age = arc4random_uniform( 80 ) + 20 ;
- NSString *sql = [NSString stringWithFormat:@ "INSERT INTO t_person (name, age) VALUES('%@', '%ld')" , nameStr, age];
- char *errmsg = NULL;
- sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
- if (errmsg) {
- NSLog(@ "Error:%s" , errmsg);
- }
- }
- NSLog(@ "Insertion completed!" );
- }
As mentioned above, the sqlite3_exec() method is generally not used to query data. Because querying data requires obtaining query results, the query is relatively troublesome. The sample code is as follows: sqlite3_prepare_v2(): Check the validity of sql sqlite3_step() : Get query results row by row, repeating until the last record sqlite3_coloum_xxx(): Get the content of the corresponding type. iCol corresponds to the order of the fields in the SQL statement, starting from 0. According to the properties of the actual query field, use sqlite3_column_xxx to get the corresponding content. sqlite3_finalize() : release stmt
-
-
-
- - ( void )readData {
- NSMutableArray *mArray = [NSMutableArray arrayWithCapacity: 1000 ];
- char *sql = "select name, age from t_person;" ;
- sqlite3_stmt *stmt;
- NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, - 1 , &stmt, NULL);
- if (result == SQLITE_OK) {
- while (sqlite3_step(stmt) == SQLITE_ROW) {
- char *name = ( char *)sqlite3_column_text(stmt, 0 );
- NSInteger age = sqlite3_column_int(stmt, 1 );
-
- Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
- [mArray addObject:person];
- }
- self.dataList = mArray;
- }
- sqlite3_finalize(stmt);
- }
4. Summary Generally speaking, the use of SQLite3 is still quite troublesome, because they are all C language functions, which are difficult to understand. However, in the general development process, the third-party open source library FMDB is used, which encapsulates these basic C language methods, making it easier to understand when we use it and improving development efficiency. #p# FMDB 1. Introduction FMDB is a SQLite database framework for iOS. It encapsulates the SQLite C language API in an OC way. Compared with the C language framework that comes with Cocoa, it has the following advantages: It is more object-oriented and saves a lot of trouble and redundant C language code. Compared with Apple's own Core Data framework, it is more lightweight and flexible Provides a multi-threaded safe database operation method to effectively prevent data confusion Note: FMDB's gitHub address 2. Core Classes FMDB has three main classes: An FMDatabase object represents a single SQLite database and is used to execute SQL statements. The result set after executing the query using FMDatabase Used to execute multiple queries or updates in multiple threads, it is thread-safe 3. Open the database Like the C language framework, FMDB creates an FMDatabase object by specifying the SQLite database file path, but FMDB is easier to understand and use. Before using it, you still need to import sqlite3.dylib. The method to open the database is as follows: - NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.db" ];
- FMDatabase *database = [FMDatabase databaseWithPath:path];
- if (![database open]) {
- NSLog(@ "Database opening failed!" );
- }
It is worth noting that the value of Path can be passed in the following three situations: - The specific file path will be created automatically if it does not exist
- Empty string @"" will create an empty database in the temporary directory. When the FMDatabase connection is closed, the database file will also be deleted.
- nil, a temporary in-memory database will be created. When the FMDatabase connection is closed, the database will be destroyed.
4. Update In FMDB, all operations except query are called "updates", such as create, drop, insert, update, delete, etc., and the executeUpdate: method is used to perform updates: -
- - (BOOL)executeUpdate:(NSString*)sql, ...
- - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
-
- [database executeUpdate:@ "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)" ];
-
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES(?, ?)" , @ "Bourne" , [NSNumber numberWithInt: 42 ]];
5. Query There are also 3 query methods, which are quite simple to use: - - (FMResultSet *)executeQuery:(NSString*)sql, ...
- - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
Query example: -
- FMResultSet *result = [database executeQuery:@ "SELECT * FROM t_person" ];
-
- while ([result next]) {
- NSString *name = [result stringForColumn:@ "name" ];
- int age = [result intForColumn:@ "age" ];
- }
6. Thread Safety It is unwise to use an FMDatabase instance in multiple threads at the same time. Do not let multiple threads share the same FMDatabase instance, which cannot be used in multiple threads at the same time. If you use an FMDatabase instance in multiple threads at the same time, it will cause data confusion and other problems. Therefore, please use FMDatabaseQueue, which is thread-safe. Here is how to use it: - FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
- [queue inDatabase:^(FMDatabase *database) {
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_1" , [NSNumber numberWithInt: 1 ]];
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_2" , [NSNumber numberWithInt: 2 ]];
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_3" , [NSNumber numberWithInt: 3 ]];
- FMResultSet *result = [database executeQuery:@ "select * from t_person" ];
- while ([result next]) {
- }
- }];
And you can easily wrap simple tasks into transactions: - [queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_1" , [NSNumber numberWithInt: 1 ]];
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_2" , [NSNumber numberWithInt: 2 ]];
- [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_3" , [NSNumber numberWithInt: 3 ]];
- FMResultSet *result = [database executeQuery:@ "select * from t_person" ];
- while ([result next]) {
- }
-
- *rollback = YES;
- }];
FMDatabaseQueue will create a serialized GCD queue behind the scenes and execute the blocks you pass to the GCD queue. This means that even if you call the method from multiple threads at the same time, GDC will execute it in the order it receives the blocks. |