iOS developers should know: which data persistence solutions

iOS developers should know: which data persistence solutions

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:

  1. "Application Package"  
  2. Documents
  3. Library
  4. Caches
  5. Preferences
  6. 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.

  1. NSString *path = [[NSBundle mainBundle] bundlePath];
  2. 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.

  1. NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
  2. 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.

  1. NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
  2. NSLog(@ "%@" , path);

Library/Preferences: When iTunes syncs the app, it will sync the contents of this folder, which usually stores the app's settings.

  1. 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.
  2. NSString * path = NSTemporaryDirectory ();
  3. 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:

  1. NSArray;
  2. NSMutableArray;
  3. NSDictionary;
  4. NSMutableDictionary;
  5. NSData;
  6. NSMutableData;
  7. NSString;
  8. NSMutableString;
  9. NSNumber;
  10. NSDate;

1. Get the file path

  1. NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
  2. NSString *fileName = [path stringByAppendingPathComponent:@ "123.plist" ];

2. Storage

  1. NSArray *array = @[@ "123" , @ "456" , @ "789" ];
  2. [array writeToFile:fileName atomically:YES];

3. Read

  1. NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
  2. 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

  1. //1. Get the NSUserDefaults file  
  2. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  3. //2. Write content to the file  
  4. [userDefaults setObject:@ "AAA" forKey:@ "a" ];
  5. [userDefaults setBool:YES forKey:@ "sex" ];
  6. [userDefaults setInteger: 21 forKey:@ "age" ];
  7. //2.1 Immediate synchronization  
  8. [userDefaults synchronize];
  9. //3. Read the file  
  10. NSString *name = [userDefaults objectForKey:@ "a" ];
  11. BOOL sex = [userDefaults boolForKey:@ "sex" ];
  12. NSInteger age = [userDefaults integerForKey:@ "age" ];
  13. 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.

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

  1. //1. Follow the NSCoding protocol  
  2. @interface Person : NSObject //2. Set properties  
  3. @property (strong, nonatomic) UIImage *avatar;
  4. @property (copy, nonatomic) NSString *name;
  5. @property (assign, nonatomic) NSInteger age;
  6. @end  

Implementing protocol methods

  1. //Unzip  
  2. - (id)initWithCoder:(NSCoder *)aDecoder {
  3. if ([ super init]) {
  4. self.avatar = [aDecoder decodeObjectForKey:@ "avatar" ];
  5. self.name = [aDecoder decodeObjectForKey:@ "name" ];
  6. self.age = [aDecoder decodeIntegerForKey:@ "age" ];
  7. }
  8. return self;
  9. }
  10. //Archive  
  11. - ( void )encodeWithCoder:(NSCoder *)aCoder {
  12. [aCoder encodeObject:self.avatar forKey:@ "avatar" ];
  13. [aCoder encodeObject:self.name forKey:@ "name" ];
  14. [aCoder encodeInteger:self.age forKey:@ "age" ];
  15. }

Special attention

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

  1. NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.data" ];
  2. Person *person = [[Person alloc] init];
  3. person.avatar = self.avatarView.image;
  4. person.name = self.nameField.text;
  5. person.age = [self.ageField.text integerValue];
  6. [NSKeyedArchiver archiveRootObject:person toFile:file];

If you need to unarchive an object from a file, just call a factory method of NSKeyedUnarchiver, unarchiveObjectWithFile:.

  1. NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.data" ];
  2. Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
  3. if (person) {
  4. self.avatarView.image = person.avatar;
  5. self.nameField.text = person.name;
  6. self.ageField.text = [NSString stringWithFormat:@ "%ld" , person.age];
  7. }

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

  1. /**
  2. * Open the database and create a table
  3. */  
  4. - ( void )openDatabase {
  5. //1. Set the file name  
  6. NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.db" ];
  7. //2. Open the database file, if there is no file, it will be created automatically  
  8. NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
  9. if (result == SQLITE_OK) {
  10. NSLog(@ "Open database successfully!" );
  11. //3. Create a database table  
  12. char *errmsg = NULL;
  13. sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)" , NULL, NULL, &errmsg);
  14. if (errmsg) {
  15. NSLog(@ "Error:%s" , errmsg);
  16. } else {
  17. NSLog(@ "Table creation successful!" );
  18. }
  19. } else {
  20. NSLog(@ "Failed to open database!" );
  21. }
  22. }

Execute Instructions

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.

  1. /**
  2. * Insert 1000 records into the table
  3. */  
  4. - ( void )insertData {
  5. NSString *nameStr;
  6. NSInteger age;
  7. for (NSInteger i = 0 ; i < 1000 ; i++) {
  8. nameStr = [NSString stringWithFormat:@ "Bourne-%d" , arc4random_uniform( 10000 )];
  9. age = arc4random_uniform( 80 ) + 20 ;
  10. NSString *sql = [NSString stringWithFormat:@ "INSERT INTO t_person (name, age) VALUES('%@', '%ld')" , nameStr, age];
  11. char *errmsg = NULL;
  12. sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
  13. if (errmsg) {
  14. NSLog(@ "Error:%s" , errmsg);
  15. }
  16. }
  17. NSLog(@ "Insertion completed!" );
  18. }

Query command

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, repeat until the last record is reached

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 stm

  1. /**
  2. * Read data from the table into an array
  3. */  
  4. - ( void )readData {
  5. NSMutableArray *mArray = [NSMutableArray arrayWithCapacity: 1000 ];
  6. char *sql = "select name, age from t_person;" ;
  7. sqlite3_stmt *stmt;
  8. NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, - 1 , &stmt, NULL);
  9. if (result == SQLITE_OK) {
  10. while (sqlite3_step(stmt) == SQLITE_ROW) {
  11. char *name = ( char *)sqlite3_column_text(stmt, 0 );
  12. NSInteger age = sqlite3_column_int(stmt, 1 );
  13. //Create object  
  14. Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
  15. [mArray addObject:person];
  16. }
  17. self.dataList = mArray;
  18. }
  19. sqlite3_finalize(stmt);
  20. }

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:

FMDatabase

An FMDatabase object represents a single SQLite database and is used to execute SQL statements.

FMResultSet

The result set after executing the query using FMDatabase

FMDatabaseQueue

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:

  1. NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@ "person.db" ];
  2. FMDatabase *database = [FMDatabase databaseWithPath:path];
  3. if (![database open]) {
  4. NSLog(@ "Database opening failed!" );
  5. }

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 "update", such as create, drop, insert, update, delete, etc., and executeUpdate: method is used to perform update.

  1. There are three common methods :
  2. - (BOOL)executeUpdate:(NSString*)sql, ...
  3. - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
  4. - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
  5. //Example  
  6. [database executeUpdate:@ "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)" ];
  7. //or  
  8. [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:

  1. - (FMResultSet *)executeQuery:(NSString*)sql, ...
  2. - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
  3. - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

Query example:

  1. //1. Execute query  
  2. FMResultSet *result = [database executeQuery:@ "SELECT * FROM t_person" ];
  3. //2. Traverse the result set  
  4. while ([result next]) {
  5. NSString *name = [result stringForColumn:@ "name" ];
  6. int age = [result intForColumn:@ "age" ];
  7. }

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:

Create a queue.

  1. FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

Using Queues

  1. [queue inDatabase:^(FMDatabase *database) {
  2. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_1" , [NSNumber numberWithInt: 1 ]];
  3. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_2" , [NSNumber numberWithInt: 2 ]];
  4. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_3" , [NSNumber numberWithInt: 3 ]];
  5. FMResultSet *result = [database executeQuery:@ "select * from t_person" ];
  6. while ([result next]) {
  7. }
  8. }];

And you can easily wrap simple tasks into transactions:

  1. [queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
  2. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_1" , [NSNumber numberWithInt: 1 ]];
  3. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_2" , [NSNumber numberWithInt: 2 ]];
  4. [database executeUpdate:@ "INSERT INTO t_person(name, age) VALUES (?, ?)" , @ "Bourne_3" , [NSNumber numberWithInt: 3 ]];
  5. FMResultSet *result = [database executeQuery:@ "select * from t_person" ];
  6. while ([result next]) {
  7. }
  8. //rollback  
  9. *rollback = YES;
  10. }];

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.

<<:  What computer technology skills do programmers have that would amaze non-programmers?

>>:  WeX5 cross-terminal mobile development framework V3.2 preview version released

Recommend

Brand promotion: 23 ways to refine product and brand selling points

The starting point of all businesses is to benefi...

iPhone XR becomes the most popular smartphone in 2019

The "Smartphone Model Market Tracking" ...

Ten rules for open source success

[[151765]] Everyone wants it, and many are trying...

I heard that programmers all want such a working environment?

[[134213]] Last week I received an email from thr...

How does product operation improve user stickiness?

Products of various categories cover all aspects ...

Toutiao account operation and promotion, do you know all these?

In recent years, Toutiao has developed rapidly, a...

Review of NetEase Cloud’s personality dominant color activity!

I believe that everyone has been flooded with Net...

LeakCanary: detect all memory leaks

Original text: LeakCanary: Detect all memory leak...

How to do Christmas marketing? Here are 3 ways and 10 cases

The afterglow of “Double Twelve” has not yet fade...