Часто для работы 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. Добавим этот файл в ресурсы приложения.
Сгенерируем класс Event. Для этого щелкаем правой кнопкой мыши по файлу CoreDataExample.xdatamodel и выбираем Add->New File…
У нас появится опция создать Managed Object Class
После его создание будут сгенерированы файлы Event.m и Event.h:
Теперь создадим класс, который будет сохранять данные в базу. Назовем его EventsRepository, заодно создадим протокол EventsRepositoryDelegate. Менять в сгенерированных шаблоном классах мы способ работы с данными не будем, эту тему можно развить на несколько самостоятельных статей, но для нашего предзаполнения будем использовать EventsRepository.
Еще нам понадобится класс, который будет парсить наш xml-файл и сохранять данные через репозиторий. Создадим класс EventsXmlParser и протокол EventsXmlParserDelegate.
Осталось только добавить использование наших новых классов в RootViewController.
Во-первых, RootViewController будет реализовывать оба наших новых протокола, и, во-вторых, добавим property EventsRepository.
Предзаполним базу. Для этого добавим в RootViewController метод populateDataBase и вызовем его на viewDidLoad.
Запустим приложение — база заполнится. Скопируем созданную базу в директорию приложение и добавим файл базы в проект (например в группу Resources) (напоминаю где она лежит /Users/%ИмяПользователя%/Library/Application Support/iPhone Simulator/%версия iOS%/Applications/%GUID приложения%/Documents/) и уберем вызов фукции populateDataBase — предзаполнять базу их xml-файла нам больше не нужно. Удалим приложение из симулятора, для этого можно либо грохнуть папку приложения в /Users/%ИмяПользователя%/Library/Application Support/iPhone Simulator/%версия iOS%/Applications/, либо удалить через симулятор (удаляется как на устройстве) — зажимаем левую кнопку мыши на иконке приложения, отпускаем, когда иконка начинает колебаться, нажимаем появившийся крестик — удаляем.
Нам осталось сказать нашему приложению не создавать базу данных при первом запуске, а копировать на ее место созданную нами базу.
Для этого идем в класс CoreDataExampleAppDelegate и в геттере persistentStoreCoordinator заменяем строку
на
Надеюсь, что этот способ поможет вам. Буду рад увидеть в комментраиях ваши решения подобных задач.
Если необходимое количество данных невелико, то их можно подгрузить в базу во время старта приложения. Если же вам для работы приложения нужен большой объем исходной информации, то такое решение не подойдет, заставлять ждать пользователей пока закончатся все операции подгрузки — это моветон, да и заказчик, увидев как долго загружается ваше приложение, может пересмотреть планы по будущему сотрудничеству.
В этой статье я расскажу как можно быстро предзаполнить 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];
Надеюсь, что этот способ поможет вам. Буду рад увидеть в комментраиях ваши решения подобных задач.