[iOS] CoreData singleton sample

도움 받은 곳 : http://natashatherobot.com/ios-core-data-singleton-example/

 

db가 필요한 예제 만들다가 sqlite 클래스 만들기 귀찮아서 이 참에 core data를 써봐야겠다 싶어서 이 예제를 만들었다.

 

 

#import <Foundation/Foundation.h>

@interface DBManager : NSObject

+ (DBManager*) sharedInstance;

- (NSFetchedResultsController*) fetchAllWithClassName:(NSString*)className
											cacheName:(NSString*)cacheName;

- (NSFetchedResultsController *)fetchEntitiesWithClassName:(NSString *)className
                                           sortDescriptors:(NSArray *)sortDescriptors
                                        sectionNameKeyPath:(NSString *)sectionNameKeypath
                                                 predicate:(NSPredicate *)predicate;

- (void)saveWithBlock:(void (^)(BOOL saved, NSError *error))savedBlock;

- (void)deleteEntity:(NSManagedObject *)entity;

- (id)createEntityWithClassName:(NSString *)className
           attributesDictionary:(NSDictionary *)attributesDictionary;
@end

 

#import "DBManager.h"

@interface DBManager()

@property (strong, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (strong, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (strong, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

@end

@implementation DBManager

static DBManager *_instance;

+(DBManager*) sharedInstance
{
	if (!_instance)
	{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _instance = [DBManager new];
        });
    }

    return _instance;
}

-(id) init
{
	self = [super init];
	if (self)
	{
		[self connect];
	}

	return self;
}

-(void)connect
{	
	NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"TestCoreData" withExtension:@"momd"];
    self.managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
	self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

	NSURL *documentsDirectory =[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
	NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:@"TestCoreData3.sqlite"];

	NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption,
                             nil];

	NSError *error = nil;
    if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
													   configuration:nil URL:storeURL
															 options:options error:&error]) {
        NSLog(@"===============\nUnresolved error %@, %@", error, [error userInfo]);
        abort();
    }
	else
	{
		self.managedObjectContext = [NSManagedObjectContext new];
		[self.managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
	}
}

- (NSFetchedResultsController*) fetchAllWithClassName:(NSString*)className
											cacheName:(NSString*)cacheName
{
	NSFetchRequest *fetchRequest = [NSFetchRequest new];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

	[fetchRequest setFetchBatchSize:20];

	NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"createdAt" ascending:NO];
	NSArray *sortDescriptors = @[sortDescriptor];
	[fetchRequest setSortDescriptors:sortDescriptors];

	NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
																							   managedObjectContext:self.managedObjectContext
																								 sectionNameKeyPath:nil
																										  cacheName:cacheName];

	NSError *error = nil;
	if (![fetchedResultsController performFetch:&error]) {
		// Replace this implementation with code to handle the error appropriately.
		// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
	    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
	    abort();
	}

	return fetchedResultsController;
}

- (NSFetchedResultsController *)fetchEntitiesWithClassName:(NSString *)className
                                           sortDescriptors:(NSArray *)sortDescriptors
                                        sectionNameKeyPath:(NSString *)sectionNameKeypath
                                                 predicate:(NSPredicate *)predicate
{
    NSFetchedResultsController *fetchedResultsController;
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:className
                                              inManagedObjectContext:self.managedObjectContext];
    fetchRequest.entity = entity;
    fetchRequest.sortDescriptors = sortDescriptors;
    fetchRequest.predicate = predicate;

    fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
																   managedObjectContext:self.managedObjectContext
																	 sectionNameKeyPath:sectionNameKeypath
																			  cacheName:nil];

    NSError *error = nil;
    BOOL success = [fetchedResultsController performFetch:&error];

    if (!success) {
        NSLog(@"fetchManagedObjectsWithClassName ERROR: %@", error.description);
    }

    return fetchedResultsController;
}

- (id)createEntityWithClassName:(NSString *)className
           attributesDictionary:(NSDictionary *)attributesDictionary
{
    NSManagedObject *entity = [NSEntityDescription insertNewObjectForEntityForName:className
															inManagedObjectContext:self.managedObjectContext];
	NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithDictionary:attributesDictionary];
	[dictionary setValue:[NSDate date] forKey:@"createdAt"];
	[dictionary setValue:[NSDate date] forKey:@"updatedAt"];

    [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
        [entity setValue:obj forKey:key];
    }];

    return entity;
}

- (void)saveWithBlock:(void (^)(BOOL saved, NSError *error))savedBlock
{
    NSError *saveError = nil;

    savedBlock([self.managedObjectContext save:&saveError], saveError);
}

- (void)deleteEntity:(NSManagedObject *)entity
{
    [self.managedObjectContext deleteObject:entity];
}

@end

 

일단 이렇게 하면 어느 정도 필요한 건 다 된 것 같다.

그냥 sqlite 랑 비슷하다고만 생각했는데, 개념이 많이 달라서 고생 좀 했다 ㅠㅠ

보통의 db는 select하고, insert하는 걸 바로바로 해줘야하는데, 얘는 그런거 없이 배열 정보로 갖고 있다가 한방에 저장시켜 버린다.

그래서 core data 예제를 만들면 앱이 꺼질 때 딱 한 번 save를 한다….

이거 때문에 아주 많이 헤맸다.

 

근데 이거 테이블 많아지면서, 관계설정 들어가기 시작하면 감당 안되지 싶은데 ;;;;;

 

 

 

// fetch all
//self.fetchedResultsController = [self.dbManager fetchAllWithClassName:@"Event" cacheName:@"Master"];

// fetch with sort
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor];
self.fetchedResultsController = [[DBManager sharedInstance] fetchEntitiesWithClassName:@"Event" sortDescriptors:sortDescriptors sectionNameKeyPath:nil predicate:nil];

self.fetchedResultsController.delegate = self;

// count
NSLog(@"count : %d", self.fetchedResultsController.fetchedObjects.count);

// get one
NSManagedObject *object = [self.fetchedResultsController.fetchedObjects objectAtIndex:0];
NSLog(@"info : %@", [object valueForKey:@"name"]);

select 는 이렇게 하면 된다.

where 절에 해당하는 predicate는  NSPredicate 로 검색하는게 나을 것 같다.

보통의 where와는 많이 달라서 테스트를 해보는게 좋을 듯…..

 

 

NSManagedObject *object = [self.fetchedResultsController.fetchedObjects objectAtIndex:0];
[object setValue:self.nameField.text forKey:@"name"];

fetchedResultsController에서 NSManagerObject를 가져오고, 그거의 특정 key에 해당하는 value를 바꿔주면 된다.

NSDictionary와 사용법은 같다고 생각하면 이해하기 편하다.

 

 

NSMutableDictionary *dictionary = [NSMutableDictionary new];
[dictionary setValue:self.nameField.text forKey:@"name"];
[[DBManager sharedInstance] createEntityWithClassName:@"Event" attributesDictionary:dictionary];

Event 모델의 name에 값을 넣어서 배열에 저장하는 부분이다.

보통의 db였다면 이 부분에서 insert 쿼리가 실행됐을 테지만, core data는 그런거 없다.

 

 

[[DBManager sharedInstance] saveWithBlock:^(BOOL saved, NSError *error) {
	if (!saved)
		NSLog(@"error : %@", error);
	else
		[self.navigationController popViewControllerAnimated:YES];
}];

실제로는 이런 식으로 save를 날려줘야 저장된다.

이러면 fetchedResults 에 변경된 놈, 추가된 놈, 수정된 놈들의 쿼리가 날아간다.

특히 이 부분 때문에 이해하기 정말 힘들었다.

 

 

전체 소스는 https://github.com/susemi99/TestCoreData 에 올려뒀다.