[19금] 학교 폭력에 대한 답

[주의 : 본 포스팅은 매우 저렴한 언어들로 점철될 우려가 있음]

연일 터지는 학교 폭력, 그리고 피해학생의 자살… 뭐 이제는 까딱하다간 말하면 입아파지는 수준에 이를지도 모르겠다. 그리고 한켠에서는 뭐 청소년 단체니, 여성 단체니, 교사 공무원이니… 하는 사람들이 책상 머리에 앉아서 “아이들에게 어쩌구 저쩌구…” 썰을 풀면서 온갖 학교 폭력 대책을 내놓고 일부에서는 “시험적으로” 시행해보기도 하니, 역시나 “시험적으로 성과가 있는 것처럼 보고”되고 있을 뿐이다.

그리고 문제를 “학교 폭력”이라고 부르는 것부터가 별로 좋지 않은 프레임을 짜고 시작하는 일이다. ‘학교 폭력’이라고하면 기껏해야 왕따나 아니면 애들 사이에 주먹다짐 정도로 문제를 축소해서 비추는 효과를 갖고 있기 때문이다. 학교 폭력이라는 이 문제는 정확하게 말하면 “미성년자 강력범죄”의 개념을 명확하게 드러내는 단어로 명칭을 바꿔야 한다. 그리고 그 문제에 대한 해법 역시 단순한 ‘학교폭력’이 아닌 ‘강력범죄’로 인식했을 때 실마리를 찾을 수 있다.

몇 년전에 밀양 개새끼들, 다들 기억이나 하고 있는지 모르겠다. 그 때도 그 개새끼들에 대해 포스팅을 하나 내면서 “아무도 처벌받지 않을 거고, 밀양에서 딸키우기 힘들거다”라는 식의 말을 했었는데, 아주 슬프게도 그 말 그대로 됐다. 40명이 자매를 1년간 강간했는데, 5명이 반성문 좀 썼고, 나머지는 아무런 조치도 취해지지 않았다.

이러한 배경에는 다음과 같은 조건들이 작용한다.

1. 가해자 일부 학생의 부모라는 인간들이 밀양 지주쯤 되나보다. 아무튼 경찰서장이나, 형사과장.. 계장.. 아니면 그냥 형사들 한테 형님 소리는 좀 들을 것 같다.

2. 교사라는 인간들은 “나만 보신하면 그만”이라는 생각에 사로잡혀 있었을 것이다. 이건 뭐 “교직사회”라는 곳을 생각해보면 굉장히 당연한 일이라 생각된다.

3. 뭐 사고를 좀 쳤을 때 학생이면 가볍게 처벌하거나 거의 처벌하지 않는다. 나의 기억을 돌이켜 볼 때 이건 20년 전 내가 중학생일 때도 유효했다. 양아치 새끼들이 좀 움찔했던 순간은 같이 뽄드 빨던 녀석이 숨진채 발견된 날 아침뿐이었다. 돈 뺏다 경찰서 갔다온 다음날도 희희낙락하다가 다른 친구가 조사 받으러 가게 되면 “어차피 학생은 반성문만 쓰면 돼”라고 조언까지 날려주는 여유.

이 몇 가지 조건들이 결합하면서 법치 국가에서는 일어날 수 없는 일이 그냥 일상화되기 시작한다. “앞길이 구만리” 이야기가 나오면서 좋은 게 좋은 거고… 형사들은 집어넣어야 마땅한 개새끼들이 알고보니 아는 형님 아들이고…

결국 그누구도 처벌받지 않았고 피해 여학생은 전학을 갔지만, 가해 부모들의 괴롭힘 (합의해 달라 어쩌라)이 이어져 결국 행방불명되는 처지에 이르렀다.인생이 망가진 지경을 떠나 생사도 확인이 안되는 듯.

물론 아주 직접적인 연쇄 작용은 아니지만, 몇 년 후, 밀양의 저 개새끼들이 딱 그나이가 될 무렵에 고대에서도 사건이 하나 벌어지지. 뭐 처벌이 어느 수준까지 이뤄졌는지는 모르지만(워낙 고대에서는 걸출한 분들이 많이 나와서…), 그 때 여론이 상당히 험한 분위기로 일어났고, 아마 그런 분위기조차 이뤄지지 못했다면 제 2의 밀양 개새끼들이 내과 산부인과를 나의, 그리고 당신의 동네에 개업해서 무슨 변태 행각을 벌일지 모를 일일뻔했다.

(천만 다행으로 아마 그녀석들은 출교조치가 되어 의사 면허를 딸 수 없게 되었다. 그런데 이게 “여론에 떠밀려” 결정된 거라는게 문제다. 게다가 고대 출신 의사들은 이 건에 대해 조용하더라? 병원을 갔는데 의사가 고대출신이면… 나라면 좀 긴장하겠다.)

청소년 범죄에 대해 이런 멋진 전례를 남겨주신 덕분에 똥만 가득찬 여러 교사들의 머리에서 나오는 발상들 중 어느 하나도 유의미한 실효성을 가진 학교 폭력 방지 대책이 될 수 없다고 단언한다.이건 단순히 애들 싸움의 문제가 아니라 “폭력 사건”이 ‘학교’라는 프레임속에서 다뤄질 때의 사회 구조의 문제이다.

답은 단 하나 “학생 신분으로 벌인 범죄에 대해서는 성인과 동등한 수준의 처벌을 받게 하며, 특정 진로를 택하려할 때 불이익을 받도록 강제”하는 방안이 필요하다. 너무 심하다고? 40마리가 넘는 미친개가 당신 딸을 1년동안 물어뜯었을 때도 이런 처벌이 그때도 무거운지 다시 생각해보라. 이게 말이 안된다고 생각될지 모르지만, 이런 강력한 처벌은 다음과 같은 효과를 가져온다.

“학교 폭력” 혹은 “청소년 강력범죄” 예방이라는 게임의 플레이어를 “피해자 vs 가해자” 구도에서 “교사 + 학부모 + 학생 당사자” 모두로 확대한다. 처벌이 강력해지면 이 게임은 실전이 될 수 밖에 없다. 행동 잘 못 했다가는 인생 좆되는 거 한순간이 되는 게 벌어질 수도 있다는 것이다.

“학교 폭력”이란 게 애들이 장난치다 보면 격해져서 서로 주먹질도 할 수 있고 어쩌고… 만약 실제로 이렇게 강력하게 처벌한다면 애들은 알아서 조심할 뿐 더러. 주먹 한 번 잘 못 놀렸다간 고등학교도 못가고 대학교도 못가게 생기면 부모들이, 선생들이 애들한테 마르고 닳도록 교육시키지 않겠는가?

아마 유치원도 들어가기 전의 아이들에게 가르치는 1순위가 영어 따위가 아니라 “친구와 싸우지 마라. 다른 사람이 싫다는 행동은 하지마라.”는 기본적인 예의와 상식이 될 것이다. 즉, 단순히 애들한테 “싸우지 마라”는 교육만 하고 사회 시스템의 책임이 끝나는 지금과 같은 상황에서 “전체 교육과정에서 절대 넘어서는 안되는 선”을 확정해주고 이를 지킬 수 있도록 교사와 학부모가 신경을 안 쓸 수 없도록 만드는 효과가 있다. 단순히 처벌의 문제가 아니다.

이런 분위기가 만들어지면 자연스럽게 “피해자 중심의 후조치”가 가능해진다. 일단 무조건 빵에 가는 거다. 아예 합의 따위도 없도록 만들어야 피해학생, 피해학부모에게 2차적인 위해가 없을 게다. 아마 이렇게 딱 10년만 시행해본다면, 물론 부작용이 없을 수 없겠지만 학교 폭력에 대한 사회 인식은 바꿀 수 있다.

앞길이 구만리 같은… 개새끼들 앞길 생각해주다가 무슨 일이 일어났는지 아까 밀양 개새끼 사건에 덧붙여 재밌는 일이 있더라.

http://bbs3.agora.media.daum.net/gaia/do/petition/read?bbsId=P001&articleId=121797

밀양, 대단하다. 고담대구니 갱스오브부산이니 꺼지라그래라.

아마 5년 안에 “경찰의 보호를 받던 여성이 경찰에게 성폭행당해…”라는 기사가 밀양 발로 뜨는 날이 올 거 같은 확신이 든다. 그리고 저런 인원을 자체적으로 걸러내지 못하는 공무원 사회를 두고서 위에서 내가 언급한 “교직사회라면 당연하다”는 식의 막말에 대해서는 읽는 누가 기분 나빠도 별로 미안해하고 싶은 마음 따위는 없다.

 

[Cocoa] NSFetchedResultsController

코어데이터와 UITableView와의 연결

지금까지 몇 개의 예제를 통해 코어데이터를 사용해서 일련의 데이터를 디스크에 읽고 쓰며, 이를 관리하는 방법에 대해 살펴보았는데, 이런 작업을 보다 쉽게 만들어주는 컨트롤러가 있었으니, 바로 NSFetchedResultsController이다.이 클래스는 코어데이터의 컨텍스트를 기반으로 저장소로부터 조건에 맞는 객체를 읽어들여서, UITableView의 데이터소스 메소드에서 쉽게 사용할 수 있는 형태로 제공한다. 또한 읽어온 객체에 대해 추적 기능을 가지고 있어서 managed object에 어떤 변경이 발생할 때 이를 감지하여 적절하게 테이블 뷰에서의 변경을 만들어낼 수 있다.

단, NSFetchedResultsController는 저장소나 컨텍스트를 생성하지는 못한다. 코어데이터 프로그램에서 수동으로 배열을 만들어 불러온 데이터를 관리하는 부분에서의 코드의 양을 줄이는 용도로 사용할 수 있다는 것이 이 클래스의 의의랄까.

NSFetchedResultsController는 이름에서처럼 불러온 데이터들을 제어한다. 따라서 데이터를 불러오는 데 필요한 managed object context, fetch request, predicate, sort descriptor 등은 일반적인 코어데이터 프로그램에서와 같이 우리가 일일이 생성해 주어야 한다. 대신, 이 컨트롤러는 UITableView의 indexPath에 대한 접근 방식에 일치하는 메소드들을 가지고 있으므로, 데이터소스를 생성하는데 매우 편리하게 활용된다.

생성방법

컨트롤러의 생성을 위해서는 initWithFetchRequest:… 메소드를 사용하면 된다. 이 때 필요한 파라미터로는 다음의 것들이 있다.

  1. (NSFetchRequest*)fetchRequest
  2. (NSManagedObjectContext*)managedObjectContext
  3. (NSString *)sectionNameKeyPath
  4. (NSString*)cacheName

기본적으로 fetchRequest는 별도로 생성해야 한다. 섹션네임키패스는 섹션이 여러 개인 경우 각 섹션의 이름을 제공하는 객체를 지정한다. 보통 섹션을 하나만 사용하는 경우에는 nil을 넘기면 된다. 또한 캐시 이름은 캐시를 저장할 파일을 생성한다. nil을 사용하면 메모리내에서만 추적이 일어나는데, 이는 약간의 오버헤드를 유발할 수 있다. 대신 서로 다른 엔트리에 대해서 컨트롤러를 반복해서 재사용해야 하거나 할 때는 일일이 캐시를 삭제해 주어야 하므로, 상황에 맞게 사용하면 된다. 또한 캐시는 단순히 이름만 주면 된다.

다음은 컨트롤러를 생성하는 예제이다. 해당 클래스에는 컨텍스트, 패치 리퀘스트, 컨트롤러가 각각 프로퍼티로 설정되어 있다고 가정한다.

<# managedObjectContext를 구함 #>
...

-(NSFetchRequest*)fetchRequest
{
  if(!_fetchRequest) {
    _fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entityDescription = [NSEntityDescription EntityForName:@"Memo" inManagedObjectContext:self.managedObjectContext];
    [_fetchRequest setEntity:entityDescription];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isAvailable == YES"];
    [_fetchRequest setPredicate:predicate];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastModifiedDate" ascending:NO];
    [_fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
  }
  return _fetchRequest;
}

-(NSFetchedResultsController*)controller {
  if (!_controller) {
    _controller = [[NSFetchedResultsController alloc] initWithFetchRequest:self.fetchRequest
        managedObjectContext:self.managedObjectContext
        sectionNameKeyPath:nil
        cacheName:nil];
  }
  return _controller;
}

performFetch

컨트롤러에 performFetch 메시지를 보내면 코어데이터 저장소로부터 지정된 조건에 맞게 결과를 가져와 그 결과값을 컨트롤러 내부에서 관리하기 시작한다. 만약 델리게이트가 지정되어 있다면, 컨트롤러는 불러온 결과에 대한 변경을 추적하고 변경이 생기면 이를 델리게이트에게 알려준다.

테이블 뷰와의 결합

컨트롤러는 sections, sectionIndexTitles의 프로퍼티와 objectAtIndexPath:, sectionForSectionIndexTitle:, sectionIndexTitleForSectionName: 등의 메소드를 가지고 있고, 이를 사용하여 테이블 뷰에 데이터소스로 연결될 수 있다.

다음은 테이블뷰의 데이터소스를 구현한 예이다.

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
{
  return [self.controller.sections count];
}
-(NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView
{
  return self.controller.sectionIndexTitles;
}
-(UITableView*)sectionForSectionIndexTitle:(NSString*)title atIndex:(NSInteger)index
{
  return [self.controller sectionForSectionIndexTitle:title atIndex:index];
}
-(UITableView*)tableView titleForHeaderInSection:(NSInteger)section
{
  id<NSFetchedResultSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
  return [sectionInfo name];
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
  id<NSFetchedResultsSectionInfo> sectionInfo = [[self.controller sections] objectAtIndex:section];
  return [sectionInfo numberOfObjects];
}
-(UITableViewCell)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
  UITableViewCell *cell = [tableView dequeReusableCellWithIdentifier:@"cellIdentifier"];
  NSManagedObject *managedObject= [self.controller objectAtIndexPath:indexPath];
  <# cell에 표시될 내용을 memo로 부터 지정 #>
  cell.textLabel.text = [managedObject valueForKey:@"title"];
  ...
  return cell;
}

각각의 섹션은 NSFetchedResultsSectionInfo 프로토콜을 따르는데, 이 프로토콜에는 indexTitle, name, numberOfObjects, objects이 정의되어 있고, 이를 통해 섹션과 관련한 정보를 테이블 뷰에 제공할 수 있다.

데이터의 변경

데이터가 추가/삭제/변경될 때 컨트롤러의 델리게이트가 있다면 (그리고 그 델리게이트가 최소 1개의 델리게이트 메소드를 구현했다면) 컨트롤러는 로드된 데이터를 추적하게 된다. 그리고 변화가 발생할 때 델리게이트에게 적절한 메시지를 보내게 된다.

-controllerWillChangeContent:
 -controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:
 -controller: didChangeSection: atIndex: forChangeType:
 -controllerDidChangeContent:1</pre>
이 때 가장 많이 사용하는 것은 두 번째의 것으로 각 객체의 추가/삭제에 대한 내용으로 이는 다음과 같이 구현된다. 특히 이 메소드는 여러 개의 데이터가 한 번에 변경되는 경우에, 여러 차례 반복해서 호출되므로 이에 신경을 써야 한다.

1- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{        
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeUpdate:
                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeMove:
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}

또한 섹션 자체의 변경에 대해서는 다음과 같이 구현할 수 있다. 거의 1대 1로 대응된다고 보면 된다.

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
        switch(type)
        {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}

또한, 컨텐츠의 변경 시작과 끝을 알리는 두 메소드는 tableView의 beginUpdates, endUpdates를 각각 호출하여 변경 사항이 UI에 한 번에 적용되는 것으로 보이도록 할 수 있다.

[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);

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