[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 11. 기본적인 데이터 저장 방법

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 8. 12. 17:58

* 애플리케이션 샌드박스
- 아이폰에서는 4가지 데이터 자장 메커니즘(프로퍼티 리스트, 객체 아카이브, SQLite3, 코어 데이터)이 공유하는 중요한 구성 요소가 있다. 그것은 애플리케이션의 /Documents폴더이다. 모든 애플리케이션은 자신만의 /Documents폴더를 갖는다. 그리고 애플리케이션은 /Documents 디렉토리 안의 파일만 읽고 쓸 수 있는 제약이 있다.
- Finder 윈도우를 열고 홈 디렉토리 안의 Library/ApplicationSupport/iPhone Simulator/User/폴더로 이동한다.
- 내 개발환경은 iPhone SDK 4 이기 때문에 디렉토리 구성이 조금 다른다. Library/Application Support/iPhone Simulator/4.0/Applications 안에 있다.
- 아이폰은 애플리케이션을 Applications 폴더 안에 저장한다.
- 가끔은 애플리케이션의 여러 디렉토리들 사이에서 .sb 파일을 보게 될 것이다. .sb파일은 시뮬레이터가 파일 이름과 동일한 이름의 프로그램을 실행할 때 사용하는 설정값들을 보관하고 있다. 폴더 안에는 여러분이 만든 예제 애플리케이션의 하나와 애플리케이션이 사용하는 Documents, Library, tmp 폴더가 들어 있을 것이다. NSUserDefaults를 통해 저장한 환경설정 데이터를 제외하고, 애플리케이션의 모든 데이터는 Docuemtns폴더에 저장된다.
- NSUserDefaults로 저장한 환경설정 데이터는 Library/Preferences폴더에 저장된다. tmp 디렉토리는 애플리케이션이 임시로 사용하는 파일을 저장하는 곳이다.

* Documents 디렉토리 경로 가져 오기
- 어떻게 Docuemtns 디렉토리의 전체 경로를 알아내어 파일을 읽고 쓰는데 사용할 수 있을까? 사실 이것은 매우 간단하다. NSSearchPathForDirectoriesInDomain라는 C함수가 있고 홈 디렉토리의 경로를 제공한다.
- 하지만 NSSearchPathForDirectoriesInDomain 함수가 제공하는 많은 기능들은 Mac OS X를 위해 설계되었기 때문에, 아이폰에서 사용될 때는 반환 값을 가지지 않는다. 왜냐하면 NSSearchPathForDirectoriesInDomain가 반활할 수 있는 디렉토리들은 아이폰상에서는 실제로 존재하지 않거나, 샌드박스 메커니즘 때문에 접근할 수 없기 때문이다.
NSString *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

- NSDocumentDirectory는 Documents 디렉토리의 경로를 가져올 때 인사로 사용하는 상수이다. 다음으로 NSUserDomainMask는 디렉토리를 찾을 때 검색 범위를 애플리케이션의 샌드 박스 안으로 제한한다. Mac OS X에서 NSUserDomainMask 상수를 사용하는 것은 디렉토리의 검색 범위를 사용자 홈 디렉토리 안으로 제한한다는 것을 나타낸다.
- NSSearchPathForDirectoriesInDomains 함수가 검색한 경로를 배열 값으로 반환해도 우리는 배열의 0번째 요소가 Documents 디렉토리 경로라는 것을 알 수 있다. 왜 그럴까? 각각의 애플리케이션은 오직 하나의 Documents 디렉토리를 가지고 있기 때문에 반환받은 배열에는 오직 하나의 경로만이 들어 있기 때문이다.
- NSString을 반환하는 stringByAppendingPathComponent: 메서드를 사용하면 파일 이름을 쉽게 생성할 수 있다.
NSString *filename = [documentsDirectory stringByAppendingPathComponent:@"theFile.txt"];

* tmp 디렉토리 경로 가져오기
- Foundation 함수인 NSTemporaryDirectory()는 애플리케이션 tmp 디렉토리의 전체 경로를 담고 있는 문자열 포인터를 반환한다.
NSString *tempPath = NSTemporaryDirectory();

* 파일 저장 기법
- SQLite3를 사용할 경우 데이터베이스 파일을 하나 생성하면 SQLite3가 데이터를 저장하고 불러오는 일을 처리한다.
- 파일 저장을 단순하게 처리하고 싶다면 코어 데이터를 이용하여 파일 시스템을 관리할 수 있다.
- 프로퍼티 리스트와 아카이브를 사용할 때는 하나의 파일에 데이터를 저장할 것인지 여러 파일에 데이터를 저장할 것인지를 고려해야 한다.

* 하나의 파일에 데이터 저장하기
- 가장 간단한 방법은 하나의 파일에 데이터를 저장하는 것이며 대부분의 애플리케이션에서 사용되고 있다. 데이터를 저장하려면 먼저 루트 객체를 생성해야 하지만, 보통은 아카이빙을 사용할 때 NSArray나 NSDictionary와 같이 NSObject를 상속하는 클래스들을 사용한다. 그리고 나서 루트 객체에 애플리케이션의 데이터를 저장한다. 애플리케이션은 데이터를 저장할 때마다 루트 객체 안의 모든 데이터를 파일에 저장할 것이다. 애플리케이션이 실행되면 파일의 모든 내용을 읽어서 메모리에 적재하고, 종료될 때는 모든 내용을 파일에 저장한다.
- 하나의 파일을 사용할 때의 단점은 파일 안의 모든 데이터를 메모리에 적재해야 한다는 것이다. 심지어 매우 작은 데이터만 수정해도 데이터 전체를 파일에 출력해야 한다.

* 여러 파일에 데이터 저장하기
- 데이터를 여러 파일에 나눠서 저장하면 사용자가 데이터를 요청했을 때 애플리케이션은 요청한 데이터를 가지고 있는 파일 만을 로딩하며, 사용자가 데이터를 수정하면 변경된 내용을 보관할 파일 만을 저장한다. 또한 여러 파일을 사용하면 사용자가 현재 참조하지 않는 메모리상의 데이터를 파일에 출력하고 필요한 시기에 다시 불러올 수 있기 때문에, 메모리가 부족하다는 메시지를 받았을 때 메모리를 확보할 수 있게 된다. 여러 파일에 데이터를 나눠서 저장하는 방법의 단점은 애플리케이션의 내부 구조가 다소 복잡해진다는 것이다.

* 애플리케이션 데이터 저장하기
* 프로퍼티 리스트 직렬화
- 프로퍼티 리스트에 저장하려면 인스턴스는 직렬화 객체만을 가지고 있어야 한다. 직렬화 객체는 바이트 스트림(byte stream)으로 변환되기 때문에 파일에 저장하거나 네트워크를 통해서 전송될 수 있다. 물론 모든 직력화 객체가 콜렉션 클래스(collection class)에 포함될 수 있는 것은 아닌다. NSDictionary나 NSArray와 같은 특정 객체들만이 콜렉션 클래스에 포함될 수 있다.
  • NSArray
  • NSMutableArray
  • NSDictionary
  • NSMutableDictionary
  • NSData
  • NSMutableData
  • NSString
  • NSMutableString
  • NSNumber
  • NSData

- 만일 위의 클래스들을 사용하여 데이터 모델을 만드면, 프로퍼티 리스트에 데이터를 저장하고 불러올수 있다.
- 프로퍼티 리스트를 사용할 때 NSArray나 NSDictionary를 사용하여 데이터를 보관할 것이다. NSArray나 NSDictionary에 보관하는 객체가 직렬화 객체라면 writeToFile:atomically:메서드를 호출하여 다음과 같이 프로퍼티 리스트를 만들수 있다.

[myArray writeToFile:@"/some/file/location/output.list" atomically:YES];

- atomically 파라미터를 사용하여 writeToFile:메서드를 호출하면 인자에 지정된 파일에 데이터를 출력 하기 전에 임시 파일을 생성한다. wirteToFile:메서드는 임시 파일을 만든 뒤에 인자에 지정된 위치로 임시 파일을 복사한다. 이렇게 하면 안전하게 파일을 저장할 수 있다. 왜냐하면 애플리케이션이 파일을 저장하는 도중에 갑자기 종료되면, 임시 파일에만 영향이 있을 뿐 이전에 저장된 파일은 안전하기 때문이다.
- 프로포티 리스트를 사용할 때의 단점은 사용자 객체는 직렬화하여 프로퍼티 리스트에 저장할 수 없다는 것이다. 또한 코코아 터치의 객체들을 상속받았다고 해도, 앞에서 언급한 목록에 포함된 객체만이 프로퍼티 리스트를 사용할 수 있다. 이것은 NSURL, UIImage, UIColor와 같은 클래스에 저장된 데이터들은 프로퍼티 리스트에 직접 저장할 수 없음을 의미 한다.
- 이러한 객체들을 직렬화할 수 없다는 것은 다른 객체로부터 상속받은 객체나 객체 간의 계산 결과를 가지고 있는 객체는 프로퍼티로 사용할 수 없으며, 모델 클래스에 구현된 일부 코드를 컨트롤러 클래스로 옮겨야 함을 의미한다.
- 애플리케이션에 데이터를 포함하는 멋진 방법이 하나 있다. 프로퍼티 리스트 파일을 하나 만들고 프로젝트의 Resources폴더에 프로퍼티 리스트 파일을 저장하는 것이다. 그러면 애플리케이션이 컴파일될 때 프로퍼티 리스트 파일은 애플리케이션에 포함되어 컴파일될 것이다.

* 퍼시스턴스 애플리케이션
- 여기서는 4개의 텍스트 필드에 데이터를 입력할 수 있는 애플리케이션을 하나 만들것이다.
- 애플리케이션이 종료될 때 필드 안의 데이터들은 프로퍼티 리스트 파일로 저장되고, 애플리케이션이 실행되면 프로퍼티 리스트로부터 데이터를 읽어와 다시 텍스트 필드로 로딩할 것이다.

* 퍼시스턴스 프로젝트 생성하기
- Xcode에서 뷰 기반의 애플리케이션 템플릿을 사용하여 새 프로젝트를 하나 생성하고, 프로젝트 이름에 Persistence라고 입력한다.

#### PersistenceViewController.h ####
#import 

// 파일 이름을 위한 상수
#define kFilename @"data.plist"

@interface PersistenceViewController : UIViewController {
	UITextField *field1;
	UITextField *field2;
	UITextField *field3;
	UITextField *field4;	
}

@property (nonatomic, retain) IBOutlet UITextField *field1;
@property (nonatomic, retain) IBOutlet UITextField *field2;
@property (nonatomic, retain) IBOutlet UITextField *field3;
@property (nonatomic, retain) IBOutlet UITextField *field4;

-(NSString *)dataFilePath;
// applicationWillTerminate: 메서드는 애플리케이션이 종료될 때 호출될 것이고, 프로퍼티 파일로 데이터를 저장할 것이다.
-(void)applicationWillTerminate:(NSNotification *)notification;

@end

* 퍼시스턴스 애플리케이션 뷰 설계하기
- Resources 폴더를 펼치고 PresistenceViewController.xib를 더블 클릭한다.
- 라이브러리 창으로 부터 Text Field 하나를 드래그 하여 오른쪽과 위쪽의 파란 안내선에 맞춰서 배치한다. 그다음 Text Field를 왼쪽을 잡고 윈도우 2/3 정도 되는 위치까지 늘린다. Func+1 눌러서 속성 인스펙터 창이 나타나게 한다. Clear When Editing Begins라는 항목의 체크를 해지한다. 총 4개의 Text Field를 배치한다.
- 이제 4개의 레이블을 드래그하여 뷰에 붙힌다.
- 4개의 텍스트 필드와 레이블들을 배치한 뒤, File's Owner에서 각각의 텍스트 필드로 컨트롤 드래그하여 연결한다.

* 퍼시스턴스 클래스 편집한기.
#### PersistenceViewController.m ####
#import "PersistenceViewController.h"

@implementation PersistenceViewController
@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

// 데이터 파일의 전체 경로를 반환한다. 
-(NSString *)dataFilePath
{
	// Documents 디렉토리의 경로를 paths에 저장한 다음 kFilename을 paths에 붙여서 전체 경로를 나타내는 문자열을 만든다.
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

// 메서드가 NSNotification의 포인터를 인자로 사용한다는 것을 기억하자.
// applicationWillTerminate: 메서드는 노티피케이션 notification 메서드이고 
// 모든 노티피케이션들은 하나의 NSNotification 인스턴스를 인자로 사용한다.
// 노티피케이션은 객체 간에 서로 커뮤니케이션을 할 수 있도록 만들어진 간단한 메커니즘이다.
// 모든 객체는 1개 이상의 노티피케이션을 정의할 수 있으며 그것은 애플리케이션의 노티피케이션 센터(notification center)에서 발급된다. 
// 노티피케이션 센터란 객체들 사이에 노티피케이션을 넘겨주기 위하여 존재하는 싱글턴 객체이다. 
// 일반적으로 노티피케이션은 이벤트와 노티피케이션을 발급한 객체가 전달되었다는 것을 알려주는 표시이다.
// 애플리케이션은 종료 전에 데이터를 저장해야 하므로 UIApplicationWillTerminateNotification라는 노티피케이션을 사용해야 한다.
-(void)applicationWillTerminate:(NSNotification *)notification
{
	NSMutableArray *array = [[NSMutableArray alloc] init];
	[array addObject:field1.text];
	[array addObject:field2.text];
	[array addObject:field3.text];
	[array addObject:field4.text];
	[array writeToFile:[self dataFilePath] atomically:YES];
	[array release];
}

#pragma mark -
-(void)viewDidLoad
{
	// 같은 이름의 데이터 파일이 존재하는지 확인하고, 존재 하지 않는다면 데이터 파일을 로딩하지 않는다.
	// 데이터 파일이 존재하면 파일의 내용을 배열에 복사하고, 배열을 사용해서 4개의 텍스트 필드로 문자열을 복사한다.
	// 배열의 데이터들은 정렬되어 있으므로 데이터를 저장했던 순서대로 텍스트 필드에 할당하면, 
	// 데이터는 원래 입력되었던 텍스트 필드에 정확히 로딩될것이다.
	NSString *filePath = [self dataFilePath];
	if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
	{
		NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
		field1.text = [array objectAtIndex:0];
		field2.text = [array objectAtIndex:1];
		field3.text = [array objectAtIndex:2];
		field4.text = [array objectAtIndex:3];
		[array release];
	}
	
	// 애플리케이션 인스턴스 포인터를 만들었다.
	// NSNotificationCenter의 기본 인스턴스 객체의 addObserver:selector:name:object:메서드를 호출하여
	// UIApplicationWillTerminateNotification 노티피케이션 구독하였다.
	UIApplication *app = [UIApplication sharedApplication];
	
	// 우리는 PersistenceViewController를 옵저버(Observer)객체로 지정하였다. 
	// 이것은 PresistenceViewController 클래스가 노티피케이션을 받겠다는 것을 의미한다.
	// applicationWillTerminate:를 셀렉터로 지정하여 노티피케이션이 발급되면 노티피케이션 센터가 
	// applicationWillTerminate:를 호출할 수 있게 하였다.
	// 세번째 파라미터인 name:은 구독하려는 노티피케이션의 이름이며 마지막 인자인 object:는 노티피케이션을 발급하는 객체이다.
	// 만일 마지막 인자를 nil로 지정하면 UIApplicationWillTerminateNotification을 발급하는 모든 노티피케이션을 구독한다.
	[[NSNotificationCenter defaultCenter] addObserver:self 
											 selector:@selector(applicationWillTerminate:)
												 name:UIApplicationWillTerminateNotification
											   object:app];
	
	[super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
	self.field1 = nil;
	self.field2 = nil;
	self.field3 = nil;
	self.field4 = nil;
	[super viewDidUnload];
}

- (void)dealloc {
	[field1 release];
	[field2 release];
	[field3 release];
	[field4 release];
    [super dealloc];
}

@end


- 총 정리 : 메인 뷰는 로딩을 마치면 프로퍼티 리스트파일을 찾는다. 파일이 존재하면 파일로부터 데이터를 복사해서 텍스트 필드에 넣는다. 다음으로 애플리케이션이 종료할 때 통보받기 위해 노티피케이션을 구독하였다. 애플리케이션은 종료할 때 4개의 텍스트 필드의 값을 가변 배열에 추가하고, 배열을 프로퍼티 리스트 파일에 저장한다.

* 아카이빙 모델 객체
- 코코아 프로그래밍에서 아카이빙(archiving)이라는 개념은 일종의 직렬화를 의미하지만 프로퍼티 리스트와는 달리 모든 객체에 적용할 수 있다. 모델 객체를 아카이브로 만들면 복잡한 객체를 파일로 저장하고 로딩할 수 있다. 어떤 클래스의 프로퍼티가 int, float 혹은 NSCoding 프로토콜을 따르는 클래스의 인스턴스와 같은 양을 나타내는 자료형으로만 구성되었다면 그 클래스는 완벽하게 아카이브로 만들 수 있다. 데이터를 저장하는 Foundation 클래스와 코코아 터치 클래스들은 NSCoding을 따르기 때문에(물론 UIImage처럼 예외에 해당하는 클래스도 있다.) 대부분의 클래스를 아카이브로 만드는 것은 비교적 쉬운 일이다.
- 데이터를 아카이브로 만들 때 NSCoding프로토콜과 함께 구현해야 하는 또 다른 크로토콜이 있다. 그것은 바로 NSCopying 프로토콜이며, 이 프로토콜을 따르는 객체는 복사 될수 있다. 객체가 복사될 수 있으면 데이터 모델 객체를 사용할 때 더 많은 유연성을 가진다.

* NSCoding 프로토콜 구현하기
- NSCoding 프로토콜은 2개의 메서드를 선언하고 있다.
- 하나는 객체를 인코딩하여 아카이브에 저장하는 메서드 이고, 다른 하나는 아카이브를 디코딩하여 객체를 만드는 메서드이다. 두 메서드 모두 NSCoder인스턴스를 인자로 사용한다.
- 객체를 인코딩하는 메서드는 아래와 같다. 객체를 아카이브로 만들려면 각각의 인스턴스 변수들을 적절한 메서드로 인코딩해서 encoder객체에 저장해야 한다. 따라서 NSCoder의 객체를 초기화하는 메서드를 구현할 것이다.
-(void)encodeWithCoder:(NSCoder *)encoder
{
	[encoder encodeObject:foo forKey:kFooKey];
	[encoder encodeObject:bar forKey:kBarKey];
	[encoder encodeInt:someInt forKey:kSomeIntKey];
	[encoder encodeFloat:someFloat forKey:kSomeFloatKey];
}

- 만일 NSCoding를 따르는 하위클래스를 만든다면 상위 클래스의 encodeWithCoder:를 호출하는지 확인해야 한다.
-(void)encodeWithCoder:(NSCoder *)encoder
{
	[encoder encodeObject:for forKey:kFooKey];
	[encoder encodeObject:bar forKey:kBarKey];
	[encoder encodeInt:someInt forKey:kSomeIntKey];
	[encoder encodeFloat:someFloat forKey:kSomeFloatKey];
	[super encodeWithCoder:encoder];
}

- initWithCoder:메서드를 구현하는 것은 encodeWithEncoder:를 구현하는 것보다 조금 복잡하다. 만일 클래스가 NSObject의 하위클래스이거나 NSCoding 프로토콜을 따르지 않는면, 디코더를 아래와 같이 구현할 수 있다. 메서드는 [super init]를 사용하여 객체 인스턴스를 초기화하고, 초기화에 성공하면 NSCoder인스턴스로 넘겨받은 인자를 디코딩하여 프로퍼티에 복사한다.
-(id)initWithCoder:(NSCoder *)decoder
{
	if(self = [super init])
	{
		self.foo = [decoder decodeObjectForKey:kFooKey];
		self.bar = [decoder decodeObjectForKey:kBarKey];
		self.someInt = [decoder decodeIntForKey:kSomeIntKey];
		self.someFloat = [decoder decodeFloatForKey:kAgeKey];
	}

	return self;
}

- 클래스의 상위 클래스가 NSCoding 프로토콜을 따르고, 자신도 NSCoding 프로토콜을 따르면 initWithCoder:메서드는 아래와 같은 다른 방법으로 구현해야 한다. 상위 클래스의 init 메서드를 호출하는 대신에 아래와 같은 initWithCoder:메서드를 호출해야 한다.
-(id)initWithCoder:(NSCoder *)decoder
{
	if(self = [super initWithCoder:decoder])
	{
		self.foo = [decoder decodeObjectForKey:kFooKey];
		self.bar = [decoder decodeObjectForKey:kBarKey];
		self.someInt = [decoder decodeIntForKey:kSomeIntKey];
		self.someFloat = [decoder decodeFloatForKey:kAgeKey];
	}

	return self;
}

- 이와 같이 객체의 프로퍼티를 인코딩하고 디코딩할수 있는 2개의 메서드가 구현되어 있다면 객체를 아카이브로 만들 수 있고 아카이브로부터 객체를 읽고 쓰는 것이 가능하다.

* NSCopying 프로토콜 구현하기
- 데이터 모델 클래스가 NSCopying프로토콜를 따르는 것은 매우 좋은 방법이다. NSCopying는 copyWithZone:라는 메서드를 가지고 있다. 그리고 이 메서드는 객체를 복사하는 기능을 가지고 있다. NSCopying 프로토콜의 copyWithZone:메서드를 구현한 코드는 initWithCoder:메서드를 구현한 코드와 유사하다. 원본 클래스의 인스턴스 변수를 하나 만들고 인스턴스 변수의 프로퍼티에 원본 클래스의 프로퍼티를 복사하면된다.
-(id)copyWithZone:(NSZone *)zone
{
	MyClass *copy = [[[self class] allocWithZone:zone] init];
	copy.foo = [self.foo copyWithZone:zone];
	copy.bar = [self.bar copyWithZone:zone];
	copy.someInt = self.someInt;
	copy.someFloat = self.someFloat;
	return copy;
}

- 새로 생성한 객체를 릴리즈하거나 오토릴리즈 하지 않은 점에 주목하자. 복사된 객체는 리테인 속성이 내재되어 있으며, copy나 copyWithZone:을 호출할 때 복사되는 객체는 릴리즈나 오토릴리즈 속성을 호팜함하고 있다.
- NSZone 인자에 대해 신경 쓸 필요는 없다. 이 인자는 메모리를 관리하기 위해 시스템에 의해 사용되는 구조체를 가리키는 포인터이다.

* 데이터 객체를 아카이브로 만들기
- NSCoding 프로토콜을 따르는 객체를 아카이브로 만드는 것은 어렵지 않다.
// 먼저 인코딩된 데이터를 저장하기 위해 NSMutableData의 인스턴스 변수 하나를 생성한다.
// 그리고 나서 NSMutableData인스턴스 변수에 아카이브 객체를 저장하기 위해 NSKeyArchiver 인스턴스를 생성한다.
NSMutableData *data = [[NSMutableData alloc] init;
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

// 키-밸류 코딩을 사용하여 아래와 같이 아카이브로 만들려는 객체를 인코딩할 것이다.
[archiver encodeObject:myObject forKey:@"keyValueString"];

// archiver인스턴스의 finishEncoding을 호출한다.
// 그리고 NSMutableData인스턴스를 파일로 저장하고 객체를 메모리에서 제거한다.
// 파일을 쓰는 동안에 문제가 발생하면, success 값은 NO일것이다. 
// success 값이 YES이면 데이터는 성공적으로 지정된 파일에 쓰여진 것이다.
[archiver finishEncoding];
BOOL success = [data writeToFile:@"/path/to/archiver" atomically:YES];
[archiver release];
[data release];

* 아카이브로부터 데이터 객체 가져오기
- 아카이브로부터 객체를 만들려면 저장할 때와 유사한 방법을 사용해야 한다.
// 아카이브 파일을 읽어서 NSData 인스턴스를 생성하고, 
// 데이터를 디코딩하기 위해 NSKeyedUnarchiver 인스턴스를 생성한다.
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

// 객체를 아카이브로 만들 때 사용했던 키(key)를 사용하여 unarchiver로 부터 객체를 가져온다.
self.object = [unarchiver decodeObjectForKey:@"keyValueString"];

[unarchiver finishDecoding];
[unarchiver release];
[data release];

* 아카이빙 애플리케이션
* FourLines 클래스 구현하기
- 퍼시스턴스 프로젝트를 그대로 사용하겠당. Classes Folder에 새로운 파일을 추가 하자 Cocoa Touch Class를 선택하고 파일명은 FourLines.m로 설정한다. 헤더 파일도 같이 생성한다.
- 이 파일은 데이터 모델 클래스로 사용할 것이고, 프로퍼티 리스트 애플리케이션의 딕셔너리에 보관할 데이터를 저장할 것이다.

#### FourLines.h ####
#import 
#define kField1Key @"Field1"
#define kField2Key @"Field2"
#define kField3Key @"Field3"
#define kField4Key @"Field4"

@interface FourLines : NSObject 
{
	NSString *field1;
	NSString *field2;
	NSString *field3;
	NSString *field4;
}

@property (nonatomic, retain) NSString *field1;
@property (nonatomic, retain) NSString *field2;
@property (nonatomic, retain) NSString *field3;
@property (nonatomic, retain) NSString *field4;

@end

#### FourLines.m ####
#import "FourLines.h"

@implementation FourLines
@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

#pragma mark NSCoding
// encodeWithCoder: 메서드는 4개의 프로퍼티를 각각 인코딩한다.
-(void)encodeWithCoder:(NSCoder *)encoder
{
	[encoder encodeObject:field1 forKey:kField1Key];
	[encoder encodeObject:field2 forKey:kField2Key];
	[encoder encodeObject:field3 forKey:kField3Key];
	[encoder encodeObject:field4 forKey:kField4Key];	
}

// initWithCoder: 메서드는 인코딩할 때 사용했던 키를 사용하여 각각의 데이터를 디코딩하였다.
-(id)initWithCoder:(NSCoder *)decoder
{
	if(self = [super init])
	{
		self.field1 = [decoder decodeObjectForKey:kField1Key];
		self.field2 = [decoder decodeObjectForKey:kField2Key];
		self.field3 = [decoder decodeObjectForKey:kField3Key];
		self.field4 = [decoder decodeObjectForKey:kField4Key];
	}	
	return self;
}

#pragma mark -
#pragma mark NSCopying
// copyWithZone: 메서드는 FourLines 객체의 인스턴스를 생성하고 4개의 문자열을 FourLines객체의 프로퍼트로 복사 하였다.
-(id)copyWithZone:(NSZone *)zone
{	
	FourLines *copy = [[[self class] allocWithZone:zone] init];
	copy.field1 = [[self.field1 copyWithZone:zone] autorelease];
	copy.field2 = [[self.field2 copyWithZone:zone] autorelease];
	copy.field3 = [[self.field3 copyWithZone:zone] autorelease];
	copy.field4 = [[self.field4 copyWithZone:zone] autorelease];
	return copy;
}


@end

* PersistenceViewController 클래스 구현하기
- 기존 프로젝트 파일에서 파일 몇개 변경한다.

#### PersistenceViewController.h ####
#import 

#define kFilename @"archive"
#define kDataKey @"Data"

@interface PersistenceViewController : UIViewController {
	UITextField *field1;
	UITextField *field2;
	UITextField *field3;
	UITextField *field4;	
}

@property (nonatomic, retain) IBOutlet UITextField *field1;
@property (nonatomic, retain) IBOutlet UITextField *field2;
@property (nonatomic, retain) IBOutlet UITextField *field3;
@property (nonatomic, retain) IBOutlet UITextField *field4;

-(NSString *)dataFilePath;
// applicationWillTerminate: 메서드는 애플리케이션이 종료될 때 호출될 것이고, 프로퍼티 파일로 데이터를 저장할 것이다.
-(void)applicationWillTerminate:(NSNotification *)notification;

@end

#### PersistenceViewController.m ####
#import "PersistenceViewController.h"
#import "FourLines.h"

@implementation PersistenceViewController
@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

// 데이터 파일의 전체 경로를 반환한다. 
-(NSString *)dataFilePath
{
	// Documents 디렉토리의 경로를 paths에 저장한 다음 kFilename을 paths에 붙여서 전체 경로를 나타내는 문자열을 만든다.
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

// 메서드가 NSNotification의 포인터를 인자로 사용한다는 것을 기억하자.
// applicationWillTerminate: 메서드는 노티피케이션 notification 메서드이고 
// 모든 노티피케이션들은 하나의 NSNotification 인스턴스를 인자로 사용한다.
// 노티피케이션은 객체 간에 서로 커뮤니케이션을 할 수 있도록 만들어진 간단한 메커니즘이다.
// 모든 객체는 1개 이상의 노티피케이션을 정의할 수 있으며 그것은 애플리케이션의 노티피케이션 센터(notification center)에서 발급된다. 
// 노티피케이션 센터란 객체들 사이에 노티피케이션을 넘겨주기 위하여 존재하는 싱글턴 객체이다. 
// 일반적으로 노티피케이션은 이벤트와 노티피케이션을 발급한 객체가 전달되었다는 것을 알려주는 표시이다.
// 애플리케이션은 종료 전에 데이터를 저장해야 하므로 UIApplicationWillTerminateNotification라는 노티피케이션을 사용해야 한다.
-(void)applicationWillTerminate:(NSNotification *)notification
{	
	FourLines *fourLines = [[FourLines alloc] init];
	fourLines.field1 = field1.text;
	fourLines.field2 = field2.text;
	fourLines.field3 = field3.text;
	fourLines.field4 = field4.text;
	
	NSMutableData *data = [[NSMutableData alloc] init];
	NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
	[archiver encodeObject:fourLines forKey:kDataKey];
	[archiver finishEncoding];
	[data writeToFile:[self dataFilePath] atomically:YES];
	[fourLines release];
	[archiver release];
	[data release];	
}

#pragma mark -
-(void)viewDidLoad
{
	// 같은 이름의 데이터 파일이 존재하는지 확인하고, 존재 하지 않는다면 데이터 파일을 로딩하지 않는다.
	// 데이터 파일이 존재하면 파일의 내용을 배열에 복사하고, 배열을 사용해서 4개의 텍스트 필드로 문자열을 복사한다.
	// 배열의 데이터들은 정렬되어 있으므로 데이터를 저장했던 순서대로 텍스트 필드에 할당하면, 
	// 데이터는 원래 입력되었던 텍스트 필드에 정확히 로딩될것이다.
	NSString *filePath = [self dataFilePath];
	if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
	{
		
		NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];
		NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
		
		FourLines *fourLines = [unarchiver decodeObjectForKey:kDataKey];
		field1.text = fourLines.field1;
		field2.text = fourLines.field2;
		field3.text = fourLines.field3;
		field4.text = fourLines.field4;
		
		[unarchiver release];
		[data release];
		
	}
	
	// 애플리케이션 인스턴스 포인터를 만들었다.
	// NSNotificationCenter의 기본 인스턴스 객체의 addObserver:selector:name:object:메서드를 호출하여
	// UIApplicationWillTerminateNotification 노티피케이션 구독하였다.
	UIApplication *app = [UIApplication sharedApplication];
	
	// 우리는 PersistenceViewController를 옵저버(Observer)객체로 지정하였다. 
	// 이것은 PresistenceViewController 클래스가 노티피케이션을 받겠다는 것을 의미한다.
	// applicationWillTerminate:를 셀렉터로 지정하여 노티피케이션이 발급되면 노티피케이션 센터가 
	// applicationWillTerminate:를 호출할 수 있게 하였다.
	// 세번째 파라미터인 name:은 구독하려는 노티피케이션의 이름이며 마지막 인자인 object:는 노티피케이션을 발급하는 객체이다.
	// 만일 마지막 인자를 nil로 지정하면 UIApplicationWillTerminateNotification을 발급하는 모든 노티피케이션을 구독한다.
	[[NSNotificationCenter defaultCenter] addObserver:self 
											 selector:@selector(applicationWillTerminate:)
												 name:UIApplicationWillTerminateNotification
											   object:app];
	
	[super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
	self.field1 = nil;
	self.field2 = nil;
	self.field3 = nil;
	self.field4 = nil;
	[super viewDidUnload];
}

- (void)dealloc {
	[field1 release];
	[field2 release];
	[field3 release];
	[field4 release];
    [super dealloc];
}

@end

* 아이폰에 내장된 SQLite3 사용하기
- SQLite3라는 아이폰에 내장된 SQL 데이터베이스를 사용하는 것이다. SQLite3는 목잡한 데이터를 처리할 수 있는 능력이 있으며, 객체를 사용해 처리하는 것보다 훨씬 빠르다. SQLite3를 사용하면 모든 객체를 메모리에 로딩하지 않고, 처리할 수 있다. SQLite3로부터 계산된 결과를 가져오는 것은 모든 객체를 메모리에 로딩하고 객체의 합을 계산하는 것보다 훨씬 빠르다.
- SQLite3는 구조적 질의언어(SQL)를 사용한다.
- 이절에서는 SQLite3 데이터베이스를 초기화하는 방법과 애플리케이션과 상호작용하는 방법에 대해서 설명할 것이다.

* 데이터베이스 생성하고 열기
- SQLite3를 사용하면 먼저 데이터베이스를 열어야(OPEN)한다. 데이터베이스를 열기 위한 함수인 sqlite3_open()는 기존에 만들어진 데이터베이스 하나를 열거나 명시된 데이터베이스가 존재하지 않는다면 새로운 데이터베이스를 만드는 기능을 제공한다.
- result값이 SQLITE_OK라면 데이터베이스가 성공적으로 열린 것이다. 여기서 주의할 점은 데이터베이스 파일의 경로를 인자로 넘길 때는 표준 C문자열을 사용해야 한다는 것이다. SQLite3는 오브젝티브C가 아닌 일발적인 C로 작성되었고 NSString를 사용할수 없다.
sqlite3 *database;
int result = sqlite3_open("/path/to/database/file", &database);

- 다행히 NSString에는 인스턴스를 C문자열을 변환해주는 멤버 메서드가 있다.
char *stringPath = [pathString UTF8String];

- SQLite3 데이터 베이스와 관련된 작업을 모두 마쳤다면 아래아 같이 호출하여 데이터베이스를 닫는다.
sqlite3_close(database);

- 데이터베이스는 모든 데이터를 테이블에 저장한다. SQL의 CREATE문을 실행하는 것으로 새로운 데이블을 만들 수 있고, sqlite3_exec함수를 사용하여 아래와 같이 열려있는 데이터베이스에서 CREATE문을 실행할 수 있다.
- 명령이 성공적으로 실행됐는지 확인하기 위해 결과 값이 SQLITE_OK인지 확인할 필요가 있다. 만일 아니라면 errorMsg에 발생한 문제에 대한 설명이 포함되어 있을 것이다.
char *errorMsg;
const char *createSQL = "CREATE TABLE IF NOT EXITS PEOPLE (ID INTEGER PRIMARY KEY AUTOINCREMENT, FILED_DATA TEXT");
int result = sqlite3_exec(database, createSQL, NULL, NULL, &errorMsg);
- sqlite3_exec함수는 SQLite3명령어를 실행하는 데 사용되며 반환 값을 가지지 않는다. 이 함수는 update, insert, delete문에 사용된다.
NSString *query = @"SELECT ID, FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
int result = sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil);

- result 값이 SQLITE_OK이면 SQL문이 성공적으로 준비된(prepared)것이고, 결과셋(result set)을 가지고 계속 진행할 수 있다. 아래는 데이터베이스로부터 결과셋을 만들고 int값과 NSString을 가져오는 예제이다.
while(sqlite3_step(statement) == SQLITE_ROW)
{
	int rowNum = sqlite3_column_int(statement, 0);
	char *rowData = (char *)sqlite3_column_text(statement, 1);
	NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
	// DO Someting with the data here
	[fieldValue release];
}

sqlite3_finalize(statement);

* 바인딩 변수들
- 데이터베이스에 값을 입력하기 위해 SQL문장에 자체에 값을 포함시켜 사용하는 것이 가능하지만, 데이터 베이스에 입력할 때는 바인딩 변수라는 것을 사용하는 연습이 필요하다. SQL문장에 부적절한 문자나 따옴표가 포함되지 않도록 확인하는 일은 상당히 귀찮은 일이다. 바인딩 변수를 사용하면 이러한 일들을 쉽게 처리할수 있다.
- 바인딩 변수를 사용하여 테이블에 레코드를 삽입하는 것은 일반적으로 SQL문장을 만드는것과 동일하지만 물음표를 포함시켜야만 한다. 각각의 물음표는 문장이 실행되기 전에 실제 값으로 치환될 하나의 변수를 나타낸다. 그리고 나서 SQL문에 전처리 과정을 거치면 바인딩 변수들은 각각의 값들로 치환될 것이다.
char *sql = "insert into foo values (?,?);";
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK)
{
	sqlite3_bind_int(stmt, 1, 235);
	sqlite3_bind_text(stmt, 2, "Bar", -1, NULL);
}
if(sqlite3_step(stmt) != SQLITE_DONE)
	NSLog(@"This should bt real error checking!");
sqlite3_finalize(stmt);

- 사용하기 원하는 데이터 타입에 의존하여 다중 바인딩 문을 사용하는것도 가능하다.
- 첫번째 인자는 바인딩될 데이터 타입과는 무관하며 sqlite3_prepare_v2()를 호출하기 전에 선언된 sqlite3_stmt의 포인터이다.
- 두번째 인자는 바인딩하려는 변수의 인덱스 번호이다. SQL문의 첫 번째 물음표는 인덱스 값으로 1을 사용할 것이고 SQL문 안에서 각각의 물음표는 왼쪽부터 순서대로 1씩 증가된 인덱스 값을 가진다.
- 세번째 인자는 물음표에 치환된 실제 값이다.
- 대부분의 바인딩 함수들은 3개의 인자만을 사용하지만 텍스트나 바이너리 데이터를 인자로 바인딩할 때는 2개의 인자를 추가로 사용한다.
- 네번째 인자는 세 번째 인자의 데이터 길이를 나타내는 인자이다. C문자열을 인자로 넘겨줄 때는 문자열의 길이 대신 -1을 사용하는 것이 가능하며 -1을 사용하면 함수는 문자열 전체를 사용하게 될 것이다. 문자열이 아닌 경우에는 정확한 데이터의 길이를 int값으로 넘겨줘야만한다.
- 다섯번째 인자는 콜백 함수의 포인터로써 선택적으로 사용할 수 있으며 SQL문이 호출된 후에 메모리 해제와 같은 특별한 일을 수행하고 싶다면 해당 기능을 가진 콜백 함수를 등록할 수 있다. 전통적으로 다섯 번째 인자로 등록되는 콜백 함수는 malloc()으로 할당된 메모리를 해제하는 함수를 등록하는 데 사용되었다.
- 만일 SQL문자열이 update문보다는 SQL쿼리문을 가지고 있다면 SQL_DONE을 반활 할때 까지 sqllist3_step()을 호출해야만 한다. 만일 update문이라면 sqlite3_step()는 한번만 호출하면 된다.

* SQLite3 사용을 위한 프로젝트 설정하기
- C함수의 형태로 제공되는 수많은 절차적 API들을 사용해서 SQLite3에 접근할 수 있다. 이 API를 사용하기 위해서는 Mac OS X와 아이폰의 /usr/lib에 위치한 libsqlite3.dylib라는 이름의 동적 라이브러리 파일을 애플리케이션에 링크시켜야 한다.
- Xcode로 이동한 뒤 Persistence 프로젝트에서 Groups & Files의 Frameworks을 선택하고 Project 메뉴의 [Add to Project ..]를 클릭한다. 그리고 나서 /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulatorX.Y.sdk/usr/lib으로 이동하여 libsqlite3.dylib라는 이름의 파일을 찾아서 추가한다.
- 메시지 창이 뜨면 Copy items into destination group's folder (if needed)항목이 체크 해제되었는지 확인한다. 그리고 Reference Type을 Relative to Current SDK로 변경하고 추가 add버튼을 누른다.
- 만일 Reference Type을 Absolute Path로 선택하였다면,/usr/lib/libsqlite3.dylib와 같이 절대 경로를 사용하여 링크하게 될 것이다.

#### PersistenceViewController.h ####
#import "PersistenceViewController.h"
#import "FourLines.h"

@implementation PersistenceViewController
@synthesize field1;
@synthesize field2;
@synthesize field3;
@synthesize field4;

// 데이터 파일의 전체 경로를 반환한다. 
-(NSString *)dataFilePath
{
	// Documents 디렉토리의 경로를 paths에 저장한 다음 kFilename을 paths에 붙여서 전체 경로를 나타내는 문자열을 만든다.
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

// 메서드가 NSNotification의 포인터를 인자로 사용한다는 것을 기억하자.
// applicationWillTerminate: 메서드는 노티피케이션 notification 메서드이고 
// 모든 노티피케이션들은 하나의 NSNotification 인스턴스를 인자로 사용한다.
// 노티피케이션은 객체 간에 서로 커뮤니케이션을 할 수 있도록 만들어진 간단한 메커니즘이다.
// 모든 객체는 1개 이상의 노티피케이션을 정의할 수 있으며 그것은 애플리케이션의 노티피케이션 센터(notification center)에서 발급된다. 
// 노티피케이션 센터란 객체들 사이에 노티피케이션을 넘겨주기 위하여 존재하는 싱글턴 객체이다. 
// 일반적으로 노티피케이션은 이벤트와 노티피케이션을 발급한 객체가 전달되었다는 것을 알려주는 표시이다.
// 애플리케이션은 종료 전에 데이터를 저장해야 하므로 UIApplicationWillTerminateNotification라는 노티피케이션을 사용해야 한다.
-(void)applicationWillTerminate:(NSNotification *)notification
{	
	// 데이터를 저장하기 위해, for 루프를 사용하여 각각의 행을 업데이트하는 UPDATE 문을 생성
	for( int i = 1; i <= 4; i++)
	{
		// 필드 이름을 생성하고 필드 이름을 인자로 사용하여 텍스트 필드 아웃렛을 가져왔다.
		// valueForKey: 메서드가 이름을 키(key) 값으로 사용하여, 프로퍼티를 반환했다는 점이다.
		NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", i];
		UITextField *field = [self valueForKey:fieldName];
		[fieldName release];
		
		// 에러가 발생했을때 에러 메시지를 출력할 수 있도록 포인터 선언
		char *errorMsg;
		// 표준 SQL문인 INSERT문 대신에 INSERT OR REPLACE를 사용함으로써 이미 같은 행번호를 가지는 레코드가
		// 테이블에 저장되어 있어도 신경 쓸 필요가 없다.
		char *update = "INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA) VALUES (?, ?);";

		sqlite3_stmt *stmt;
		
		// 바인딩 변수를 사용한 쿼리 명령문을 준비하고 바인딩 변수를 실제 값으로 치환.
		if(sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK)
		{
			sqlite3_bind_int(stmt, 1, i);
			sqlite3_bind_text(stmt, 2, [field.text UTF8String], -1, NULL);
		}
		
		// 쿼리 명령문을 실행하기 위해 sqlite3_step함수를 호출하였으며, 쿼리 명령문이 실행되었는지 확인하고
		// 명령문을 소멸시키고 데이터베이스를 닫는다.
		
		// 에러가 발생했을 때 assertion문을 사용한 점을 주목하자. 이러한 종류의 에러는 사용자가 애플리케이션을 사용하면서
		// 발생하는 것이 아니라 개발자의 실수로 발생하기 때문에 예외처리나 직접적인 에러 확인보다는 assertion을 사용하는것이 좋다.
		// assertion 매크로를 사용하는 것은 우리가 작성한 코드를 디버깅하는 데 사용될 수 있으며 최종적으로 만들어지는 
		// 애플리케이션에는 assertion 매크로는 제외돌 것이다.
		if(sqlite3_step(stmt) != SQLITE_DONE)
			NSAssert1(0, @"Error updating table: %s", errorMsg);
		
		sqlite3_finalize(stmt);		  	   
	}
	sqlite3_close(database);
}

#pragma mark -
-(void)viewDidLoad
{
	// 먼저 데이터베이스를 열었다. 만일 데이터 베이스를 여는데 문제가 있다면, 데이터베이스는 닫힐 것이고 assertion이 일어날것이다.
	if(sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK)
	{
		sqlite3_close(database);
		NSAssert1(0, @"Failed to open database", "");
	}
	
	// 데이터를 저장할 테이블을 생성하였다.
	// CREATE TABLE SQL문을 사용하면 테이블을 생성할수 있다. 
	// IF NOT EXISTS을 사용하여 데이터베이스가 기존에 존재하는 데이터를 덮어써버리는 것을 방지 할수 있다.
	// 만일 같은 이름에 테이블이 존재한다면, 쿼리문은 아무 일도 하지 않고 조용히 종료될 것이다.
	// 그래서 테이블의 존재를 확인하지 않고도 애플리케이션이 실행될 때마다 매번 호출할 수 있다.
	char *errorMsg;
	NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS (ROW INTEGER PRIMARY KEY, FIELD_DATA TEXT);";
	
	if(sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK)
	{
		sqlite3_close(database);
		NSAssert1(0, @"Error creating table : %s", errorMsg);
	}
	
	// 데이터를 가져오는 작업
	NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
	sqlite3_stmt *statement;
	
	if(sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK)
	{	
		// while문을 사용하여 반환받은 각각의 레코드를 순환하였다.
		while(sqlite3_step(statement) == SQLITE_OK)
		{
			// 행 번호를 가져와서 int변수에 저장하고, 필드 데이터 문자열의 포인터를 C문자열 포인터에 변수에 저장한다.
			int row = sqlite3_column_int(statement, 0);
			char *rowData = (char *)sqlite3_column_text(statement, 1);
			
			// 행 번호를 참조하여 필드 이름을 만들고, C문자열을 NSString으로 변환하였다.
			NSString *fieldName = [[NSString alloc] initWithFormat:@"field%d", row];
			NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
			
			// 텍스트 필드에 데이터베이스로부터 가져온 값을 설정하기 위해 필드 이름과 NSString 포인터를 사용하였다.
			UITextField *field = [self valueForKey:fieldName];
			field.text = fieldValue;
			
			[fieldName release];
			[fieldValue release];		
		}
		sqlite3_finalize(statement);
	}
	
	
	// 애플리케이션 인스턴스 포인터를 만들었다.
	// NSNotificationCenter의 기본 인스턴스 객체의 addObserver:selector:name:object:메서드를 호출하여
	// UIApplicationWillTerminateNotification 노티피케이션 구독하였다.
	UIApplication *app = [UIApplication sharedApplication];
	
	// 우리는 PersistenceViewController를 옵저버(Observer)객체로 지정하였다. 
	// 이것은 PresistenceViewController 클래스가 노티피케이션을 받겠다는 것을 의미한다.
	// applicationWillTerminate:를 셀렉터로 지정하여 노티피케이션이 발급되면 노티피케이션 센터가 
	// applicationWillTerminate:를 호출할 수 있게 하였다.
	// 세번째 파라미터인 name:은 구독하려는 노티피케이션의 이름이며 마지막 인자인 object:는 노티피케이션을 발급하는 객체이다.
	// 만일 마지막 인자를 nil로 지정하면 UIApplicationWillTerminateNotification을 발급하는 모든 노티피케이션을 구독한다.
	[[NSNotificationCenter defaultCenter] addObserver:self 
											 selector:@selector(applicationWillTerminate:)
												 name:UIApplicationWillTerminateNotification
											   object:app];
	
	[super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
	self.field1 = nil;
	self.field2 = nil;
	self.field3 = nil;
	self.field4 = nil;
	[super viewDidUnload];
}

- (void)dealloc {
	[field1 release];
	[field2 release];
	[field3 release];
	[field4 release];
    [super dealloc];
}

@end

* 코어 데이터 사용하기
- 아이폰 SDK에서 지원하지 않는 기능 중 가장 주목할 만한 것은 데이터 ㅁ모델을 시각적으로 설계할 수 있는 애플의 도구인 코어데이터이다. 애플은 아이폰 SDK3부터 코어 데이터를 지우너하기 시작했으며, 코어 데이터릐 지원은 아이폰 개발자들에게는 큰 축복이었다.
- Xcode에서 새 프로젝트를 생성한다. 이번에는 Window-based Application 템플릿을 사용하되 Choose버튼을 누르기 전에 오른쪽 하단에 보면 Use Core Data for storage라는 체크박스를 체크한다. 파일명은 Core_Data_Persistence라고 입력한다.

* 엔티티와 관리객체
- Resources 폴더에는 Core_Data_Persistence.xcdatamodel이라는 이름의 처음보는 파일이 들어 있을것이다. 이 파일은 데이터 모델 파일이다. 코어 데이터는 코드 작성 없이도 애플리케이션의 데이터 모델을 시각적으로 설계할 수 있도록 해준다.
- '엔티티'와 '관리객체' 모두가 데이터 모델 객체를 참조하기 때문에 이 두 용어가 조금은 혼동스러울 수 있다. '관리객체'는 애플리케이션이 실행될 때 생성된 '엔티티'의 실제 인스턴스를 참조하는데, 사용되며 '엔티티'라는 용어는 '관리객체'가 인스턴스를 참조하는 동안 객체의 설명을 참조하는데 사용된다.
- 엔티티는 총 4개의 프로퍼틸로 구성된다. 그것은 바로 속성, 관계, 페치드(fetched) 프로퍼티, 페치 리퀘스트(tetch request)이다.
- 속성은 오브젝티브 C 클래스의 인스턴스 변수에 해당하는 코어 데이터 엔티티의 함수이다.
- 관계(relationship)는 엔티티 사이의 관계를 정의한다. 관계는 하나 혹은 그 이상일 수 있다.
- 페치드 프로퍼티는 관계와는 동시에 사용될 수 없는 특성이다. 즉,  둘 중 하나를 선택해 사용해야 한다. 관계와의 차이점은 로딩 방식이 다르다는 것이다. 예를 들어, 사람과 집주소가 관계로 이어져 있다면 사람이 로딩될 때 집주소도 로딩될 것이다. 반대로 사람이 집주소를 페치드 프로퍼티로 참조하고 있다면 사람이 로딩되더라도 집주소는 로딩되지 않는다. 적어도 집주소에 접근하기 전까지는 말이다. 일존의 '지연로딩'
- 페치 리퀘스트는 미리 정의된 쿼리이다.

* 키-밸류 코딩
- 관리객체를 사용할 때 프로퍼티의 값을 가져오거나 저장하기 위해 속성의 이름을 키로 사용한다. 관리객체로부터 name이라는 속성에 저장된 값을 가져오기 위해 다음과 같이 호출 할 수 있다.
NSString *name = [myManagedObject valueForKey:@"name"];

- 비슷한 방법으로 새로운 값을 관리객체의 프로퍼티에 저장하려면 다음과 같이 사용한다.
[myManagedObject setValue:@"Martha Steware" forKey:@"name"];

* 컨텍스트에 모든 것을 밀어 넣기
- 그렇다면 이 관리객체들은 어디에 존재하는 것일까? 그들은 퍼시스턴스 스토어(persistent store)라는 곳에 있으며 퍼시스턴스 스토어는 이따금씩 백킹 스토어(backing store, 일종의 보조 기억장치)의 형태로 참조되기도 한다.
- 퍼시스턴스 스토어는 몇 가지 서로 다른 형태를 가진다. 기본적으로 Core Data 애플리케이션은 애플리케이션의 디렉토리 안에 있는 SQLite 데이터베이스와 같은 백킹 스토어를 만든다. 심지어 데이터가 SQLite통해 저장되더라도, 코어 데이터 프레임워크 안의 클래스들이 데이터의 저장과 로딩에 관련된 모든 일을 처리한다. 만일 코어 데이터를 사용한다면 SQL문을 직접 사용하지 않아도 된다. 오직 객체를 다루는 것만으로 코어 데이터가 필요한 SQL문들을 처리해주기 때문이다. 백킹스토어는 SQLite뿐만 아니라 가벼운 파일과 같은 형태로 사용될 수 있다.
- 대부분의 애플리케이션들이 오직 하나의 저장소만을 사용하지만 하나의 애플리케이션에서 다수의 저장소를 사용하는 것도 가능하다.
- 보통 저장소를(저장소는 애플리케이션의 델리케이트 안에서 치리된다.) 직접 생성하는 것보다 컨텍스트(Context)를 참조하는 관리객체 컨텍스트(managed object context)를 호출하여 생성한다. 이 컨텍스트 중개자는 저장소에 접근하여 객체가 마지막으로 저장되었을 때부터 지금까지 프로퍼티의 변경된 정보들을 가지고 있다. 또한 컨텍스트는 취소 관리자(undo manager)를 통해 모든 변경 사항을 기록하여 언제든지 실행한 작업을 취소하거나 마지막으로 데이터라 저장됐을 때로 롤백 (rollback)할 수 있다.
- 대부분의 코어 데이터의 함수들을 호출할 때는 NSManagedObjectContext를 인자로 필요로 하거나 컨텍스트에 실행될 수 있어야 한다. 복잡한 멀티스레드 아이폰 애플리케이션을 제외한 대부분의 경우에 애플리케이션의 델리케이트로부터 managedObjectContext 프로퍼티를 가져와 사용할 수 있다.

* 관리객체 생성하기
- 관리객체를 생성할때는 alloc이나 inint대신에 NSEntityDescription클래스의 팩톨리 메서드를 사용한다. 이 클래스의 인스턴스들은 메모리 안에서 단일 엔티티를 나타낸다. 엔티티는 클래스와 유사하다는 것을 기억하자. 엔티티는 객체의 정보를 가지고 있으며 특정 엔티티가 가지는 프로퍼티들을 정의하고 있다.
- 새로운 객체를 생성하는 코드는 다음과 같다.
theLine = [NSEntityDescription insertNewObjectForEntityForName:@"EntityName" inManagedObjectContext:context];

- insertNewObjectForEntityForName:inManagedObjectContext: 메소드는 객체를 생성하는 일 외에도 생성된 객체를 컨텍스트에 저장하고 오토릴리즈된 객체를 반환하는 일을 한다. 위의 코드는 호출했어도 컨텍스트 상의 객체는 아직 저장소에 저장된 것이 아니다. 객체는 저장소에 저장하려면 관리객체 컨텍스트의 save:메서드를 호출해야 한다.

* 관리객체 가져오기
- 저장소에서 관리객체를 가져오려면 페치 리퀘스트를 하나 만들고 객체의 엔티티나 가져오길 원하는 객체를 명시한 NSEntityDescription을 사용하여 요청해야 한다.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescr = [NSEntityDescription entityForName:@"EntityName" inManagedObjectContext:context];
[request setEntity:entityDescr];

- 필요하다면 NSPredicate를 사용한 페치 리퀘스트의 조건을 명시할 수 있따. NSPredicate는 페치 리퀘스트의 실행 결과를 만들기 위해 필요한 기준을 정의하기 위해 사용되며 SQL문과 유사하다.
NSPredicate *pred = [NSPredicate predicateWithFormat:@"(name =%d)", nameString];
[request setPredicate:pred];

- 생성한 NSPredicate는 페치 리퀘스트에 명시된 엔티티의 모든 객체를 가져오는 대신 name프로퍼티에 nameString값이 저장되어 있는 객체만을 자여오게 하였다. 그래서 nameString이 @"Bob"이라는 값을 가지고 있다면 페치 리퀘스트는 오직 name 프로퍼티가 "Bob"인 객체만을 가져오게 될것이다.
- 엔티티 디스크립션과 함께 페치 리퀘스트를 생성하고 나면 NSManagedObjectContext: 인스턴스 메서드를 사용하여 패치 리퀘스트를 실행한다.
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if(objects == nil)
{
//handle error
}

- executeFetchRequest:error:는 저장소로부터 특정 객체를 불러올 것이며 배열을 통해 객체를 반환할 것이다. 만일 에러가 발생하면 배열 대신 nil값을 반환받게 될것이고 인자로 사용한 error포인터는 에러의 내용이 담긴 NSError객체를 가리키게 될 것이다. 에러가 발생하지 않으면 명시된 기준과 일치하는 데이터가 없어서 아무런 값을 담고 있지 않더라도 제대로 된 배열을 반환반게 될 것이다. 배열을 반환받는 시점부터 배열 안의 관리객체에 대한 모든 변경 내용은 관리객체 컨텍스트에 의해 기록될 것이며 컨텍스트에 save: 메시지를 보내면 저장될 것이다.

* 데이터 모델 설계하기
- Persistence_Core_Data.xcdatamodel을 클릭하고 Xcode의 데이터 모델 편집기를 실행한다.
- 데이터 모델 편집기의 왼쪽 상단의 창은 현재 데이터 모델의 모든 엔티티를 보여주기 때문에 엔티티 창(Entity pane)이라고 한다.
- 엔티티 창의 왼쪽 아래의 십자 모양의 아이콘을 클릭하면 Entity라는 이름의 엔티티 하나가 생성되고 선택될 것이다.
- 데이터 모델 편집기의 하단 창에는 새로운 무언가가 추라되었다. 상단의 3개의 창을 사용하여 데이터 모델을 만들면 화면의 하단 창에 데이터 모델이 시각적으로 표현되어 나타날 것이며 이것을 다이어그램 뷰(Diagram view)라고 한다.
- 데이터 모델 편집기의 우측 상단 창은 상세 정보창(detail pane)이라고 부른다. 데이터 모델 편집기의 상세 정보창은 현재 선택한 엔티티나 프로퍼티를 편집할 수 있는 기능을 제공한다.
- Name필드의 값을 Entity에서 Line으로 변경하자.
- 데이터 모델 편집기의 위쪽 창은 프로퍼티 창(property pane)이라고 한다. 프로퍼티 창은 엔티티에 새 프로퍼티를 추가 할때 사용한다.
- 프로퍼티 창의 왼쪽 하단의 모서리에서 있는 십자 버튼을 클릭하고 Add Attribute를 선택한다. 상세 정보창에서 속성의 이름을 newAttribute에서 lineNum로 변경하고 Integer값을 저장 할수 있도록 type을 Integer 16으로 변경하자. Name 필드 아래 체크 박스에서 Optional속성을 사용하지 않을 것이므로 체크를 해지 한다. 인터페이스 상에서 라인은 레이블에 대응되지 않으므로 필요가 없다. Transient속성은 미리 정의도니 속성 타입이 없는 비표준 객체를 저장하기 위해 사용된다. Indexed는 이 속성의 데이터를 저장하기 위해 SQL데이터베이스 내부의 인덱스를 생성하다.
- 다시 플러스 아이콘을 클릭하고 Add Attribute를 선택한다. 이번에는 lineText라는 이름의 속성을 하나 만들고 타입은 String으로 변경한다.

* Presistence 뷰와 컨트롤러 생성하기
- 윈도우 기반의 애플리케이션 템플릿을 선택하였으므로 뷰 컨트롤러가 제공되지 않는다. 그래서 Classes폴더를 클릭하고 새로운 파일을 추가 한다. Cocoa Touch Class의 UIViewController subclass를 선택한다. 파일 이름은 PersistenceViewController라고 입력하고 헤더 파일도 같이 생성한다. 또한 With XIB for user interface가 체크되었는지를 확인한다.

#### PersistenceViewController.h ####
#import 

@interface PersistenceViewController : UIViewController 
{
	UITextField *line1;
	UITextField *line2;
	UITextField *line3;
	UITextField *line4;
}

@property (nonatomic, retain) IBOutlet UITextField *line1;
@property (nonatomic, retain) IBOutlet UITextField *line2;
@property (nonatomic, retain) IBOutlet UITextField *line3;
@property (nonatomic, retain) IBOutlet UITextField *line4;

@end

#### PersistenceViewController.m ####
#import "PersistenceViewController.h"
#import "Core_Data_PersistenceAppDelegate.h"

@implementation PersistenceViewController
@synthesize line1;
@synthesize line2;
@synthesize line3;
@synthesize line4;

-(void)applicationWillTerminate:(NSNotification *)notification
{
	// 레퍼런스를 사용하여 생성된 관리객체 컨텍스트의 포인터를 만듬
	Core_Data_PersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
	NSManagedObjectContext *context = [appDelegate managedObjectContext];
	
	// nil배열이 반환되었을 때 어떠한 에러가 발생했는지 시스템으로부터 가져오기 위해 NSError 포인터 하나를 생성
	NSError *error;
	
	// 각각의 레이블을 생성하기 4번 반복되는 순환문을 실행
	for(int i=1; i <= 4; i++)
	{
		// i 변수 값을 line이라는 글자 뒤에 붙이는 것으로 4개의 레이블의 이름을 만들고 valueForKey:를 사용하여
		// 적합한 필드를 가리키는 레퍼런스를 얻는데 사용
		NSString *fieldName = [NSString stringWithFormat:@"line%d",i];
		UITextField *theField = [self valueForKey:fieldName];
		
		// 페치 리퀘스트 하나를 만듬		
		NSFetchRequest *request = [[NSFetchRequest alloc] init];
		
		// 데이터 모델 편집기에서 설계한 Line엔티티를 서술하는 엔티티 디스크립션을 생성
		// 애플리케이션의 델리케이트로부터 컨텍스트를 가져오는데 사용
		// 엔티티 디스크립션을 생성하면 이것을 페치 리퀘스트에 넘겨서 리퀘스트가 어떤 타입의 엔티티를 찾아야 할지를 알려준다.
		NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Line" inManagedObjectContext:context];
		[request setEntity:entityDescription];
		
		// 저장소에 관리객체가 저장되어 있는지를 확인하여 필드의 으측 객체를 식별하는 NSPredicate를 생성
		NSPredicate *pred = [NSPredicate predicateWithFormat:@"(lineNum = %d)", i];
		[request setPredicate:pred];
		
		// 아직은 저장소에서 관리객체를 가져올지 아니면 새로 운 관리객체를 만들지를 알수 없으므로 nil값을 할당		
		NSManagedObject *theLine = nil;
		
		// 컨텍스트에 페치 리퀘스트를 실행
		NSArray *objects = [context executeFetchRequest:request error:&error];
		
		// objects 변수가 nil값인지를 확인.. 만일 nil 값이라면 에러가 발생하였을 것이고 애플리케이션에
		// 필요한 에러 확인을 할 것이다. 
		if(objects == nil) {
			NSLog(@"There was an error!");
		}
		
		// 기준에 맞는 객체를 반환하였는지 확인하고 객체가 있다면 로딩하도록 한다.
		// 만일 객체를 반환받지 못하면 새로운 객체를 생성하여 텍스트 필드의 텍스트를 관리객체에 저장하기 위해 새로운 관리객체를 생성
		if([objects count] > 0)
			theLine = [objects objectAtIndex:0];
		else
			theLine = [NSEntityDescription insertNewObjectForEntityForName:@"Line" inManagedObjectContext:context];
		
		// 키-벨류 코딩을 사용하여 라인의 번호와 텍스트를 관리객체에 설정한다.
		[theLine setValue:[NSNumber numberWithInt:i] forKey:@"lineNum"];
		[theLine setValue:theField.text forKey:@"lineText"];
		
		[request release];
	}
	
	// 컨텍스트에 변경사항을 저장한다.
	[context save:&error];
}

- (void)viewDidLoad 
{
	// 애플리케이션의 델리게이트를 참조하는 레퍼런스를 만들고 애플리케이션의 기본 컨텍스트를 가져오는 코드를 생성
	Core_Data_PersistenceAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
	NSManagedObjectContext *context = [appDelegate managedObjectContext];
	
	// 엔티티를 나타낼 엔티티 디스크립션을 생성
	NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Line" inManagedObjectContext:context];
	
	// 페치 리퀘스트를 생성하고 엔티티 디스크립션을 리퀘스트로 넘겨줘서 어떠한 객체를 받아올 것인지를 설정
	NSFetchRequest *request = [[NSFetchRequest alloc] init];
	[request setEntity:entityDescription];
	
	// 저장소에 저장된 모든 Line 객체들을 받아와야 하므로 NSPredicate는 만들지 않고 리퀘스트를 실행하는 것으로
	// 저장된 모든 Line 객체를 가져오도록 컨텍스트에 요청
	NSError *error;
	NSArray *objects = [context executeFetchRequest:request error:&error];
	
	// 배열 값을 반환하였는지 확인하고 로그를 출력
	if(objects == nil)
	{
		NSLog(@"There was an error!");
	}
	
	// 루프 안에서 저장소에서 가져온 관리객체로부터 lineNum와 lineText값을 가져오고 사용자 인터페이스의 텍스트
	// 필드에 이 값들을 업데이트
	for(NSManagedObject *oneObject in objects)
	{
		NSNumber *lineNum = [oneObject valueForKey:@"lineNum"];
		NSString *lineText = [oneObject valueForKey:@"lineText"];
		NSString *fieldName = [NSString stringWithFormat:@"line%@", lineNum];
		UITextField *theField = [self valueForKey:fieldName];
		theField.text = lineText;
	}
	
	[request release];
	
	// 애플리케이션이 종료될 때 사용자가 수정한 모든 변경 사항을 저장할 수 있게..
	UIApplication *app = [UIApplication sharedApplication];
	[[NSNotificationCenter defaultCenter] addObserver:self
											 selector:@selector(applicationWillTerminate:)
												 name:UIApplicationWillTerminateNotification
											   object:app];
	
	[super viewDidLoad];
	
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	self.line1 = nil;
	self.line2 = nil;
	self.line3 = nil;
	self.line4 = nil;
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)dealloc {
	[line1 release];
	[line2 release];
	[line3 release];
	[line4 release];
    [super dealloc];
}


@end

* Persistence 뷰 컨트롤러를 애플리케이션의 최상위 컨트롤러로 지정하기
- 윈도우 기반의 애플리케이션 템플릿을 사용하였으므로 애플리케이션을 실행하기 전에 해야 할 일이 한가지 더 있다. 애플리케이션의 최상위 컨트롤러로 사용하기 위해 PersistenceViewController의 인스턴스를 생성하고 Persistence 뷰를 애플리케이션의 메인 윈도우의 하위뷰로 추가 해야 한다.

#### Core_Data_PersistenceAppDelegate.h ####
#import 
#import 

@class PersistenceViewController;

@interface Core_Data_PersistenceAppDelegate : NSObject  {
    
    UIWindow *window;
	PersistenceViewController *rootController;
    
@private
    NSManagedObjectContext *managedObjectContext_;
    NSManagedObjectModel *managedObjectModel_;
    NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet PersistenceViewController *rootController;

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

- (NSString *)applicationDocumentsDirectory;

@end

#### Core_Data_PersistenceAppDelegate.m ####
#import "Core_Data_PersistenceAppDelegate.h"
#import "PersistenceViewController.h"

@implementation Core_Data_PersistenceAppDelegate

@synthesize window;
@synthesize rootController;

#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
	
    // Override point for customization after application launch.
	
	[window addSubview:rootController.view];
    [window makeKeyAndVisible];
	
	return YES;
}

- 마지막으로 인터페이스 빌더로 돌아가서 최상위 컨트롤러의 인스턴스를 생성하고 조금 전에 생성한 아웃렛과 연결해야 한다.
- MainWindow.xib를 더블 클릭해서 인터페이스 빌더를 실행한다. 라이브러리에서 View Controller를 가져와 MainWindow.xib라는 nib 메인 윈도우에 놓는다. Func + 4를 눌러서 아이텐티티 인스펙터를 띄우고 하위 클래스를 UIViewController에서 PersistenceViewController로 변경한다. 그리고 Core_Data_Persistene App Delegate아이콘을 Persistence View Controller 아이콘으로 컨트롤-드래그 한다. rootController 아웃렛을 선택한다.
: