20120102 :: [iOS] 저장이 가능한 간단 메모장 3 (코어데이터)

해당 포스트의 코드가 너무 부끄러운 수준으로 디자인이 잘못돼 있어서 새롭게 작성한 글이 있으니 이 글을 참고해주세요.

이미 세 개의 포스팅(관련글 1, 관련글 2, 관련글 3)을 통해 간단한 메모장 앱을 만드는 방법을 살펴보았는데, 이번에는 완전히 똑같은 앱을 코어데이터를 사용하여 생성하는 방법에 대해 살펴보도록 하겠다. 이 시리즈의 맨 처음에 코어데이터에 대해 언급하면서 초보자에게는 좀 많이 어렵다고 이야기한 바 있는데, 이는 실제로 코어데이터가 사용하기 어려운 프레임워크라는 의미라기보다는 코어데이터를 실제로 사용하기 위해서 알고 있어야 하는 배경 지식이 상당히 많다는 의미라고 보는 것이 정확할 듯 하다.

이전 글들에서 언급한 바와 같이 코어데이터는 파일이나 데이터베이스등 저장장치에 사용자가 작성/편집한 데이터를 읽고 쓰도록하는 것을 ‘보다 쉽게’ 만들어주는 프레임워크이다.

이 데이터들은 외부 파일에 바이너리(아카이빙된 데이터)로 기록될 수도 있고, 데이터베이스(SQLite3) 형태로 저장될 수도 있다. 코어데이터는 이렇게 영구 저장소에 저장되는 데이터를 직접 데이터베이스를 다루는 방법을 몰라도 사용할 수 있게 (그것도 매우 쉽게) 해준다. 다만 데이터와 데이터 입출력을 다루는 부분을 담당하는 코어데이터 구조의 각 부분에 대한 약간의 이해가 필요하다.

코어데이터의 구조

코어데이터는 데이터베이스 파일에서부터 자료를 읽어와 이를 미리 디자인한 데이터 모듈 클래스의 인스턴스로 만들고, 또 편집되거나 새로 생성된 데이터 모듈의 내용을 데이터 베이스에 저장하는 작업을 내부적으로 처리해주는 마법의 상자 같은 것으로 이해하면 된다. 실제로 코어데이터는 진짜 마술 같이 보이는 경우가 많은데, 이는 코어데이터 자체가 여러 층으로 구성되어 있고, 이 층들이 서로 유기적으로 맞물려 돌아가도록 설계되어 있기 때문이다. 코어데이터는 개발자와 영구저장소 (하드디스크 혹은 원격 서버상의 저장 파일)사이의 간격을 메꾸는 다층의 구조인데, 대략 다음과 같은 순서로 내려간다.

  • 프로그래머
  • 관리객체
  • 관리객체 모델
  • 관리 객체 컨텍스트
  • 영구저장소 코디네이터
  • 영구저장소

이상의 구조를 살펴보자면 프로그래머는 데이터베이스에 저장된 각 레코들을 관리되는 객체 형태로 다루게 된다. 즉 DB의 어떤 하나의 레코드가 있다고 가정할 때, 이 레코드가 하나의 객체와 같이 동작하는 것이다. 레코드의 각 필드는 객체의 인스턴스 변수처럼 다뤄지게 된다. 이러한 단일 레코드에 대응되는 객체를 관리 객체(managed object 로 직역하면 “관리되는 객체”가 더 맞는 표현이겠다.)라고 한다.

관리 객체는 앱을 만드는 과정에서 관리 객체 모델을 디자인하고 이 모델을 클래스화 한 것의 인스턴스이다. 즉 관리 객체 모델은 DB 입장에서보자면 테이블의 구성을 나타내는 스키마와 같다고 볼 수 있다.

관리 객체와 관리 객체 모델은 관리 객체 컨텍스트와 밀접한 연관을 맺고 있다. 관리 객체 컨텍스트(이하 컨텍스트)는 코어데이터의 핵심이 되는 모듈이다. 개발자는 디자인된 관리 객체에 어떤 정보들이 담겨 있는지를 알고 있는 상태에서 컨텍스트를 사용해 객체를 저장하거나 영구저장소로부터 불러오게 된다.

실제로 데이터를 읽고 쓰는 작업은 모두 컨텍스트를 통해 이뤄진다. 또한 이 컨텍스트는 그 아래쪽 부분에 있는 영구 저장소와 영구 저장소 코디네이터와도 밀접하게 연관되어 있다. 하지만 작업의 복잡도를 줄이기 위해 모든 것은 컨텍스트가 알아서 관리하게 될 뿐이므로, 실제 개발자는 컨텍스트를 다루기만 하면 된다.

영구저장소 코디네이터는 영구저장소의 파일 위치와 그 파일에 대한 실제 입출력을 담당한다. 또한 코디네이터는 하나의 컨텍스트와 관련을 맺고 있게 되어 그 컨텍스트로부터 입력 혹은 출력에 대한 요청을 받아 처리하게 된다. 따라서 개발자가 실제 영구저장소 코디네이터를 직접 조작할 일은 거의 없다.

드물게 코어데이터 저장소의 형식을 DB가 아닌 아카이빙 바이너리로 변경하거나 하는 경우를 제외하면 코디네이터와 영구저장소 자체를 코드에서 변경할 일은 없다. 그저 개념적으로 존재하는 DB 파일에 대한 API라고 생각하면 된다.

데이터 모델에 대한 디자인 – 엔티티

코어데이터에서 사용되는 데이터 모델을 엔티티라고 한다. 하나의 엔티티 역시 하나의 데이터베이스 테이블과 비슷하다. 엔티티는 속성(Attribute)과 관계(Relationship)라는 요소로 이루어진다. 속성은 엔티티가 가지고 있는 각 정보들의 필드라 생각하면 된다. 마찬가지로 코어데이터는 이 엔티티의 구조와 비슷한 방식으로 데이터 베이스 파일을 만들어 정보를 기록하게 된다. 또한 관계라는 속성도 가지고 있는데, 엔티티가 2개 이상인 경우에 두 엔티티 사이의 관계를 정의하는 항목이다. 두 개의 테이블이 있는 데이터베이스에서 두 테이블을 join하는 수단이라고 이해해도 무방할 정도로 둘은 닮아 있다.

엔티티는 Xcode 상에서 시각적으로 편집할 수 있게 되며, 이렇게 생성된 각 엔티티는 클래스 파일로 생성되어, 이를 반입하여 데이터 모델 객체를 사용하면 된다.

엔티티를 대표하는 클래스인 NSEntityDescription을 빼놓을 뻔 했다. NSEntityDescription은 임의의 엔티티를 표현하는 추상 클래스이다. Entity를 설명해준다는 의미가 있는데, 이 때의 설명(Description)이 바로 엔티티의 각 속성(attribute)과 관계(relationship)이다.

NSEntityDescription은 새로운 엔티티의 인스턴스를 어떤 컨텍스트에 만들 때 사용하게 된다. 신규 레코드를 이 객체를 통해 생성하면 생성된 객체를 컨텍스트가 인식하고 추적하게 된다.

신규 프로젝트 생성

이번에도 빈 프로젝트에서 시작해보고자 한다. 코어데이터를 사용하고, 스토리보드를 사용할 것이다. 신규 프로젝트에서 Empty Application으로 새로운 앱을 만든다. 생성시 Use Coredata에 반드시 체크한다. 코어데이터를 수동으로 반입할 수도 있지만, 상당히 많은 양의 코드를 작성해야 하므로 그냥 이 방법을 사용하는 것이 최선이 될 것이다.

또한, 스토리 보드를 사용할 것이므로 앱 델리게이트의 초기화 함수에서 UI를 그리는 코드를 제거해 둔다.

메모 데이터의 구조

메모 앱이 워낙 간단하니, 데이터 구조 자체는 크게 특별할 것이 없다.메모의 제목과 본문 (문자열), 그리고 최초 생성한 시각 및 최종 수정한 시각 (NSDate)의 속성 정도면 될 것이다.

Xcode에서 데이터모델 파일을 클릭하면 엔티티 편집기가 열리는데, 하단의 Add Entity 버튼을 클릭해서 엔티티를 추가한다. 엔티티의 이름은 클래스와 같이 대문자로 시작하도록 한다. 이제 오른쪽의 Attributes 매트릭스에서 Add attribute 버튼을 클릭해서 새 속성을 추가한다.각 속성과 유형은 다음과 같이 지정한다.

  • title (string)
  • memo (string)
  • creationDate (date)
  • lastModifiedDate (date)

이제 이렇게 생성된 엔티티로부터 데이터클래스를 생성한다. 데이터 클래스는 NSManagedObjectModel클래스의 서브 클래스가 되며, 각 속성을 인스턴스 변수로 가지게 된다. 수동으로 만들어도 되지만, 간략한 작업을 위해 메뉴의 Editor > Create NSManagedObjectClass… 를 선택하고 Memo 엔티티를 선택해 클래스 파일을 생성한다. (만약, 엔티티 편집기에서 Memo 엔티티를 선택한 상태라면 그냥 바로 생성된다)

Memo.h, Memo.m 파일이 만들어지는 것을 확인할 수 있다. 코드를 살펴보면 각각의 속성이 프로퍼티로 설정되어 있는 것을 볼 수 있고 특이하게 @systhesize를 사용하지 않고  @dynamic 으로 구현부에 프로퍼티가 생성되어 있음을 확인할 수 있다. 이는 접근자가 런타임에 동적으로 생성된다는 의미이다.

UI 만들기

새 스토리보드를 하나 생성한다. 테이블뷰 컨트롤러 하나를 추가하고 메뉴의 Editor > Embed > Navigation controller를 선택해 네비게이션이 가능한 구조를 만든다. 테이블 뷰 컨트롤러의 네비게이션 바에 바 버튼을 하나 추가하고, 유형을 Compose로 한다.

여기에 뷰 컨트롤러를 하나 더 추가한다. 추가한 다음 테이블 뷰에서 추가한 Compose 버튼에서 드래그하여 새로 추가한 뷰 컨트롤러로 끌어온다.

각각의 뷰 컨트롤러는 RootViewController와 DetailViewController가 될 것이다. 따라서 두 뷰 컨트롤러의 실제 파일을 추가하자.

Prefix Header

본 프로젝트에서는 거의 모든 클래스에서 앱 델리게이트를 인스턴스로 만든다. 또한 Memo 클래스 역시 대부분의 클래스에서 사용된다. 각 클래스의 헤더파일에서 델리게이트와 메모 데이터 객체를 import 할 수 있지만, 타이핑의 수고를 줄이기 위해 prefix.pch 파일에 이 두 파일을 반입하는 코드를 넣어두면 같은 코드를 반복하여 입력해야 하는 수고를 줄일 수 있다.

// prefix.pch
#import "appDelegate.h"
#import "Memo.h"

루트 뷰 컨트롤러

RootViewController라는 이름으로 UITableViewController를 하나 생성한다. 우선은 수정할 것이 없으므로 그대로 둔다. 대신 스토리보드에서 테이블 뷰의 클래스 속성을 RootViewController로 변경해준다.

디테일 뷰 컨트롤러

DetailViewController라는 이름으로 UIViewController를 하나 생성한다. 스토리보드에서 뷰 컨트롤러의 클래스 속성을 DetailViewController로 변경한다. 다음 이 뷰 컨트롤러에 텍스트 필드하나와 텍스트 뷰를 추가한다. Assistant Editor를 열어 스토리 보드에서 코드 창으로 드래그하여 각각의 아울렛을 추가하자. 이름은 각각 title, memo  정도로 주면 되겠다.

새 메모를 추가하는 동작

새로운 메모를 생성하게 되면 새 메모 객체(데이터 모델)가 생성되고 작성된 정보는 해당 객체에 담겨져서 코어데이터 컨텍스트로 저장된다. 이 글에서는 새롭게 생성된 메모나 현재 리스트에서 선택된 메모 객체를 앱델리게이트가 소유하는 것으로 디자인했는데1 추후에 새로 쓴 글에서는 리스트 뷰 컨트롤러가 이를 가지고 있고, 상세/편집 뷰로 전환시에 이를 전달하도록 바꾸었다.

앱델리게이트

새 메모를 추가하는 동작을 구현해보겠다. 먼저 앱 델리게이트는 현재 만들어진 메모들을 담고 있는 배열과 선택된 메모의 인덱스 번호를 저장하는 프로퍼티를 하나 추가해야 한다.

// AppDelegate.h
...
@interface .....

@property (strong, nonatomic) NSMutableArray *memoArray;
@property (assign, readwrite) int newIndex;

@end

구현부에서는 앱 초기 로딩시에 newIndex 값을 특정한 값으로 고정하도록 한다.

//AppDelegate.m

@implement ....

@synthesize memoArray, newIndex;

-(BOOL)ApplicationDidFinishLaunchingWithOptions... {
    memoArray = [[NSMutableArray alloc] init];
    newIndex = -1;
}

디테일뷰

디테일뷰에서는 뷰가 표시될 때 새로운 메모인지를 확인하고, 새 메모인 경우에는 관리객체 컨텍스트에 새로운 관리객체를 추가한다. 다시 리스트로 돌아갈 때 컨텍스트의 변경사항을 저장해 준다.2

먼저 현재 편집되는 메모 객체를 나타내는 인스턴스 변수가 하나 있어야 한다. 그리고 앱 델리게이트에 접근할 수 있어야 하므로 앱 델리게이트도 인스턴스를 하나 만들도록 한다.

// DetailViewController.h

@interface DetailViewController : UIViewController
{
    AppDelegate *appDelegate;
    Memo *currentMemo;
    NSManagedObjectContext *managedObjectContext;
}

@end

구현부에서는 먼저 뷰가 로드되었을 때, 새로운 메모인지 파악하고 새 메모인 경우 새로운 관리 객체를 생성하는 코드가 필요하다.

// DetailViewController.m
-(void)viewDidLoad {
    [super viewDidLoad];
    appDelegate = [[UIApplication sharedApplication] delegate];
    managedObjectContext = [appDelegate managedObjectContext];

    if([appDelegate newIndex] == -1]) {

        currentMemo = [NSEntityDescription 
                insertNewObjectForEntityForName:@"Memo" 
                         inManagedObjectContext:managedObejctContext];
        [currentMemo setCreationDate:[NSDate date]];
    }
}

이제 back 버튼을 눌러 리스트로 돌아가는 시점에 새로 생성된 메모가 저장되도록 하자.

이 부분의 코드는 실질적으로는 리스트뷰에서 호출한 디테일뷰가 종료될 때 발생하는 이벤트를 처리하는 것과 동일하므로 앱 델리게이트가 아닌, 리스트뷰 컨트롤러에서 처리하는 것이 옳다.

-(void)viewWillDisappear {
    [currentMemo setTitle:[[self title] text]];
    [currnetMemo setMemo:[[self memo]text]];
    [currentMemo setLastModifiedDate:[NSDate date]];

    [[appDelegate memoArray] insertObject:currentMemo atIndex:0]
        [appDelegate setNewIndex:-1];

    [appDelegate saveContext];
}

메모 리스트 표시

이제 만들어진 메모가 리스트에 표시되도록 한다. 실제 생성된 메모들은 앱 델리게이트의 memoArray  배열에 추가되므로 루트 뷰 컨트롤러에서는 이 배열을 참고하여 데이터를 뿌려주면 된다.

스토리보드

먼저 스토리보드에서 RootViewController의 테이블 뷰에 있는 프로토타입 셀을 선택한다. 코드를 절약하기 위해 디폴트 디자인을 사용한다. 프로토타입 셀의 유형이 Custom으로 되어 있는데 Right Detail로 바꾼다. 그럼 셀의 레이블이 2개가 좌우로 표시되는 기본 스타일의 셀로 프로토 타입 셀이 변경된다. 또한 셀의 Reuse Indentifier란에는 Cell 이라고 적어준다.

스토리보드에 변경 점을 꼭 저장하고 RootViewController.h 파일로 넘어가도록 한다.

RootViewControll.h

헤더파일에서는 메모 전체 리스트를 담는 배열과 앱 델리게이트 인스턴스를 선언한다. 또한 추가/삭제 등으로 메모 전체 리스트가 변경될 때 이 배열을 원래 데이터와 동기화하는 메소드도 하나 선언해 준다.

@interface RootViewController : UITableViewController
{
    NSArray *memoListArray;
    AppDelegate *appDelegate;
}

-(void)refreshListArray;
@end

RootViewController.m

지난 강좌에서와 똑같이 루트 뷰 컨트롤러는 주로 테이블 뷰에 필요한 정보를 뿌려주는 역할을 하면 된다. 메모 리스트는 섹션이 1개로 항상 고정되어 있으며, 메모의 개수만큼 셀의 개수를 준비한다. 단, 추가나 삭제의 경우를 대비해 셀의 개수를 정하기 전에 앱 델리게이트의 memoArray와 루트 뷰의 memoListArray를 동기화한다.

-(void)refreshListArray
{
    memoListArray = [appDelegate.memoArray copy];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    [self refreshListArray];
    return [memoListArray count];
}

여기서는 메모의 최종 수정일을 표시할 것인데, 최종 수정일은 NSDate 객체로 표현된다. 따라서 날짜 포매터를 사용하여 날짜 객체를 문자열로 변환하는 방법을 포함해야 한다.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell...
    [cell.textLabel setText:[[memoListArray objectAtIndex:indexPath.row] title]];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateStyle:NSDateFormatterShortStyle];

    [cell.detailTextLabel setText:[dateFormatter stringFromDate:[(Memo*)[memoListArray objectAtIndex:indexPath.row]lastModifiedDate]]];

    return cell;
}

저장된 메모 읽어오기

여기까지 만든 내용에서는 새 메모를 추가하고 추가한 메모는 저장되며, 리스트에 반영된다. 하지만 아직 저장된 메모를 읽어오는 내용을 추가하지 않았다. 저장된 데이터를 읽어오는 방식은 조금 복잡해 보일 수도 있다. 이는 DB를 사용하는 경우 쿼리를 보내는 것과 마찬가지로 컨텍스트에 요청서 객체를 만들어 요청을 수행하면 된다. 단 DB를 직접 사용하는 경우에는 그 결과를 루프를 돌면서 하나씩 생성해야 하는데 비해, 코어데이터에서는 요청서만 정성스레(?) 만들어 주면 원하는 결과를 한꺼번에 배열로 만들어 받을 수 있다는 장점이 있다.

요청서를 만드는 절차는 다음과 같다.

  • 요청서(NSRequsetFetch)의 인스턴스를 만든다.
  • NSEntityDescription을 사용해 엔티티 이름에 대한 엔티티를 구한다.
  • 정렬디스크립터(SortDescriptor)를 생성한다. 이는 결과가 정렬되는 방식을정의한다
  • 요청서에 엔티티와 디스크립터를 세팅한다.
  • 컨텍스트에 요청서를 넘겨 실제 데이터를 배열로 읽어온다.

AppDelegate.m

앱 델리게이트 클래스의 초기화 메서드는 다음과 같이 수정한다.

이 메소드는 앱이 런칭된 직후에 호출된다. 따라서 별다른 동작을 거의 하지 않는 것이 좋다. 앱의 화면이 뜨기 이전에 viewDidLoad, viewWillAppear 등의 메소드가 호출되는 다른 시점에 작업을 처리하는 게 좋다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    newIndex = -1;
    memoArray = [[NSMutableArray alloc] init];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *anEntity = [NSEntityDescription entityForName:@"Memo" inManagedObjectContext:[self managedObjectContext]];
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastModifiedDate" ascending:NO];
    [fetchRequest setEntity:anEntity];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];

    NSArray *list = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
    [self setMemoArray:[list mutableCopy]];
}

이제 생성된 프로젝트를 빌드하고 실행하면 빈 메모 리스트로부터 새로운 메모를 작성하고 이것이 리스트에 추가되는 것을 확인할 수 있다.

수정하기

수정을 위해서는 앱 델리게이트가 리스트에서 선택된 객체를 기억해야 한다. 리스트에서 선택된 메모를 기억해서 디테일뷰에서는 이 메모 객체를 참조하여 뷰의 내용을 표시하고, 변경된 내용을 저장하도록 해야 한다.

AppDelegate.h

선택된 메모의 정보를 담는데 사용할 인스턴스 변수를 하나 앱델리게이트에 정의해야 한다.

@property (strong, nonatomic) Memo *selectedMemo;

또한 아까 만들어 둔 newIndex 프로퍼티는 삭제하도록 한다.

AppDelegate.m

선택된 메모 정보는 기본적으로 nil 값을 가진다.

...
@synthesize selectedMemo;
...
-(BOOL)ApplicationDidFinishLaunchingWithOptions:....
{
    ....
        selectedMemo = nil;
    return YES;
}

DetailViewController.m

디테일뷰 컨트롤러에서는 신규 메모인 경우와 기존 메모를 편집하는 경우를 구분해야 한다.

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    appDelegate = [[UIApplication sharedApplication] delegate];
    managedObjectContext = [appDelegate managedObjectContext];
    if([appDelegate selectedMemo] == nil){
        // 새로운 메모
        currentMemo = [NSEntityDescription insertNewObjectForEntityForName:@"Memo"
            inManagedObjectContext:managedObjectContext];
        [currentMemo setCreationDate:[NSDate date]];

    }else{
        currentMemo = [appDelegate selectedMemo];
        [self.title setText:[currentMemo title]];
        [self.Memo setText:[currentMemo memo]];
    }

편집을 완료한 경우에도 새 메모인 경우에는 신규 메모를 memoArray에 추가하지만, 기존 메모인 경우에는 그럴 필요가 없다.

-(void)viewWillDisappear:(BOOL)animated
{
    [currentMemo setMemo:(NSString *)[self.Memo text]];
    [currentMemo setTitle:(NSString *)[self.title text]];
    [currentMemo setLastModifiedDateap:[NSDate date]];

    if(appDelegate.selectedMemo == nil ){

        [appDelegate.memoArray insertObject:currentMemo atIndex:0];
    }

    [appDelegate setSelectedMemo:nil];
    [appDelegate saveContext];
}

RootViewController.m

루트 뷰 컨트롤러에서는 리스트에서 선택할 때 앱 델리게이트의 selectedMemo를 선택한 메모로 지정해주고, 삭제하는 경우의 코드를 추가한다. 리스트를 탭할 때는 segue를 통해 화면이 전환되므로, tableView:willSelectRowAtIndexPath: 메소드를 통해 selectedMemo를 지정한다.

-(NSIndexPath*)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [appDelegate setSelectedMemo:(Memo *)[memoListArray objectAtIndex:indexPath.row]];
    [appDelegate setNewIndex:1];

    return indexPath;
}

삭제

삭제시에는 컨텍스트에서 해당 메모 객체를 제거하고, 앱 델리게이트의 memoArray에서도 객체를 제거한다. (테이블뷰의 데이터 원본이므로)

컨텍스트를 저장하고나면 루트 뷰 컨트롤러의 memoListArray를 refresh한 다음 셀을 제거한다.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [appDelegate.memoArray removeObject:[memoListArray objectAtIndex:indexPath.row]];
        [appDelegate.managedObjectContext deleteObject:[memoListArray objectAtIndex:indexPath.row]];
        [appDelegate saveContext];
        memoListArray = [NSArray arrayWithArray:(NSArray *)[appDelegate memoArray]];
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }

}

스토리보드

끝으로 스토리보드에서 RootViewController의 테이블 뷰의 셀에서 DetailViewController로 우클릭-드래그를 통해 segue를 추가한다.

이제 앱을 빌드하고 실행하면 메모를 추가하고, 편집, 삭제할 수 있는 앱이 동작하는 것을 확인할 수 있다.

맺음

코어데이터는 데이터를 읽고 저장하는 방식을 매우 간단하게 만들어주기 때문에 저장되는 데이터가 종류가 많고 복잡할수록 많은 양의 코드들을 줄여주는 좋은 장치가 된다. 특히 현재까지 SQLite를 사용하기 위해서는 C 함수로 구성된 API를 사용해야 하므로 코드의 양도 많아지고 상당한 양의 노가다를 해야하는 단점이 있는 것이 사실이다. 코어데이터는 이를 자유롭게 활용하기 위해서는 “많은 것”을 읽어봐야 한다는 문제는 있지만 초보자의 입장에서 접근하기가 그렇게 어려운 것은 아니며, 데이터 타입 자체를 코코아 객체 유형으로 사용할 수 있어 코드 작성 면에서 많은 이점이 있다.

소스코드 : http://www.box.com/s/gee69aumdmdpbtl2jjix


  1. 앱델리게이트는 [[UIApplication SharedApplication] delegate]로 쉽게 접근이 가능하니까 이렇게했지만, 이러려면 모든 클래스에서 앱 델리게이트객체를 알고 있어야 한다는 문제가 있다. 
  2. 역시 이 작업은 델리게이트등을 통해서 특정한 한 객체가 관리객체조작 전체를 담당하는 것이 옳다.