📜 ⬆️ ⬇️

Core Data Database Prefilling

image Often, for the iPhone / iPad applications to work, a certain “default” data set in the database is required. Unfortunately, Apple does not provide standard tools for pre-filling the application database.
If the required amount of data is small, then they can be loaded into the database during the start of the application. If you need a large amount of initial information for the application to work, then this solution will not work, forcing users to wait until all the download operations are completed - this is a moveton, and the customer, having seen how long your application is loading, can reconsider plans for future cooperation.

In this article I will tell you how you can quickly pre-fill the sqlite database of an application using Core Data.


')
The essence of the idea:
- fill the sqlite database
- add it to the application resources
(The base of the application running on the simulator is here: / Users /% UserName% / Library / Application Support / iPhone Simulator /% iOS version% / Applications /% GUID of% / Documents /)
- during the first launch of the application, do not create a database, but replace it with a default one.

In this article, the database is filled directly in the application (from the xml-file). Of course, you can fill the base with many other ways, but for that you need to understand the base scheme created by Core Data, common sense and laziness made me reject such options.

I do not like when in such articles you have to scroll through an uninteresting description of the whole process, but you just want to immediately see the source code of the example. Therefore, I say immediately: the source code of the example application here (SDK 4.1)

To see how the idea works, let's create a small application based on the Navigation-based Application template that uses Core Data. In my example, it is called CoreDataExample


We prefill the database and use it at the first start.
Let the source data is stored in the Events.xml file. Add this file to the application resources.
<Events >
<Event timeStamp = "01/16/2010" />
<Event timeStamp = "02/01/2010" />
<Event timeStamp = "03/05/2010" />
</ Events >

Generate the Event class. To do this, right-click on the file CoreDataExample.xdatamodel and select Add-> New File ...


We will have the option to create a Managed Object Class

After its creation, the Event.m and Event.h files will be generated:
//Event.h
#import <CoreData / CoreData.h>

@interface Event : NSManagedObject
{
}

@property ( nonatomic, retain ) NSDate * timeStamp;

@end

//Event.m
#import "Event.h"

@implementation Event

dynamic timeStamp;

@end


Now we will create a class that will save data to the database. Let's call it EventsRepository, and at the same time create the EventsRepositoryDelegate protocol. We will not change the way of working with data in the classes generated by the template, this topic can be developed into several independent articles, but we will use EventsRepository for our pre-filling.
//EventsRepositoryDelegate.h

#import <UIKit / UIKit.h>

@protocol EventsRepositoryDelegate

@property ( nonatomic, retain ) NSFetchedResultsController * fetchedResultsController;

@end

//EventsRepository.m

#import <Foundation / Foundation.h>
#import "EventsRepositoryDelegate.h"
#import "Event.h"


@interface EventsRepository : NSObject {

id <EventsRepositoryDelegate> delegate;

}

@property ( assign ) id <EventsRepositoryDelegate> delegate;
- ( void ) saveEventWithTimeStamp : ( NSDate * ) timeStamp;

@end

//EventsRepository.m

#import "EventsRepository.h"


@implementation EventsRepository

@synthesize delegate;

- ( void ) saveEventWithTimeStamp : ( NSDate * ) timeStamp {

NSManagedObjectContext * context = [ delegate.fetchedResultsController managedObjectContext ] ;

Event * event = [ NSEntityDescription insertNewObjectForEntityForName : @ "Event" inManagedObjectContext : context ] ;

event.timeStamp = timeStamp;

NSError * error = nil ;
if ( ! [ context save : & error ] ) {

NSLog ( @ "Unresolved error% @,% @" , error, [ error userInfo ] ) ;
abort ( ) ;
}

[ event release ] ;
}

@end



We also need a class that will parse our xml file and save data through the repository. Create the EventsXmlParser class and the EventsXmlParserDelegate protocol.
//EventsXmlParser.h

#import <Foundation / Foundation.h>
#import "EventsXmlParserDelegate.h"
#import "Event.h"


@interface EventsXmlParser : NSObject <NSXMLParserDelegate> {

id <EventsXmlParserDelegate> delegate;
}

@property ( assign ) id <EventsXmlParserDelegate> delegate;

- ( void ) saveEventWithTimeStamp : ( NSDate * ) timeStamp;

@end

//EventsXmlParser.m

#import "EventsXmlParser.h"


@implementation EventsXmlParser

@synthesize delegate;

- ( void ) parser : ( NSXMLParser * ) parser
didStartElement : ( NSString * ) elementName
namespaceURI : ( NSString * ) namespaceURI
qualifiedName : ( NSString * ) qualifiedName
attributes : ( NSDictionary * ) attributeDict {

if ( [ elementName isEqualToString : @ "Event" ] ) {

NSDateFormatter * dateFormatter = [ [ NSDateFormatter alloc ] init ] ;
[ dateFormatter setDateFormat : @ "dd.MM.yyyy" ] ;

NSDate * timeStamp = [ [ [ NSDate alloc ] init ] autorelease ] ;

timeStamp = [ dateFormatter dateFromString : ( NSString * ) [ attributeDict objectForKey : @ "timeStamp" ] ] ;

[ delegate.eventsRepository saveEventWithTimeStamp : timeStamp ] ;

[ dateFormatter release ] ;
}
}

- ( void ) dealloc {

[ super dealloc ] ;
}

@end

//EventsXmlParserDelegate.h

#import <UIKit / UIKit.h>
#import "EventsRepository.h"


@protocol EventsXmlParserDelegate

@property ( nonatomic, retain ) EventsRepository * eventsRepository;

@end


It remains only to add the use of our new classes in RootViewController.
First, RootViewController will implement both of our new protocols, and, second, we add property EventsRepository.
// full header file RootViewController.h

#import <UIKit / UIKit.h>
#import <CoreData / CoreData.h>
#import "EventsXmlParserDelegate.h"
#import "EventsRepositoryDelegate.h"
#import "EventsXmlParser.h"
#import "EventsRepository.h"

@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate, EventsXmlParserDelegate, EventsRepositoryDelegate> {

@private
NSFetchedResultsController * fetchedResultsController_;
NSManagedObjectContext * managedObjectContext_;
EventsRepository * eventsRepository_;
}

@property ( nonatomic, retain ) NSManagedObjectContext * managedObjectContext;
@property ( nonatomic, retain ) NSFetchedResultsController * fetchedResultsController;
@property ( nonatomic, retain ) EventsRepository * eventsRepository;

@end

// and what you need to remember to add in RootViewContorller.m for property eventsRepository
...
@synthesize eventsRepository = eventsRepository_;
...
- ( EventsRepository * ) eventsRepository {

if ( eventsRepository_ ! = nil ) {
return eventsRepository_;
}

eventsRepository_ = [ [ EventsRepository alloc ] init ] ;
eventsRepository_.delegate = self;

return eventsRepository_;
}
...

- ( void ) dealloc {
[ eventsRepository_ release ] ;
[ fetchedResultsController_ release ] ;
[ managedObjectContext_ release ] ;
[ super dealloc ] ;
}


Prefill the database. To do this, add the populateDataBase method to the RootViewController and call it on the viewDidLoad.
- ( void ) populateDataBase {

NSLog ( @ "populate" ) ;

NSURL * url = [ NSURL fileURLWithPath : [ [ NSBundle mainBundle ] pathForResource : @ "Events" ofType : @ "xml" ] ] ;
NSXMLParser * xmlParser = [ [ NSXMLParser alloc ] initWithContentsOfURL : url ] ;

EventsXmlParser * eventsXmlParser = [ EventsXmlParser new ] ;
[ eventsXmlParser setDelegate : self ] ;

[ xmlParser setDelegate : eventsXmlParser ] ;

BOOL success = [ xmlParser parse ] ;

if ( success )
NSLog ( @ "Data Base has been populated" ) ;
else
NSLog ( @ "Error: Data Base hasn't been populated" ) ;

[ eventsXmlParser release ] ;
[ xmlParser release ] ;
}


Run the application - the database is full. Copy the created database into the application directory and add the database file to the project (for example, in the Resources group) (remember where it is / Users /% UserName% / Library / Application Support / iPhone Simulator /% iOS version% / Applications /% GUID% / Documents /) and remove the call to the function populateDataBase - we no longer need to pre-fill the base of their xml-file. Remove the application from the simulator, for this you can either bang the application folder in / Users /% UserName% / Library / Application Support / iPhone Simulator /% iOS version% / Applications /, or delete it via the simulator (deleted as on the device) - hold down the left button click on the application icon, let go, when the icon starts to oscillate, click the appeared cross - delete.

It remains for us to tell our application not to create a database on the first launch, but to copy the base we created to its place.
To do this, go to the CoreDataExampleAppDelegate class and in the persistentStoreCoordinator getter, replace the string
NSURL * storeURL = [ NSURL fileURLWithPath : [ [ self applicationDocumentsDirectory ] stringByAppendingPathComponent : @ "CoreDataExample.sqlite" ] ] ;

on
NSString * storePath = [ [ self applicationDocumentsDirectory ] stringByAppendingPathComponent : @ "CoreDataExample.sqlite" ] ;

NSFileManager * fileManager = [ NSFileManager defaultManager ] ;

if ( ! [ fileManager fileExistsAtPath : storePath ] ) {

NSString * defaultStorePath = [ [ NSBundle mainBundle ] pathForResource : @ "CoreDataExample" ofType : @ "sqlite" ] ;
if ( defaultStorePath ) {
[ fileManager copyItemAtPath : defaultStorePath toPath : storePath error : NULL ] ;
}
}

NSURL * storeURL = [ NSURL fileURLWithPath : storePath ] ;


Hope this method will help you. I would be glad to see in the comments your solutions to such problems.

Source: https://habr.com/ru/post/108229/


All Articles