03 친해지길 바래 - 직원 급여 관리 애플리케이션
이전글 - 02 컨트롤 다루기 - 한줄 말하기 애플리케이션 / 루비 코코아 프로그래밍
이번 예제부터는 MVC 패턴이 적용된다. 일단 모델 클래스인 Person을 만드는 것부터 시작이다. 두개의 accessor를 제공하는 일반적인 객체이다.
- class Person < OSX::NSObject
attr_accessor :name, :expected_raise
def init
super_init
@name = "New Employee"
@expected_raise = 5.0
self
end
end
놀라운 점은, 이 것으로 6장의 예제에서 해야하는 코딩이 끝났다는 사실이다. 그 외에 컨트롤러와 뷰 부분은 인터페이스 빌더에서 모두 마무리했다. Person 객체의 배열을 다루는 ArrayController와 이 배열을 표현하는 TableView 만으로 깔끔한 애플리케이션을 완성할 수 있다는 점이 놀라웠다. 포매터를 이용한 % 표시, 정렬 등은 덤으로 얻어지는 기능이었다.
왜 전에 GUI 프로그래밍할 때는 이런 생각을 못했을까 싶다. 코코아 프레임워크는 컨벤션(예를 들면 키-값 코딩과 바인딩)을 통한 생산성을 추구한다는 면에서 레일스와 유사해보인다. 아, 바인딩 좋구려.
여기까지는 순조로웠는데, 그 다음에 값자기 난관에 부딪쳤다.
키-값 코딩 (Key-Value Coding)
Objective-C에는 키-값 코딩이라는 개념이 있다. 이를 통해 객체의 속성을 읽고 편집하는데 바인딩 메커니즘을 구현하는 기본이라고 하겠다. 루비 코코아에서 특정 속성이 키-값 코딩에 이용된다면 반드시 kvc_로 시작하는 접근자 메서드들을 사용해야한다(kvc는 키-값 코딩의 이니셜이다). 필자는 이 부분을 모르고 단순히 attr_accessor만으로 했더니, 바인딩이 잘 되지 않아서 몇 시간을 허비하였다. 제공되는 메서드는 다음과 같다.
- kvc_accessor / kvc_reader / kvc_writer
- kvc_depends_on
- kvc_wrapper / kvc_wrapper_reader / kvc_wrapper_writer
- kvc_array_accessor
이 예제에서는 MyDocument에서 @employees(Array 인스턴스)를 kvc_array_accessor를 통해 노출했다. 이렇게 하면, 코코아 프레임워크에서 MyDocument 객체나 insertObject_inEmployeesAtIndex나 removeObjectFromEmployeesAtIndex과 같은 메시지를 보내서 바인딩을 구현할 수 있게 된다.
이 부분은 필자보다 먼저 RaiseMan을 루비로 포팅하고 경험담을 블로깅한 Rubyfication of Raise Manager 글을 통해 알 수 있었다. 감사를.
루비가 코코아를 만났다
관련 자료를 찾던 도중, 루비 코코아에 대한 좋은 발표 자료도 볼 수 있었다. 루비 코코아를 소개하는 좋은 자료였다.
그림 출처: http://www.slideshare.net/Robbert/ruby-meets-cocoa
Undo & Redo
다시 구현으로 돌아와서 이번에 추가할 기능은 Undo와 Redo이다. 커맨드 패턴 설명에 항상 나오는 Undo와 Redo, 이론적으로 뭐 커맨드를 쌓을 스택 하나만 있으면 쉽게 구현할 것 같지만, 막상 하려면 만만치 않은 어려운 기능이다. 전에 이미지 편집기를 만들 때도 이 기능을 구현하려다가, 생각 이상의 복잡도에 나중으로 미뤄버렸던 기억이 난다. 하지만 요즘 세상에 사용자의 눈은 높아질대로 높아졌으니 꼭 필요한 기능인 것 같다. 코코아에서는 NSUndoManager라는 것과 NSInvocation을 지원해 Undo와 Redo를 조금 쉽게 구현할 수 있게 해준다. 당연히 맨땅에 헤딩하는 것보다는 쉽게 할 수 있을테지. 코코아가 시키는대로만 하면 멋진 애플리케이션을 만들 수 있을 것 같다.
각 이벤트(직원 추가 / 삭제 / 수정)를 잡아서 NSUndoManager에 넣어주는 것으로 구현은 끝났다. 아래 코드는 insertObject 메시지를 받았을 때 현재 추가되는 객체를 지우는 작업을 UndoManager에 넣는 코드이다.
- class MyDocument < OSX::NSDocument
kvc_array_accessor :employees
- def insertObject_inEmployeesAtIndex(person, index)
insert_undo("Insert Person"){|invocation| invocation.removeObjectFromEmployeesAtIndex(index) }
startObservingPerson(person)
@employees.insert(index, person)
end
def insert_undo(name)
undo = undoManager
yield undo.prepareWithInvocationTarget(self)
undo.actionName = name unless undo.isUndoing
end
end
그리고 직원의 이름이나 기대 인상률 값이 변경하는 액션도 Undo를 지원하기 위해서 Person 모델의 옵저버를 구현하였다.
- def startObservingPerson(person)
person.addObserver_forKeyPath_options_context(self, 'name', OSX::NSKeyValueObservingOptionOld, nil)
person.addObserver_forKeyPath_options_context(self, 'expected_raise', OSX::NSKeyValueObservingOptionOld, nil)
end - def observeValueForKeyPath_ofObject_change_context(key_path, obj, change, content)
old = change.objectForKey(OSX::NSKeyValueChangeOldKey)
insert_undo("Edit") {|invocation| invocation.changeKeyPath_ofObject_toValue(key_path, obj, old)}
end
NSObject#init
구현하는 동안 한가지 당황스러운 버그를 만나 거의 2~3시간 정도를 원인을 찾아 디버깅을 했다. 사람 추가는 잘 되는데, 삭제를 눌렀을 때 간혹 메모리 관련 문제를 호소하며(옵저빙 하고 있는 객체가 벌써 메모리에서 사라졌다) 애플리케이션이 죽는 문제가 발생했다. 도저히 원인을 찾을 수 없어 예제를 처음부터 다시 구현해봤지만, 소용이 없었다.
An instance 0x5d77dc0 of class Person is being deallocated while key value observers are still registered with it. Break on _NSKVODeallocateLog to start debugging.
그러다 발견한 것이 Person 모델 클래스에서 초기화를 하기 위해 구현한 initialize 메서드였다. 이 메서드를 init으로 바꾸니 모든 문제가 사라지고, 잘 돌아갔다. 아마 코코아 객체의 라이프사이클과 관련된 문제라고 추정된다. 한가지 교훈은 NSObject를 상속한 클래스의 경우는 초기화를 init에서 하는 것이 좋겠다는 사실이다. 루비 클래스니까 당연히라며 initialize에서 구현했다가는 필자처럼 원인도 모를 메모리 문제로 시간을 허비할지도 모른다.
여기까지가 책 6장과 7장을 읽으며 구현한 내용이다. 전체 소스 파일은 RaiseMan_ver1.zip에 있다.
후기
'친해지길 바래'라는 이 글의 제목이 정말 적절해보인다. 책의 내용은 어렵지않게 이해했지만, 이 내용을 루비 코코아로 옮기는데 한참의 시간이 걸렸다. 덕분에 루비 코코아를 조금 이해할 수 있게 되었다. Objective-C가 가진 동적인 특성 덕분에 이런 브리지 전략이 잘 돌아갈 수 있었던 것 같다. 그리고 루비나 Objective-C나 모두 스몰토크에서 영향을 많이 받은 언어여서 유사한 점도 많은 것 같다. 마지막으로 코코아 프레임워크를 생각하면 다시 한번 감탄사를 남긴다. 참 예쁘고 편하게 잘 만들었다.
다음 편 04 기능 두큰술 - 직원 급여 관리 애플리케이션 (2)에서는 RaiseMan을 좀 더 나은 애플리케이션으로 만들 것이다(전체보기: 루비 코코아 프로그래밍).
Comments (0)