Objective-C data persistence

Keywords: Database SQL Attribute iOS

brief introduction

In development, we usually need to store some data locally. In this article, we introduce the method of data persistence in iOS.

  • 1. Attribute List

  • 2. Object archiving

  • 3. Preference Settings

  • 4. Embedded database (SQLite3)

  • 5. Core Data, a persistence tool provided by Apple

The following four ways are introduced one by one. In the last one, we introduced the basic knowledge of sandbox and document management. We will introduce the following content based on the previous one.

1. Attribute List

Property list (plist), specifying the configuration of the application such as tabbar status, black and white list, network request ATS, etc. We can edit it manually through Xcode or Property List Editor. And as long as dictionaries or arrays contain specific serializable objects, NSDictionary and NSArray instances can be written to attribute lists or created.

Serialized objects are objects that can be converted into byte streams for storage in files or transmission over the network.

Class Objective-C can be serialized:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;

We retrieve the directories that need to be accessed according to the method in the previous article and then perform the save and read operations.

//Access path
 NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *fileName = [path stringByAppendingPathComponent:@"test.plist"];

//Save array
NSArray *array = @[@"Gavin", @"Alice", @"Lucy"];
[array writeToFile:fileName atomically:YES];

//Read information
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];

atomically: The parameter lets the method write data to a secondary file rather than to a specified location. After successful writing to the file, the auxiliary file will be copied to the location specified by the first parameter, which is a safer way to write to the file. If the application crashes during storage, the existing file will not be destroyed.

Only a small number of objects can be stored in the attribute list. Let's look at a more powerful method.

2. Object archiving

Let's first introduce some basic concepts:

Archving: Another form of serialization, which can serialize any object, write for saving data, easily write complex objects to files, and then read them from them.

The implementation of object archiving should follow the NSCoding protocol as well as the NSCopying protocol. NSCopying can allow replication of objects. Because most Foundation and Cocoa Touch classes that support storing data follow the NSCoding protocol, archiving is relatively easy to implement for most classes.

NSCoding Protocol

The NSCoding protocol declares two methods, archiving and decoding. Archiving encodes an object into the archive, decoding decodes the archive to create a new object. Let's look at the following examples

  @interface Person : NSObject  
  @property (nonatomic, copy) NSString *name;
  @property (nonatomic, assgin) NSInteger age;
  @end
 //File
  - (void)encodeWithCoder:(NSCoder *)aCoder {
      [aCoder encodeObject:self.name forKey:@"name"];
      [aCoder encodeInteger:self.age forKey:@"age"];
  }
//File release
  - (id)initWithCoder:(NSCoder *)aDecoder {
      if ([super init]) {
          self.name = [aDecoder decodeObjectForKey:@"name"];
          self.age = [aDecoder decodeIntegerForKey:@"age"];
      }
      return self;
  }

By implementing these two methods, the attributes of the objects can be coded and decoded, and then the objects can be archived and written to or read from the archives.

NSCopying Protocol

Following the NSCopying protocol, object replication can be achieved. NSCopying has a method called copyWithZone: the method implements both of them.

-(id)copyWithZone:(NSZone *)zone{
    Persion *obj = [[[self class] allocWithZone:zone]init];
    obj.name = [self.name copyWithZone:zone];
    obj.age = self.age;
    return obj
}

NSZone: The parameter refers to the struct that the system uses to manage memory. Now iOS is completely negligible.

Use

First create an instance of NSMutableData to contain encoded data, and then create an instance of NSKeyed Archiver to archive objects into the NSMutableData instance.

//Used to store coded data
NSMutableData *data = [[NSMutableData alloc] init];

//Coding class
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

//Create Persion classes
Persion *teach = [[Persion alloc] init];
teach.name = @"Gavin";
teach.age = 25;

//File
[archiver encodeObject:teach forKey:@"TeachPersion"];

//Finish coding
[archiver finnishEncoding];

//Access path
 NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
 NSString *fileName = [path stringByAppendingPathComponent:@"test"];

//Write file successfully returns YES failure returns NO
BOOL success = [data writeToFile:fileName atomically:YES];


//Decode
NSData *decoderData = [[NSData alloc] initWithContentsOfFile:fileName];

NSKeyedArchiver *unarchiver = [[NSKeyedArchiver alloc] initForReadingWithData:decoderData ];

Persion *gavin = [Persion alloc] init];
gavin = [unarchiver decodeObjectForKey:@"TeachPersion"];

[unarchiver finishDecoding];

/*
Can also be used
 File [NSKeyed Archiver archive RootObject: person to File: fileName];
Decoding [NSKeyed Unarchiver unarchiveObjectWithFile: file];
*/

Note: If the classes that need to be archived are subclasses of a custom class, you need to implement the archiving and unarchiving methods of the parent class before archiving and unarchiving. That is, [super encodeWithCoder:aCoder] and [super initWithCoder:aDecoder] methods;

3. Preference Settings

Preference settings are designed to store configuration information for applications, and generally do not store other data in preference settings. If the synchronize method is not invoked, the system will be saved to the file at any time according to the I/O situation. So if you need to write to the file immediately, you must call the synchronize method. Preference settings save all data to the same file. That is, a plist file named after the package name in the preference directory.

//1. Get the NSUserDefaults file
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

//2. Write content to a file
[userDefaults setObject:@"teach" forKey:@"name"];
[userDefaults setInteger:21 forKey:@"age"];

//2.1 Immediate Synchronization
[userDefaults synchronize];

//3. Read files
NSString *name = [userDefaults objectForKey:@"name"];
NSInteger age = [userDefaults integerForKey:@"age"];

4. Embedded database (SQLite3)

SQLite3 is an embedded SQL database in iOS. SQLite3 is very effective in storing and retrieving data. It can aggregate data more complex and faster.

SQLite3 uses SQL (Structured Query Language Structured Query Language), database basic introduction can be click here Now let's look at the use of the database.

To use SQLite3, we need to connect libsqlite3.dylib's dynamic library

Use

//Access path method facilitates our access to directories
- (NSString *)dataFilePath{
    //Access path
     NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

 return [path stringByAppendingPathComponent:@"data.sqlite"];
}

- (void)testSQL3{
    //Create a database
    aqlite *database;

    //Because it's a C language library, you can't use NSString directly.
    const char *path = [self dataFilePath] UTF8String];

    //Sqlite_open (path, & database == SQLIE_OK indicates that the database was successfully opened
    if (sqlite_open(path, &database) != SQLIE_OK){
        //Failed to create database
        sqlite_close(database);
        NSAssert(0,@"Failed to open database");
    }

    //Create table
    NSString *createSQL = @"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)";
    char *errorMsg;
    if(sqlite3_exec(database,[createSQL UTF8String)], NULL, NUMM, &errorMsg) != SQLIE_OK){
        //Table creation failed
        sqlite_close(database);
        //Output error message
        NSAssert(0,@"Error creating table:%s", errorMsg);
    }

    //insert data
    //Our Persion class above creates instances to store in
    //SQL statement
     NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person (name, age) VALUES('%@', '%ld')", teach.name, teach.age];

     if (sqlite3_exec(database, sql.UTF8String, NULL, NULL, &errorMsg) != SQLIE_OK){
        //Insert failure
        NSAssert(0,@"Error inster:%s", errorMsg);
    }

    //Query data
    NSString8 *query = @"select name, age from t_person;";
    sqlite3_stmt *statement;

    //sqlite3_prepare_v2: Check the validity of sql
    if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLIE_OK){

    //Facilitate the return of each line
    //sqlite3_step(): Get the query results row by row, repeating them until the last record
    while(sqlite3_step(statement) == SQLIE_OK){

        //sqlite3_coloum_xxx(): Get the corresponding type of content, iCol corresponds to the order of fields in the SQL statement, starting from 0.
        //According to the properties of the actual query field, the corresponding content can be obtained by using sqlite3_column_xxx.
        char *name = (char *)sqlite3_column_text(statement, 0);
        NSInteger age = sqlite3_column_int(statement, 1);

        Persion *teach = [[Persion alloc] init];
        teach.name = [[NSString alloc] initWithUTF8String:name];
        teach.age = age;
    }

    //Close database connection
    sqlite_finalize()
    }

    sqlite_close(database);
}

Using SQLite3 is more cumbersome, you can use the existing framework FMDB, you can search GitHub.
Following is a summary of some of the following FMDB methods for easy reference Quote The use of FMDB can also be accessed GitHub-FMDB.

FMDB

FMDB has three main classes:

  • FMDatabase
    An FMDatabase object represents a separate SQLLite database for executing SQL statements.

  • FMResultSet
    Result Set after Query Execution Using FMDatabase

  • FMDatabaseQueue
    Used to execute multiple queries or updates in a multithreaded environment, which is thread-safe

Create a database

Like the c language framework, FMDB creates FMDatabase objects by specifying the file path of the SQLite database, but FMDB is easier to understand and use, and needs to import sqlite3.dylib before using it. Open the database as follows:

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
FMDatabase *database = [FMDatabase databaseWithPath:path];    
if (![database open]) {
    NSLog(@"Failed to open the database!");
}

It is noteworthy that the value of Path can be passed into the following three situations:

Specific file paths are automatically created if they do not exist
The empty string @", which creates an empty database in the temporary directory, is deleted when the FMDatabase connection is closed.
nil, creates an in-memory temporary database that will be destroyed when the FM Database connection is closed

To update

In FMDB, all operations except queries are called "updates", such as create, drop, insert, update, delete and so on. ExcuteUpdate: method is used to perform updates:

//Common methods are as follows3Species:   
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//Example
[database executeUpdate:@"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"];   
//perhaps  
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES(?, ?)", @"Bourne", [NSNumber numberWithInt:42]];

query

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

Thread safety

It is not advisable to use an FMDatabase instance in multiple threads at the same time. Don't let multiple threads share the same FMDatabase instance, it can't be used in multiple threads at the same time. If you use an FM database instance in multiple threads at the same time, it will cause data confusion and other problems. So use FM Database Queue, which is thread-safe. The following are the methods of use:

  • Create queues.
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
  • Usage queue
[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 your business:

[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
           *rollback = YES;  
    }];

The FM Database Queue background establishes a serialized G-C-D queue and executes the blocks you pass to the G-C-D queue. This means that you call methods from multiple threads at the same time, and GDC executes them in the order of blocks it receives.

5. Core Data, a persistence tool provided by Apple

Core Data is a stable and comprehensive persistence tool. Let's first look at the code to implement the above operation.

- (void)test{
    //Get the app proxy
    AppDelegate *appDelegate = [UIApplication shareApplication].delegate;

    //Create context
    NSManageObjectContext *context = [appDelegate manaedObjectContext];

    //Create request
    NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:@"test"];

    //storage
    NSManageObject *coreData= NSEntitydescription insertNewObjectForEntityForName:@"test" inManagedObjectContext:context];

    [coreData setValue:teach.name forKey:@"name"];
    [coreData setValue:teach.age forKey:@"age"];

    //Save context
    [appDelegate saveContext];

    //Context returns every object in the library
    NSError *error;
    NSArray *objects = [context executeFetchRequest:request error &error];

    //Convenient Array Finding
    //We only save one piece of data, so we only go to the first one.
    Persion *teach = [[Persion alloc] init];
    teach.name = [objects[0] valueForkey:@"name"];
    teach.age = [objects[0] valueForkey:@"age"];
}

It should be noted that we need to monitor the life cycle of app to ensure the stability of data when we operate data.

More detailed usage of Core Data can be seen in official documents, API is very detailed, in the creation of the program remember to check the use of Core Data.

PS

The contact lasted five hours, that is, now there is time. Some of them are afraid of introducing incompletely, and then add slowly. If you find any questions, please comment.

Posted by dirtyfrenchman on Tue, 26 Mar 2019 01:00:29 -0700