Pull to refresh

Предзаполнение базы данных Core Data

Reading time 8 min
Views 5.1K
image Часто для работы iPhone/iPad приложений необходим некоторый «дефолтовый» набор данных в базе. К сожалению, Apple стандартных средств предзаполнения базы приложения разработчикам не предоставляет.
Если необходимое количество данных невелико, то их можно подгрузить в базу во время старта приложения. Если же вам для работы приложения нужен большой объем исходной информации, то такое решение не подойдет, заставлять ждать пользователей пока закончатся все операции подгрузки — это моветон, да и заказчик, увидев как долго загружается ваше приложение, может пересмотреть планы по будущему сотрудничеству.

В этой статье я расскажу как можно быстро предзаполнить sqlite базу приложения, использующего Core Data.



Суть идеи:
— заполнить базу данных sqlite
— добавить ее в ресурсы приложения
(База приложения, работающего на симуляторе, находится тут: /Users/%ИмяПользователя%/Library/Application Support/iPhone Simulator/%версия iOS%/Applications/%GUID приложения%/Documents/)
— во время первого запуска приложения не создавать базу данных, а подменить ее дефолтовой.

В этой статье база заполняется непосредственно в приложении (из xml-файла). Заполнить базу, конечно, можно и множеством других способов, но для этого нужно разбираться в схеме базы, созданной Core Data, здравый смысл и лень заставили меня отказаться от таких вариантов.

Не люблю, когда в подобных статьх приходится пролистывать неинтересное описание всего процесса, а хочется просто сразу посмотреть исходный код примера. Поэтому говорю сразу: исходный код приложения-примера тут (SDK 4.1)

Чтобы посмотреть как работает идея, создадим небольшое приложение на основе шаблона Navigation-based Application, которое использует Core Data. В моем примере оно называется CoreDataExample


Предзаполним базу и используем ее при первом старте.
Пусть исходные данные у нас хранятся в файле Events.xml. Добавим этот файл в ресурсы приложения.
<Events>
    <Event timeStamp="16.01.2010" />
    <Event timeStamp="01.02.2010" />
    <Event timeStamp="05.03.2010" />
</Events>

Сгенерируем класс Event. Для этого щелкаем правой кнопкой мыши по файлу CoreDataExample.xdatamodel и выбираем Add->New File…


У нас появится опция создать Managed Object Class

После его создание будут сгенерированы файлы Event.m и Event.h:
//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


Теперь создадим класс, который будет сохранять данные в базу. Назовем его EventsRepository, заодно создадим протокол EventsRepositoryDelegate. Менять в сгенерированных шаблоном классах мы способ работы с данными не будем, эту тему можно развить на несколько самостоятельных статей, но для нашего предзаполнения будем использовать EventsRepository.
//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
 
 


Еще нам понадобится класс, который будет парсить наш xml-файл и сохранять данные через репозиторий. Создадим класс EventsXmlParser и протокол EventsXmlParserDelegate.
//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


Осталось только добавить использование наших новых классов в RootViewController.
Во-первых, RootViewController будет реализовывать оба наших новых протокола, и, во-вторых, добавим property EventsRepository.
//целиком заголовочный файл 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
 
//и что нужно не забыть добавить в RootViewContorller.m для 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];
}


Предзаполним базу. Для этого добавим в RootViewController метод populateDataBase и вызовем его на 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];
}
 


Запустим приложение — база заполнится. Скопируем созданную базу в директорию приложение и добавим файл базы в проект (например в группу Resources) (напоминаю где она лежит /Users/%ИмяПользователя%/Library/Application Support/iPhone Simulator/%версия iOS%/Applications/%GUID приложения%/Documents/) и уберем вызов фукции populateDataBase — предзаполнять базу их xml-файла нам больше не нужно. Удалим приложение из симулятора, для этого можно либо грохнуть папку приложения в /Users/%ИмяПользователя%/Library/Application Support/iPhone Simulator/%версия iOS%/Applications/, либо удалить через симулятор (удаляется как на устройстве) — зажимаем левую кнопку мыши на иконке приложения, отпускаем, когда иконка начинает колебаться, нажимаем появившийся крестик — удаляем.

Нам осталось сказать нашему приложению не создавать базу данных при первом запуске, а копировать на ее место созданную нами базу.
Для этого идем в класс CoreDataExampleAppDelegate и в геттере persistentStoreCoordinator заменяем строку
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"CoreDataExample.sqlite"]];

на
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];


Надеюсь, что этот способ поможет вам. Буду рад увидеть в комментраиях ваши решения подобных задач.

Tags:
Hubs:
+6
Comments 12
Comments Comments 12

Articles