[Cocoa] 코어데이터를 수동으로 적용하기

코어데이터 코어데이터 코어데이터. 쉽지도 않은 내용인데 이 블로그에서 최근에 코어데이터를 지긋지긋하게 많이도 다루는 것 같다. ㅠㅡㅠ 하지만 언젠가는 피가되고 살이될 코어데이터에 대한 내용이다.

이미 “간단한” 저장은 아주 손쉽게 Keyed Archiver를 사용하여 인코딩한 객체를 파일로 바로 저장하는 것은 살펴보았다. 하지만 만약, 저장한 주소록에 사람이 수백만명이라면 엄청나게 많은 데이터가 앱이 실행될 때 한번에 메모리로 로드되어 올라갈 것이다. (이것이 아카이빙으로 내용을 저장할 때의 한계이다. 많은 데이터는 결국 한 번에 로딩해서 안고 있어야 하는 부담이 있다.)

하지만 코어데이터는 굉장히 빠르게 영구저장소를 계속해서 액세스하고, 자동으로 차등저장 및 로딩을 지원하기 때문에 데이터세트가 어느 정도까지는 커져도 괜찮다. (적어도 나는 그렇게 알고 있다.)

iOS라면 UIManagedDocument를 사용하면 문서파일 자체를 코어데이터 영구저장소 파일(데이터베이스 파일)로 바로 사용할 수 있다. 이 내용은 이미 살펴본 바가 있는데, 문제는 NSManagedDocument 라는 것은 아직 공식적으로 존재하지 않는 클래스이다. (아 이런…) 결국 코코아 앱을 시작할 때 코어데이터를 적용해주지 않으면… 콸콸콸콸…

그래서, 만들어보기로 했다.

사실 코어데이터를 적용하여 시작한 앱 델리게이트 클래스를 적당히 수정하면 된다. 하지만 공부하는 차원에서 만들어보기로 한다. 그럼 여기서 다시 개념 정리의 복습이 있겠다.

1. managed object context

컨텍스트라고 줄여서 부르기도 하는 이것은 이를테면 메모리상에 존재하는 일종의 화이트보드 같은 것이다. 새 객체를 썼다가 없앨 수도 있고 이를 다시 영구저장소에 옮겨놓을 수도 있다. 대부분의 코어데이터 앱은 이 컨텍스트를 획득하기만 하면 컨텍스트가 마치 요술상자처럼 대부분의 일을 다 처리해준다.

2. manged object

managed…라고 특별히 ‘관리된다’라고 생각하기보다 이는 ‘코어데이터와 관계된’ 이라고 생각하는 편이 조금 더 도움이 될 것 같다. 코어데이터 모델 파일에서 디자인한 객체가 실제로 만들어진 형태라고 생각하면 된다.

3. managed object model

managed object 객체의 데이터 디자인에 대한 정보를 담고 있는 객체이다. 그러니까 객체 모양에 대해 기술하는 객체라는 의미인데, 중복된 정보같이 보이고 흔히 영문 레퍼런스를 볼 때는 심지어 managed object와 혼동을 유발하기도 한다. 이 객체는 실제로 여러 데이터를 조작하고 저장하고 하는 동작에는 영향을 미치지 않는다. 대신에 영구저장소에 기록되는 파일의 내부 구조 (스키마)가 managed object의 각 요소를 제대로 담을 수 있도록 하는데 쓰인다.

즉, managedObjectModel은 영구저장소 코디네이터가 사용하는 것이다. 또한 이 클래스는 인터페이스 빌더에서 디자인한 엔티티의 자료 모델 파일인 .xcdatamodel 파일을 컴파일한 .momd 파일로부터 만들어진다.

4. persistent store coordinator

컨텍스트는 메모리상에 존재하는 스크래치패드이다. 이 메모리상에서 놀고있는 데이터는 나중에 필요할 때 사용하기 위해 저장될 필요가 있다. 메모리 상에서 뛰어노는 컨텍스트에게 디스크 등 저장장치에 제대로 접근하기 위해 도와주는 클래스로 생각하면 된다. 이 객체는 한쪽에는 컨텍스트, 다른 한 쪽에는 영구저장소와 닿아있다.

5. persistent store

SQLite, XML 등의 형태로 managed object 들이 기록되어 있는 파일 그 자체를 말한다.

목표

코어데이터를 사용하려면 어떤 객체를 “managed” 한 상태로 만들어서(1) 디스크에 쓸 수 있도록(2)하면 되는 것이다. 즉 (1)에서 managedObjectContext 객체가 필요하고, (2)에서 persistentStoreCoordinator가 필요하다. (이후 컨텍스트, 코디네이터로 표시한다.)

그리고 이 과정은 코어데이터의 인프라에 따르면 다음과 같은 순서로 행해져야 함을 알 수 있다.

  1. 데이터모델 파일의 이름을 구한다. 이 파일은 컴파일 시에 앱 번들 내 리소스폴더에서 .momd 파일로 컴파일 된다.
  2. 데이터가 기록될 영구저장소 파일의 이름과 형식을 정한다. 특히 iOS에서는 XML타입은 지원하지 않고 오로지 SQLite 타입의 파일만 만들 수 있다.
  3. 컴파일된 모델파일(.momd)로부터 NSManagedObjectModel 객체를 생성한다.
  4. ManagedObjectModel로부터 NSPersistentStoreCoordinator 객체를 생성한다.
  5. 이 코디네이터는 파일의 스키마를 알게되었다. 이제 실제 저장될 파일을 코디네이터에게 알려준다. (-addPersistentStoreWithType…)
  6. managedObjectContext 객체를 만든다음, 이 컨텍스트에게 코디네이터를 소개해준다.

영구저장소 관리자 클래스 만들기

이름이 너무 거창한데, 간단히 말해서 모델디자인 파일의 이름을 알려주면 그 파일에 맞도록 설정을 만들고, 앱 번들 내에 영구저장소 파일을 생성해 컨텍스트를 쓸 수 있도록 만들어주는 클래스이다.

모델 객체 만들기

모델객체는 간단히 리소스 폴더에서 모델(이미 알고 있음)파일 이름과 같은 이름의 .momd 파일 (사실은 디렉터리이다.)을 사용하여, 이 내용을 토대로 모델 객체를 초기화하면 된다. 이는 프로퍼티로 설정해 두는 것이 나중에 사용하기 편하다.

-(NSManagedObjectModel*)managedObjectModel
{
  NSString *modelPath = [[NSBundel mainBundle] pathForResource:self.modelName extension:@"momd"];
  NSURL *modelURL = [NSURL URLWithString:modelPath];
  _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
  return _managedObjectModel;
}

코디네이터 만들기

코디네이터는 기본적으로 모델 객체를 기반으로 생성할 수 있다. 생성한 다음에는 번들 내 적당한 위치(문서 폴더나 리소스폴더)에 파일을 하나 정해, 이 파일을 코디네이터에 저장소로 추가한다. 그러면 코디네이터가 알아서 파일을 만들고, 모델의 내용을 기반으로 파일의 스키마를 잡아준다.

저장소로는 XML이나 SQLite가 모두 가능한데, iOS에서는 SQLite 만 쓸 수 있다. (아무래도 속도 때문인 듯)

-(NSPersistentStoreCoordinator*)persistentStoreCoordinator
{
  if (!_persistentStoreCoordinator) {
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
  [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType  configuration:nil URL:self.storeURL options:nil error:NULL];
  }
  return _persistentStoreCoordinator;
}

컨텍스트만들기

컨텍스트는 쿨하게 그냥 만들고, 코디네이터만 세팅해준다.

-(NSManagedObjectContext*)managedObjectContext
{
  if(!_managedObjectContext) {
    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
  }
  return _managedObjectContext;
}

첨부하는 예제파일에서는 initWithModelName: 으로 초기화한 후 managedObjectContext 프로퍼티를 통해 컨텍스트를 뽑아갈 수 있도록 했다.

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

p.s. 코코아앱을 신규로 만들면, 이제는 코어데이터 체크 여부에 상관없이 코어데이터 프레임워크는 기본으로 link 되어 있다.

[Cocoa] NSSound를 통한 초간단 사운드 재생

코코아에는 소리를 재생할 수 있는 NSSound 클래스가 있다. NSSound는 소리 재생을 위한 작업을 극도로 단순화하는 클래스로 인스턴스 메소드를 사용하는 것만으로 간단히 사운드를 재생할 수 있다. NSSound는 AIFF, WAV, NextSND 포맷을 재생할 수 있다. 음향 데이터는 디스크 상의 파일이나, 네트워크상의 URL, 페이스트 보드에 복사한 데이터가 될 수 있다. 또한 시스템에서 사용하는 사운드는 경로가 아닌 이름으로 재생이 가능하다. 이러한 시스템 사운드는 /Library/Sounds 디렉토리에 저장되어 있다.

NSSound는 음향 데이터를 여러 방법으로 로딩할 수 있다. 기본적으로 시스템 라이브러리에 등록되어 있는 소리, 파일의 경로, URL 등으로부터 사운드를 로딩할 수 있다. 로딩된 사운드에 대해 시작 / 일시정지 / 일시정지후 재생 / 정지 등의 기본적인 playback 기능이 제공된다.  또한 NSSound는 델리게이트 메소드를 사용하여 사운드의 재생이 끝나는 시점에 특정한 동작을 실행할 수 있다.

이름으로 로딩하기

NSSound *mySound = [NSSound soundNamed:@"Temple"]; 

파일로부터 로딩하기

 NSSound *mySound = [[NSSound alloc] initWithContentsOfFile:mySoundFilePath] 

사운드 파일을 열려면 open panel 을 사용하면 된다.

-(IBAction)openSoundFilePanel
{
int result;
NSOpenPanel *panel = [NSOpenpanel openPanel];
NSArray *filesToOpen;
NSString *theFilename;
NSMutableArray *fileTypes = [NSSound soundUnfilteredFileTypes];

[opnePanel setAllowMultipleSelection:NO];
result = [openPanel runModalForDirectory:NSHomeDirectory() file:nil types:fileTypes];

if(result == NSOKButton) {
filesToOpen = [openPanel filenames];
theFilename = [filesToOpen objectAtIndex:0];

[self _loadSoundFromPath:theFilename];
// 이미 정의해둔 재생 메소드
}
}

URL로부터 로딩하기 – 파일시스템의 URL만이 적용 가능하다.

 NSSound *mySound = [[NSSound alloc] initWithContentOfURL:mySoundURL byReference:NO]; 

파일의 재생

-play, -pause, -resume, -stop 의 메소드를 보낸다. 파일의 재생이 완료되면 -sound:didFinishPlaying: 이 호출된다.
반복재생의 경우에는 loop 프로퍼티를 YES로 주면 된다. 음량은 0.0~1.0 사이의 값으로 줄 수 있다.

재생 프로그레스

전체 시간과 현재 재생 지점도 알 수 있다. -currentTime, -duration을 사용하면 된다. 타이머를 사용하며 매 순간의 현재 재생 위치를 표시하는 것이 가능하다.

아이폰에서는  이처럼 단순화된 클래스는 아직 존재하지 않는듯하다. 코어 오디오를 사용해서 재생해야 한다. 코어오디오는 iOS/OSX에서 음향을 다루는 총체적인 기능을 제공하는 프레임워크로 믹싱, 이퀄라이저 등을 내장하고 있으며 음질의 영향을 주지 않으면서 배터리 수명을 향상 시키는 기술이 적용되어 있다.

[iOS] UIImage를 카메라롤에 저장하기

OSX에서 이미지(NSImage)를 이미지 파일로 저장하기 위해서는 NSBitmapImageRef 객체를 사용해서 이미지를 직렬화한 데이터를 이미지 파일 바이너리로 만드는 과정을 거쳐야 했지만 iOS에서는 UIKit이 다음과 같이 하나의 함수로 이를 제공하고 있다.

UIImageWriteToSavedPhotosAlbum(image, target, completionselector, &context);

  • image : UIImage 객체
  • target : 저장이 완료된 후 콜백을 받을 객체
  • completionSelector: @selector()의 형태로 타깃이 실행할 메소드
  • &context : 타깃에 전달될 정보

보통은 ‘성공’한다고보고, UIImageWriteToSavedPhotosAlbum(myImage,nil,nil,nil)과 같은 형식으로 쓰는데, 실패 시의 처리를 위해서는 타깃에 다음과 같은 모양의 메소드를 만들어 둔다. (이름보다는 모양이 중요하다)

-(void)image:(UIImage*)image didFinishedSavingWithError:(NSError*)error
     context:(id*)context

이를 호출하기 위해서는…

UIImageWriteToSavedPhotosAlbum(myImage,
                        self,
                        @selector(image:didFinishedSavedToPhotoAlbum:context:),
                        context:nil);

과 같이 쓰면 이미지를 저장하고 그 결과를 처리할 수 있게 된다.

[Python101] 004. 파일. 파일을 읽고, 파일에 쓰기

사용자로부터 입력을 받아 데이터를 처리하는 프로그램은 실질적으로 효율에 한계가 있다. 처리해야 하는 데이터를 사용자가 일일이 매번 입력해야 하기 때문이다. 컴퓨터는 귀찮고 반복적으로 처리하는 작업을 수월하게 하라고 있는 것이므로 프로그램은 가능한한 많은 과정을 자동화하는 방향으로 처리하는 것이 좋은 경우가 많다.

보통의 경우에는 다음과 같은 방식의 순서로  작업을 많이 처리하게 된다.

  1. 처리해야 할 데이터를 미리 준비 (텍스트 파일이나 엑셀 파일 등)
  2. 프로그램을 실행할 때 매개변수로 처리할 파일을 주고 실행한다.
  3. 프로그램이 데이터 파일을 읽어 들여서 데이터를 주르르르륵 처리한다.
  4. 처리한 결과는 또 다른 파일에 기록되거나 화면에 출력된다.

이것은 일종의 자동화 작업이고, 파이썬은 이런 작업에 대한 처리를 쉽고 빠르게 처리하기에 매우 좋다.

파일 읽기

데이터를 저장한 파일로부터 데이터를 읽는 방법에 대해 먼저 살펴보도록 하자. 예를 들어 메모장으로 작성한 텍스트 파일의 내용을 읽어들이고, 뭔가 내용을 쓰는 작업은 다음과 같은 절차를 거친다.

  1. 파일을 연다. 파일을 열게 되면 파이썬은 이 파일을 다루기 위한 핸들러(Handler)를 사용하게 된다.
  2. 파일의 내용을 읽어들인다. 파일의 내용을 읽어들이는 작업은 파일을 열면서 생성한 핸들러가 중계해준다. 핸들러는 파이썬 스크립트와 실제 데이터 파일 사이를 중계해주는 모듈이라 생각하면 된다.
  3. 파일에는 새로운 내용을 쓰거나, 기존 파일의 내용 뒤에 내용을 추가하는 것도 가능하다. 이 역시 핸들러에 의해 데이터를 쓸 수 있다.
  4. 파일 다 사용했으면 파일을 닫는다. 만약 파일을 적절히 닫지 않으면 파이썬 프로세스가 정상적으로 종료되지 못하거나, 다른 프로그램이 해당 파일을 쓰지 못하는 경우가 발생할 수 있다.

파일을 열기 위해서는 open 명령을 사용한다. open 명령은 다음과 같이 사용한다.

 파일핸들러 = open('파일의 경로', [모드]) 

open  명령을 사용해서 파일을 열면 파일의 핸들러를 얻을 수 있다. 이 핸들러를 통해 파일의 내용을 읽어들이거나, 파일에 데이터를 쓰는 작업이 가능해진다.

ex09.py

아홉번째 예제는 텍스트 파일을 열어서 그 내용을 보는 예제이다.이 예제를 제대로 실행하기 위해서는 텍스트 파일 하나가 필요하다. 메모장을 열어 아무 텍스트나 몇 줄 입력한 후에 예제를 저장하는 폴더에 저장한다. 이름은 구분하기 쉽게 ex09.txt로 하겠다.

April

Still an irritating wind;
Vestiges of stubborn grey –
Jibing us of recent winter blight.

It’s coming though – like perky breasts
Pushing through a blouse –
Teasing, pleasing in it’s tantalising play:

Warmth of youth in April sun –
Simmering off depression,
Brains retuned; remapped for fun.

April is a portal –
Smoothly transitions
Delicate dispositions – suchlike mine,

Easing hunched bodies into
Summery smiles.

이제 ex09.py 의 소스 코드는 다음과 같다.

from sys import argv

script, filename = argv

txt = open(filename)

print "Here's your file %r:" % filename
print txt.read()

print "Type the filename again:"
file_again = raw_input('> ')

txt_again = open(file_again)
print txt_again.read()

argv 가 등장하는 것을 보니, 파라미터를 받고 실행하는 프로그램이다. 실행은 앞의 강좌에서 소개했던 것과 같은 방식으로 하면 된다.

> python ex09.py ex09.txt

Here's your file 'ex09.txt':
April

Still an irritating wind;
Vestiges of stubborn grey
Jibing us of recent winter blight.

It’s coming though  like perky breasts
Pushing through a blouse
Teasing, pleasing in it’s tantalising play:

Warmth of youth in April sun
Simmering off depression,
Brains retuned; remapped for fun.

April is a portal
Smoothly transitions
Delicate dispositions ? suchlike mine,

Easing hunched bodies into
Summery smiles.
Type the filename again:
> ex09.py
from sys import argv

script, filename = argv

txt = open(filename)

print "Here's your file %r:" % filename
print txt.read()

print "Type the filename again:"
file_again = raw_input('> ')

txt_again = open(file_again)
print txt_again.read()

자 이제 코드를 좀 살펴보자.

1) 3행을 보면 파일 이름을 argv로부터 읽어온다.

2) 5행에서는 이 파일 이름을 사용하여 해당 파일을 연다. 파일 이름만 전달 받았으므로 파이썬 코드 파일과 같은 폴더 내의 파일을 탐색하게 된다. 그리고 파일을 열어서 그 핸들러는 txt라는 이름의 변수에 대입된다.

3) txt는 곧 파일 핸들러라고 했다. 파일 핸들러는 여러 기능 모듈을 가지고 있는데 그 중 하나가 read() 이다. 이 모듈은 파일의 내용을 전부 읽어들이는 일을 한다.

이 때 txt 모듈의 read() 를 실행하기 위해서는 구두점(.)으로 이를 연결해준다. 즉 txt.read() 는 txt 모듈의 read라는 기능 모듈을 실행한다는 뜻이다. 실행이 가능한 모듈은 주로 ()가 뒤에 붙는데, 이렇게 실행이되는 모듈을 특별히 ‘함수’라고 한다.

txt.read() 모듈은 파일 전체의 내용을 읽어 이를 돌려준다. 8행은 이 결과를 모두 출력하게 된다.

4) 10행에서는 다른 파일 이름을 다시 물어본다. 나는 ex09.py를 입력했다. 파이썬 스크립트 소스 자체도 텍스트 파일이다. 단지 관습적으로 .py 라는 확장자를 썼을 뿐이다.

5) 주목할 것은 마지막의 14행이다. txt_again은 파일을 열고 그 핸들러를 대입하게 된다. 즉 txt_again이 파일의 핸들러이고, open() 이 실행된 결과와 동일하다. 즉 open()의 결과가 파일 핸들러라는 이야기다. 따라서 13, 14행은 다음의 한 행으로 합칠 수 있다.

print open(file_again).read() 

파일에 쓰기

ex09.py는 파이썬에서 다른 텍스트 파일의 내용을 읽어와서 사용할 수 있음을 보여주었다. 주소록 정리라든지, 성적 계산, 혹은 이름-이메일 정보를 가진 주소록을 가져와서 받는 사람 주소에 맞게 사람 이름이 들어가는 이메일을 보내는 프로그램 등을 만들 수 있을 것이다.

이번에는 내용을 파일에 기록하는 작업에 대해 살펴보자. 지금까지는 open() 명령에서 모드를 따로 지정하지 않았다. 모드를 따로 지정하지 않은 open()은 기본적으로 “텍스트 파일”을 “읽기”모드로 열게 된다. 파일을 기록하기 위해서는 파일을 w 모드로 열면 된다. w 모드로 파일을 여는 것은 사실 새로운 파일을 만들고 그곳에 내용을 기록하는 것을 의미한다. 따라서 파일 이름이 기존에 있던 내용이면, 그 파일을 덮어쓰게(overwrite) 된다는 점을 참고하자.

ex10.py는 사용자로부터 3줄의 텍스트를 입력받아 이를 파일에 기록해주는 프로그램이다.

from sys import argv

script, filename = argv

print "We're going to erase %r." % filename
print "If you don't want that,hit Ctrl-C(^C)."
print "If you do want that, hit RETURN."

raw_input('?')

print "Opening the file..."
target = open(filename, 'w')

print "Truncating the file. Goodbye!"
target.truncate()

print "Now I'm going to ask you for three lines."

line1 = raw_input("Line 1:")
line2 = raw_input("Line 2:")
line3 = raw_input("Line 3:")

print "I'm going to write these to the file."

target.write(line1)
target.write('\n')
target.write(line2)
target.write('\n')
target.write(line3)
target.write('\n')

target.close()

코드를 보면 알겠지만, 텍스트 파일의 이름을 파라미터로 받고 있다. 따라서 콘솔에서 파일을 실행할 때 텍스트 파일의 이름을 준다. 이 코드에는 파일의 내용을 지우는 truncate() 명령이 포함되어 있는데, 위에서 작성했던 ex09.txt 파일을 변경하도록 해보자. 결과는 아래와 같고, 하이라이트 된 라인은 실제 프롬프트에서 명령을 입력한 라인이다.

c:\Apps\textfiles>c:\Python27\python ex10.py ex09.txt
We're going to erase 'ex09.txt'.
If you don't want that,hit Ctrl-C(^C).
If you do want that, hit RETURN.
?
Opening the file...
Truncating the file. Goodbye!
Now I'm going to ask you for three lines.
Line 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ
Line 2:abcdefghijklmnopqrstuvwxyz
Line 3:0123456789
I'm going to write these to the file.

c:\Apps\textfiles>type ex09.txt
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789

텍스트 파일의 이름을 파라미터로 주고 프로그램을 실행하면 먼저 기존 파일의 삭제 여부를 묻게 된다. 엔터를 누르면 계속 진행을 하게 되고 파일을 지운다. 그런 다음 세 줄의 텍스트를 입력 받고, 이를 파일에 기록하게 된다.type 명령 (터미널에서는 cat 명령)으로 파일의 내용을 살펴보면 입력한 내용으로 해당 파일의 내용이 바뀌어 있음을 볼 수 있다.

코드의 내용을 분석해보도록 하자.

1) 파일의 이름을 filename 이라는 변수에 대입해준다. 이 파일 이름은 파라미터로 전달받은 값이다.

2) 5~7 행에서는 파일의 이름을 표시하고, 취소할 기회를 준다. 이때 ^C (CTRL+C)를 누르면 프로그램의 실행이 취소되고, 엔터를 누르면 다음으로 진행하게 된다.

3) 11~15행에서 해당 파일을 “쓰기 모드”로 열고 내용을 모두 지운다. 파일핸들러의 truncate() 명령은 내용을 모두 지우는 기능을 수행한다. 사실 “w”를 사용해 쓰기 모드로 파일을 열고 다른 내용을 기록하면 이전의 내용은 모두 지워지게 된다. 따라서 15행은 실질적으로는 필요 없는 셈이다. (15행을 주석 처리하고 한 번 실행해보라.)

4) 17~21행에서 다시 각 라인을 입력 받아 line1, line2, line3에 각각 입력 받은 내용을 추가한다.

5) 25~30 행에서는 파일 핸들러 target의 write() 기능을 이용해서 각각의 문자열을 파일에 쓴다. 이 때 각 line1, line2, line3 은 줄바꿈 문자를 포함하고 있지 않으므로 이를 추가해 준다. 물론 이는 target.write(line1+”\n”) 과 같이 써서 줄바꿈 문자를 더한 문자열을 파일에 써도 된다.

6) 끝으로 파일을 닫는다.

물론 하나의 프로그램에서 파일을 2개 이상 동시에 여는 것도 가능하다. 각각의 열린 파일은 핸들러로 조작할 수 있으므로 2개의 핸들러를 open 명령으로 만들어주면 된다.

파일 복사 프로그램

이 글의 서두에서 언급한 파일 복사하는 프로그램을 작성해 보겠다. 사실은 무척이나 단순하다. 이 프로그램은 1) 파라미터로 원본 파일과 복사할 위치를 입력받는다. 2) 원본 파일의 길이(몇 바이트나 되는지)를 알려주고, 3)복사할 위치에 이미 파일이 존재하는지를 알려준다. 그런 다음 4) 원본 파일의 내용을 읽어와서 사본 파일에 쓴다. 5) 두 파일을 닫는다.

ex11.py

이상의 내용을 코딩한 ex11.py의 내용은 다음과 같다.

#-*-coding:utf-8
from sys import argv
from os.path import exists

script, from_file, to_file = argv

print "Copying from %s to %s" % (from_file, to_file)

input = open(from_file)
indata = input.read()
print "The input file is %d bytes long." % len(indata)

print "Does the output file exists? %r" % exists(to_file)
print "Reday, hit RETURN to continue, CTRL-C to abort."
raw_input()

output = open(to_file, 'w')
output.write(indata)

print "Alright, all done."

output.close()
input.close()

실행 결과는 다음과 같다. 이미 만들어져있는 그리고 만만한 ex09.txt를 다른 파일로 복사해보도록 하겠다.

c:\Apps\textfiles>c:\Python27\python.exe ex11.py ex09.txt ex09.bak
Copying from ex09.txt to ex09.bak
The input file is 65 bytes long.
Does the output file exists? False
Reday, hit RETURN to continue, CTRL-C to abort.

Alright, all done.

c:\Apps\textfiles>type ex09.bak
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789

코드 내용 분석을 보자. 무척이나 단순하고 간단한데, 못 보던 기능을 발견했다. 바로 os.path와 exists 라는 명령이다.

1) os.path 로부터 exists라는 모듈을 반입해온다. os 는 파이썬 프로그램이 실행되는 컴퓨터의 운영체제와 관련된 모듈들의 집합이다. 그리고 그 중에는 path라는 모듈이 있는데, 이 모듈은 디스크 내의 경로, 파일 등과 관련된 기능들이 모여있다. exists 모듈은 path에 들어있는 하위 모듈인데, 주어진 경로에 파일이 이미 존재하는지를 검사하여 존재한다면 True를 돌려주는 기능을 한다.

2) input 은 원본 파일을 열어서 그 핸들러를 얻는다. input의 read() 명령을 통해 파일의 전체 내용을 읽어 indata에 대입한다.

3) 13~15는 파일의 길이를 알려주고, 복사본 위치를 찾아 거기에 이미 파일이 있는지를 알려준다. 엔터를 입력하면 raw_input() 뒤의 코드가 계속 실행된다.

4) ‘w’ 모드로 복사본 파일을 연다. 그런 다음, indata를 쓴다.

5) 끝으로 두 파일을 닫으면 된다.

위의 실행 결과에서 알수 있듯이, 파라미터로 준 파일을 보면 내용이 복사되어 있음을 알 수 있다.

사실 파일 입출력은 쉬운 내용은 아니다. ‘핸들러’라는 눈에 보이지도 않는 추상적인 개념을 도구로 다루기 때문이다. 하지만 이런 추상적인 개념은 사실 이해하기보다는 익숙해지면 쉽게 느끼게 된다. (우리가 흔히 하는 덧셈, 뺄셈도 분명 그 원리는 우리가 알고 있지만 엄밀하게는 익숙하게 느끼기 때문에 쉽게 생각할 수 있는 것이다.)

파일 입출력에 대해 간단히 알아보았다. 이로써 기본적인 입력과 출력을 처리할 수 있는 방법에 대해서는 기본기를 파악했으니, 다음 시간부터 실제 프로그램을 움직이는 흐름인 로직에 대해 살펴보기로 하겠다.

[UIKit] UIView를 이미지로 캡쳐하기

NSView를 이미지로 캡쳐할 때는 뷰의 영역에서 이미지 데이터를 바로 추출하는 방식을 사용한다. 때문에 NSBitmapImageRep의 -initWithFocusedViewRect: 메소드를 사용한 후 -presentationUsingType:properties: 를 사용하여 각 그래픽 포맷에 맞는 데이터를 얻을 수 있었다. (아니면 바로 TIFFRepresentation을 사용하여 데이터를 얻고 이를 이미지로 만들어도 된다.)

아이폰에서는 NSBitmapImageRep가 없어서 이 방법을 사용할 수 없다. 대신에 이미지를 그래픽 컨텍스트에서 얻을 수 있다는 점을 이용하여 다음과 같은 방법을 쓸 수 있다.

-(UIImage*)captureView:(UIView *)theView
{
  UIGraphicsBeginImageContextWithOptions(theView.bounds.size, theView.isOpaque, 1.0);
  theView.layer renderInContext:UIGraphicsGetCurrntContext];
  UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
  return UIImage;
}

그럼 UIKit에서 이미지를 파일로 저장하려면 어떻게 해야할까? 코코아에서는 NSBitmapImageRep이 그래픽 포맷에 대응하는 데이터를 제공했는데, UIKit에서는 이 클래스를 사용할 수 없다고 했다.

UIKit은 이를 위해 다른 함수를 제공하고 있다. UIImageJPEGRepresentation()UIImagePNGRepresentation()이 그것이다. 즉 모바일 기기에서 통용할 수 있는 그래픽 포맷은 사실상 PNG, JPEG 이므로 이 둘을 생성하는 함수를 아예 프레임워크가 지원하고 있다.

이들 함수는 각각 다음과 같이 선언되어 있으며, 이 함수를 통해 얻은 데이터를 디스크에 기록하면 바로 JPEG, PNG 파일을 얻을 수 있다. 참고로 compressionQuality는 0.0~1.0으로 1.0이 가장 좋은 화질을 의미한다.

NSData * UIImagePNGRepresentation ( UIImage *image);
NSData * UIImageJPEGRepresentation (
          UIImage *image,
          CGFloat compressionQuality );