[iOS] Core Data 사용하기(Objective-C)

Core Data 사용하기(Objective-C)

iOS에서 데이터 핸들링

나는 개발하면서 데이터를 많이 다룰일이 없었다. 사용자의 데이터 정보는 서버에서 받아오는 경우가 대부분이었고, 클라이언트에 저장하는 데이터라고 해봐야.. 설정 정보 정도 였고(NSUserDefaults), 중요한 데이터들은 주로 키체인에 저장하다보니 Core Data는 프로젝트 시작할때 체크 해제 해주는 ^^; 그런 아이였다. 소스를 리팩토링 하다보니 점점 한계에 부딛치게 되었고, 그렇게 다시 패턴을 기웃거려 보고 있었다..MVC, MVVM, VIPER등등.. 그러다 문득 데이타 모델링에 대해 깊게 생각해보지 않은것 같았다. 나에게 있어 데이터 모델링은 클래스를 만들고 그안에 프로퍼티를 정의하는것 뿐..–; 데이터 모델링이 무었일까 궁금했고 CoreData를 일단 써보는자는 생각이 들었다.

Core Data 간략 정리

보통 클라이언트에서 데이터를 많이 다룰때(CRUD) SQLite를 쓰는데, 이걸 쉽게 해주는게 Core Data라고 생각하면 된다. DB에는 테이블이 있고 이걸 보통 엔티티라고 부르며 이 엔티티에는 클래스 처럼 속성이 있다. 그리고 상속도 할수 있고, 여러가지 클래스와 유사한면이 많은데 개념상 테이블과 클래스는 다르다.(사실 추상적이라서 나는 모르겠다.) 근데 이런 클래스와 테이블의 유사점을 연결해주는(?) 프레임워크가 CoreData라고 보면된다.(이런걸 ORM이라고 하나봐요.) 단순히 데이터를 읽고, 쓰는거만 생각했으면 CoreData를 쓸 생각이 별로 안들었겠지만, 데이터 사이의 관계를 정리해주고 모델링 해줄수 있다는 말에…. 훅해서 사용해 보았지만 아직까지 장점은 잘 모르겠다. 그러나 일단 데이터만 가지고 객체 아카이빙? 안하고.. 클래스가 쑤욱~ 생성이 되고, 비동기 상태에서 클래스 데이터를 들고 다녀야하는 부담이 줄어드는것 같다.(..NSInmemoryStoreType사용)

Core Data 사용법

프로젝트 생성시에 Use Core Data를 체크하면 AppDelegate에 아래와 같은 소스가 생성 된다. 여기서 NSPersistentContainer 이넘이 Core Data Stack을 잘 다루게 도와주는 아이 인것 같다.CoreDataStack

#pragma mark - Core Data stack

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTest"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // 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.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    if ([context hasChanges] && ![context save:&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();
    }
}

보통은 이 생성된 소스를 가지고 매니저 클래스 같은걸 만들어서 사용하는듯 하다. 전체 소스는 맨 아래 있음.

프로젝트를 CoreData 체크 옵션을 통해서 생성하게되면 xxx.xcdatamodeld 파일이 생성되는데 이파일이 entity에 대한 속성을 정의하는 파일이다. 속성을 적당히 넣어주고(속성 설명은 생략함), xxx.xcdatamodeld 파일을 선택해주고 XCode메뉴 Editor에서CreateNSManagedObject Subclass를 선택하면 클래스 및 관련 카테고리 파일이 자동 생성된다.

아래는 데이터 저장에 대한 애플 소스이다. 엔티티 이름을 넣고 NSManagedObject를 상속받은 AAAEmployeeMO객체를 생성한다. 이객체에 저장할 데이터를 셋팅해주고(testData부분) 저장한다.

// 파일쓰기
AAAEmployeeMO *employee = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:[self managedObjectContext];

employee.testData = @"데이터"; // 저장할 데이터 셋팅

NSError *error = nil;
if ([[self managedObjectContext] save:&error] == NO) {
    NSAssert(NO, @"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
}

데이터를 읽는 부분이다. 동일하게 엔티티 이름을 넣어주고 fetchRequest를 날리면 엔티티객체 모델에 대한 배열을 받을수 있다.

// 데이터 읽기
NSManagedObjectContext *moc = ;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
 
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
if (!results) {
    NSLog(@"Error fetching Employee objects: %@\n%@", [error localizedDescription], [error userInfo]);
    abort();
}

전체소스

아래는 내가 사용 하기위해 싱글턴으로 정리한 소스이다.

#import <CoreData/CoreData.h>

typedef NS_ENUM(NSUInteger, CoreDataStoreType) {
    InMemoryStoreType = 0,
    SQLiteStoreType = 1
};
@interface CoreDataStackHandler : NSObject
+(instancetype)sharedInstance;
@property CoreDataStoreType storeType;
@property (readonly, strong) NSPersistentContainer *persistentContainer;
- (NSArray*)fetch:(NSString*)entityName;
- (void)deleteObject:(NSManagedObject*)object;
- (NSManagedObject*)newEntityObject:(NSString*)entityName;
- (void)saveContext;
@end
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
@implementation CoreDataStackHandler
@synthesize persistentContainer = _persistentContainer;
+(instancetype)sharedInstance {
    static CoreDataStackHandler *shared = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[CoreDataStackHandler alloc] init];
        shared.storeType = SQLiteStoreType;
    });
    return shared;
}
- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {

            NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.yep.xxx"];  // 프레임워크일때 사용하기 위해 번들명으로..
            NSURL *url = [bundle URLForResource:@"DataModel파일명" withExtension:@"momd"];
            NSManagedObjectModel *manageObjectModel = [[NSManagedObjectModel alloc]initWithContentsOfURL:url];
            _persistentContainer = [[NSPersistentContainer alloc]initWithName:@"DataModel" managedObjectModel:manageObjectModel];
            if(self.storeType == InMemoryStoreType){
                NSPersistentStoreDescription *description = [[NSPersistentStoreDescription alloc]init];
                description.type = NSInMemoryStoreType;
                _persistentContainer.persistentStoreDescriptions = @[description];
            }
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // 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.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    return _persistentContainer;
}
- (NSArray*)fetch:(NSString*)entityName {
    NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entityName];
    [request setReturnsObjectsAsFaults:FALSE];
    
    NSError *error = nil;
    NSArray *results = [self.persistentContainer.viewContext executeFetchRequest:request error:&error];
    if (!results) {
        NSLog(@"Error fetching %@ objects: %@\n%@", entitiName, [error localizedDescription], [error userInfo]);
        abort();
    }
    return results;
}
-(void)deleteObject:(NSManagedObject*)object {
    [self.persistentContainer.viewContext deleteObject:object];
}
- (NSManagedObject*)newEntityObject:(NSString*)entityName {
    return [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.persistentContainer.viewContext];
}
- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    if ([context hasChanges] && ![context save:&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();
    }
}
@end

참고

댓글남기기