키밸류 코딩이란

NSObject는 Objective-C의 표준 라이브러리라 할 수 있는 Foundation에서 가장 기본이 되는 최상위 클래스에 해당한다. 커스텀 클래스를 만들 때 아무 생각없이 상속받는 이 클래스는 Objective-C에서 클래스라는 것이 마땅히 갖추어야 하는 여러 가지 기능들을 미리 구현해둔 것이 아주 많이 있다. 그 중에서도 키밸류 코딩이라는 기술을 위한 기본적인 기능이 NSKeyValueCoding이라는 비정규 프로토콜에 정의되어 있고, NSObjects는 이를 따르고 있다. 따라서 몇가지 간단한 규칙을 지키면서 프로퍼티를 정의하기만 하면, 우리가 작성하는 모든 클래스의 프로퍼티들이 키밸류 코딩 호환이 될 수 있다. 그렇다면 키밸류 코딩은 무엇이고, 또 어떻게 활용되는 것인지에 대해서 살펴보자.

프로퍼티

키밸류 코딩은 어떠한 객체의 프로퍼티 값에 대해서 미리 정해진 접근자가 아닌 해당 프로퍼티의 이름 키를 사용해서 특정한 객체의 프로퍼티를 액세스하는 것을 말한다. 예를 들어서 어떤 클래스 Foo 에서 bar 라는 프로퍼티를 가지고 있다고 가정하고, 클래스 Foo를 작성하는 과정을 살펴보자. 먼저 Objective-C에서 어떤 클래스가 임의의 값을 저장하고 있으려면 그 값을 저장할 스토리지 변수가 필요하다. Objective-C의 클래스는 본질적으로 그 내부를 알 수 없는 불투명 구조체의 포인터이며, 구조체 내부의 멤버 변수는 인터페이스 선언부 최상단에 블럭을 사용해서 선언한다. 그리고 이렇게 선언된 멤버 변수는 외부와 완전히 격리되면서 외부에서는 액세스할 수 없고, 어떤 멤버 변수를 가지고 있는지 조차 알 수 없다. (이렇게 선언된 멤버 변수는 인스턴스 변수라 하고 흔히 ivar 라 지칭한다.) 따라서 이 변수에 값을 세팅하거나, 변수 값을 알아낼 수 있는 두 개의 메소드가 필요하다.

@interface Foo: NSObject
{
  NSString* _bar;
}
- (NSString*)bar;
- (void)setBar:(NSString*)newValue;
@end

Foo의 외부에서 해당 프로퍼티를 bar라는 이름으로 액세스하고, bar를 세팅하는 메소드를 -setBar:라고 이름붙였다. 멤버변수의 이름을 사실상 무엇이 되더라도 무관한데, 관습적으로는 getter의 이름과 똑같이 하거나 그 앞에 언더스코어를 붙인다. (언더스코어를 붙이는 이름이 멤소드 이름과 혼동을 줄이기 때문에 조금 더 권장된다.)

만약 이 bar라는 프로퍼티가 copy 시멘틱을 따른다고 하면, 두 메소드의 구현은 다음과 같이 작성될 것이다.

@implementation Foo
/// 초기화 시에 ivar를 초기화한다.
- (instancetype)init {
  self = [super init];
  _bar = nil;
}

- (NSString*)bar { return _bar; }
- (void)setBar:(NSString*)newValue]
{
  NSString* newBar = [[newValue copy] retain];
  [_bar release];
  _bar = newBar;
}
...
@end

즉 어떤 오브젝트가 그 내부에 어떤 값을 저장할 수 있고, 객체 외부에서 그 값을 액세스하려고 한다면 이 클래스는 다음의 세 가지 조건을 갖추어야 한다.

  1. 값을 저장할 수 있는 스토리지 변수
  2. 스토리지 변수를 액세스할 수 있는 getter 접근자
  3. 스토리지 변수를 업데이트할 수 있는 setter 접근자

만약 getter/setter 접근자가 모두 없는 경우라면, 해당 ivar는 클래스 내부에서만 참조할 수 있고, 외부에서는 액세스할 수 없는 값이 된다. 또 getter 메소드만 제공되는 경우라면, 객체 외부에서는 그 값을 getter 메소드를 통해서 읽을 수는 있지만 업데이트를 할 수 없는 읽기 전용의 값이 될 것이다. 이것이 Objective-C의 선언 프로퍼티의 핵심 내용이다.

따라서 어떤 클래스가 bar 라는 프로퍼티를 가지고 있다는 것은 그 프로퍼티가 -bar 혹은 -setBar: 라는 접근자 메소드를 가지고 있음을 의미한다. 그리고 그 객체에서 해당 프로퍼티를 액세스하는 것은 해당 접근자 메소드를 호출해야 하는 일이고, 따라서 객체로부터 어떤 값을 얻어와서 사용한다는 것은 “하드 코딩된 코드에서 미리 정해진 접근자 메소드를” 사용해야 한다는 것이다.

키밸류 코딩 – 문자열 기반 이름으로 동적인 프로퍼티 액세스

그런데, 임의의 객체 인스턴스 zoo 가 있다고 하자. 이 객체로부터 어떤 프로퍼티를 액세스해서 그 값을 얻으려고 한다. 그런데 어떤 프로퍼티를 가져올 것인지 혹은 갱신할 것인지가 컴파일 타임에 결정되지 않는다면 어떻게 해야 할까? 즉 “어떤 접근자 메소드를 호출할 것인지”를 코드를 작성하는 시점에 알 수 없는 것이다. “pee”라는 이름의 프로퍼티일 수도 있고, “tee”라는 이름의 프로퍼티 일수도 있는 것이다. 물론 객체 zoo가 이러한 접근자 메소드를 갖고 있는지 아닌지 여부조차 알 수 없을 수도 있다.

이처럼 컴파일 타임에 정의되지 않은 접근자 이름을 사용해서 런타임에 특정한 이름의 프로퍼티에 접근할 수 있는 기술이 키밸류 코딩이다. 키 밸류 코딩은 간단히 다음의 네 개의 메소드에 의존한다.

  • - (id)valueForKey:(NSString*)key / -(id)valueForKeyPath:(NSString*)keyPath
  • - (void)setValue:(id)obj forKey:(NSString*)key / - (void)setValue:(id)obj forKeyPath:(NSString*)keyPath

이 메소드들은 NSObject에 의해서 이미 구현되어 있다. 이 메소드들을 호출하여 성공적으로 특정한 프로퍼티에 액세스하기 위해서는 처음에 프로퍼티를 정의할 때, ivar와 접근자 메소드들의 이름이 중요하다.

  • valueForKey: 에서 키이름이 getter 메소드와 같거나
  • 키 이름과 동일한 ivar 혹은 앞에 언더스코어가 붙은 키 이름의 ivar가 있다.
  • setValue:forKey:는 키 이름을 첫글자를 대문자로 바꾸고 그 앞에 set-을 붙인 setter 메소드가 있다.

이러한 가정을 두고 있는 것이다. 만약 [zoo getValueForKey:@"bar"] 라고 했을 때,   zoo 가 Foo의 인스턴스라면, 이 메시지는 Objective-C 런타임 내부에서 [zoo bar] 로 번역될 것이다. 그리고 [zoo setValue:@"hello" forKey:@"bar"]라는 메시지를 받는다면 이는 다시 [zoo setBar:@"hello"];로 변경되어 호출될 것이다.1

키밸류 코딩을 따르는 방법

키밸류 코딩 호환 클래스를 작성하는 방법은 간단하다. 키밸류 코딩은 결국 키 이름을 기반으로 그에 매칭되는 접근자 메소드 및 인스턴스 변수를 런타임이 동적으로 찾아서 액세스해주는 기술이기 때문에 어떤식으로 프로퍼티 이름을 짓느냐는 것만, 관습을 따르면 되며, 그 관습이란 앞서 소개한 Foo의 bar와 같다.

  • 기본적으로 getter 이름이 프로퍼티 이름이며, 이것이 곧 키 이다.
  • setter 이름은 setKeyName: 과 같은 식으로 작성한다. getter이름의 첫글자를 대문자로 바꾸고 앞에 set을 붙인다.
  • ivar 이름은 getter이름과 똑같거나, 앞에 언더스코어를 붙인다.

그리고 이 관습은 @property 문법을 쓰면 자동으로 지켜진다.

@interface Foo: NSObject
@property (copy, nonatomic) NSString* bar;
@end

이상의 코드만으로 키밸류 코딩에서 요구하는 인스턴스변수, getter 메소드, setter 메소드를 모두 작성한 것과 다름없는 결과를 얻을 수 있다. 이것은 언어의 기능이라기보다는 컴파일러가 소스코드를 처리하기 직전에 자동으로 관련 코드를 만들어서 삽입해준다고 보면 된다. (이전에는 @synthesize bar; 같은 구문을 구현부에 써야했는데, LLVM 컴파일러는 이런 처리도 모두 자동으로 해주기 때문에 굳이 쓸 필요없다.)

키밸류 코딩은 왜 중요한가

그렇다면 키밸류 코딩은 왜 중요한가? 이것은 특정한 프로퍼티가 변경될 때, 자동으로 옵저버들에게 통지가 가는 키밸류 옵저빙을 비롯하여, 이 기술을 기반으로 하고 있는 코코아 바인딩등에서 기본 가정으로 “모든 참여 객체가 KVC/KVO 호환이다”라는 것을 가정하기 때문이다.

  • 키밸류 코딩 이름 규칙을 지원하면 valueForKey:, setValueForKey:는 따로 구현하지 않더라도 자동으로 지원된다.
  • 키밸류 코딩 규칙을 따르더라도 _bar = @"hello";와 같이 인스턴스 변수를 직접 변경해버리면 이는 KVO와 호환되지 않는다.
  • KVO에서는 반드시 [foo setValue:@"hello" forKey:@"bar"]를 쓰지 않아도 된다. [foo setBar:@hello]라고만 써도, 런타임에서 자동으로 통지를 보낼 수 있다. self.bar = @"hello"; 역시 setter 메소드 호출과 1:1로 치환되므로 KVO 호환이 된다. 이는 KVC 호환인 메소드는 필요한 경우 런타임에 의해 자동으로 다른 내부 메소드로 치환되기 때문에 적용가능하다. 물론 메소드 이름이 정해진 규칙을 벗어나면 이러한 기능은 지원되지 않는다.

기본적인 키밸류 코딩은 특정한 단일 값 프로퍼티의 변경을 런타임에서 동적으로 관리하는 수준에서 적용된다. 하지만 Foundation에서는 배열이나 Set과 같은 집합형식 자료 구조에 대해서도 KVC/KVO를 지원한다. 이는 단순히 이름 규칙만으로는 지원될 수 없으며, 별도의 메소드들을 추가로 작성해주어야 하는데 (대부분 NSMutableArray, NSMutableSet의 메소드들 간단힌 래핑하는 수준의 구현이다.) 이를 지원하도록 하는 것은 다음 기회에 추가로 소개하도록 하겠다.

참고자료

 

 

 

 

 


  1.   물론 키밸류 코딩은 이렇게 간단한 일차원적 변환 이상의 것이다. 실질적으로 @property 문법이 확립되어 적용되기 이전부터 존재해온 기술이기 때문에 탐색 패턴은 좀 더 많은 경우를 순차적으로 따르게 된다. 

[iOS/OSX] 앱 로컬라이징 하기

nib 파일 로컬라이징

nib 파일을 선택하고 인터페이습 빌더에서 언어를 추가하면 해당 언어로 표시할 nib 파일이 복사된다. 이에 따라 버튼 레이블이나 뷰에 올라가는 이미지와 텍스트를 변경하면 기기에서 설정한 언어에 따라 자동으로 해당 언어의 nib 파일이 선택되어 로컬라이징된 UI를 보여주게 된다.

문자열 테이블

예를 들어 경고창이나 검색 결과 문구에 대해서는 문자열 테이블을 사용하여 로컬라이징한다. 문자열테이블은 .strings 라는 확장자를 가지고 있다. 예를 들어 코코아 앱에서 찾기 패널을 만들었다고 한다면 Find.strings 파일을 각 언어별로 만들어 둘 수 있다. 문자열 테이블은 키-값 쌍의 모음이다. 각각의 키와 값은 겹따옴표로 둘러진다.

이를 코드에서 사용하려면 NSBundle 을 사용해야 한다.

NSBundle *main = [NSBundle mainBundle];
NSString *aString = [main localizedStringForKey:@"Key1" value:@"DefaultValue1" table:@"Find"];

이 코드는 Find.strings 파일에서 Key1 의 키를 찾는다. 만약 값이 발견되지 않았다면 디폴트로 DefaultValue1 이라는 문자열이 된다.

문자열 테이블 만드는 법

Xcode에서 빈 파일을 하나 만든다음, 이름을 Localizable.strings 라고 하고 English.lproj 디렉토리에 저장한다. 파일의 내용은 앞서 설명한 바와 같이 키와 값 쌍으로 입력하며, 각각의 쌍은 세미콜론으로 끝나야 한다.

"DELETE" = "Delete";
"SURE_DELETE" = "Do you really want to delete %d people?";
"CANCEL" = "Cancel";

이제 만들어진 문자열 테이블을 Xcode에서 선택하고, 인스펙터 패널에서 다른 언어 버전을 추가한다. 신규로 추가한 언어를 선택한 다음, 내용을 편집한다.

"DELETE" = "삭제";
"SURE_DELETE" = "%d명의 인원을 삭제하겠습니까?";
"CANCEL" = "취소";

알파벳이 아닌 문자가 있는 경우에는 유니코드로 변환할 것인지 묻는 경우가 있는데 변환을 해야 한다.

이렇게 저장된 문자열은 다음과 같이 사용한다. (문자열 테이블이 하나 밖에 없는 경우) 문자열 테이블이 더 있는 경우에는 테이블 이름도 명시해야 한다.

NSString *deleteString;
deleteString = [[NSBundle mainBundle] localizedStringForKey:@"DELETE" value:@"Delete?" table:nil];

이렇게 쓰는게 상당히 번거롭기 때문에 매크로로 처리할 수도 있다.

#define NSLocalizedString(key, comment) [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

이 때, comment 부분은 그냥 무시된다. 하지만 genstrings 툴을 사용할 때, 코멘트를 보고 활용할 수 있다.

ibtool 을 사용하여 nib 생성하기

ibtool 명령은 터미널을 통해 다국어 적용 문자열을 덤프할 수 있는 명령이다. 아래 예는 MyDocument.nib 파일로부터 Doc.strings 파일을 생성해 내는 명령이다.

$ cd SomeProject/English.lproj
$ ibtool --generate-stringsfile Doc.strings MyDocument.nib

이렇게 생성된 string 파일에서 만약 스페인어 버전을 만들겠다면, 스트링 파일의 값들을 수정해준다. 그런 다음,

$ mkdir ../Spanish.lproj
$ ibtool --strings-file Doc.strings --write ../Spanish.lproj/MyDocument.nib MyDocument.nib

즉, 현재 디렉터리의 MyDoument.nib 파일을 Doc.strings 파일을 참고하여 일부 레이블을 번역한 버전을 자동으로 생성하는 것이다.

문자열 포맷의 토큰 순서 변경하기

예를 들어 페이지 번호를 표시한다고 할 때,

NSString *theFormat = NSLocalizedString(@"PAGE", "%d of %d ");
x = [NSString stringWithFormat:theFormat, currentPage, totalPage];

와 같이 포맷팅할 수 있다. 하지만 언어별로는 순서가 달라질 수 있는데, 이는 다음과 같이 문자열 테이블 작성시에 토큰의 순서를 달러 표시와 함께 써서 변경할 수 있다.

"PAGE" = "%2$d 중 %1$d";

[iOS/OSX] Timer 사용하기

특정한 시간 이후에 작업을 시행하는 타이머를 사용하는 방법에는 몇 가지 방법이 있을 수 있는데, 코코아에서 타이머는 NSTimer 객체를 통해 구현된다.

타이머와 런루프

타이머는 런루프(Run Loop)와 밀접한 관련을 맺고 있다. 타이머는 스스로 동작기한을 가지고 있고, 그 상태로 런루프에 등록된다. 런루프는 그 기한이 지나는 시점에 타이머를 지켜보고 있다가, 타이머가 실행하기로 한 액션을 지정된 객체로 보내게 된다.

런루프는 (역시나 런루프와 스레드를 혼동하는 불편한 진실 때문에 이에 대한 별도의 포스팅도 필요할 듯) 사용자의 입력 등에 적절히 반응하기 위해서 계속해서 “기다리는” 일종의 루프이다. 예를 들면 키보드는 풀링이라는 방식으로 일종의 런루프를 가지고 있다. 즉 사용자가 키보드를 누르지 않는 상황에서 키보드는 ‘최선을, 총력을 다해서’ 사용자 입력을 반복해서 기다린다. 런루프는 이와 유사하게 스레드 내에서 사용자의 UI 입력을 최선을 다해 기다린다.

타이머가 지정한 액션은 런루프에 의해서 확인된다. 타이머를 사용할 때 가장 유의해야 하는 점은 NSTimer는 실시간 타이머가 아니라는 점이다. 만약 반복되는 주기를 가진 타이머의 특정 작업이 있다고 할 때, 런루프는 단지 그 타이머만을 감시하는 것이 아니라 아주 많은 종류의 입력을 처리해야 하기 때문에 이를 놓치는 경우가 있을 수 있다. 따라서 NSTimer를 통해 이뤄지는 지연된 작업이나, 반복작업은 실제 시간과 다소 차이가 있을 수 있으며, 경우에 따라서는 “상당히 큰” 차이가 발생할 수 있다.

타이머 사용하기

스케줄된 타이머 사용하기

타이머를 사용하는 가장 일반적인 방법은 NSTImer의 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 를 사용하는 방법이다. 이는 현재 메인 런루프에 타이머를 추가한다.

-(void)startOneOffTimer {
    [NSTimer scheduledTimerWithTimeInterval:1.0
        target:self
        selector:@selector(timerFired:)
        userInfo:[self userInfo]
        repeat:NO];
}

 

-(void)startRepeatingTimer {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5
        target:self selector:@selector(timerFired:)
        userInfo:[self userInfo] repeat:YES];
    self.repeatedTimer = timer;
}

타이머의 반복이 없는 경우에는 타이머가 종료된 후 해당 액션을 호출해주고 난 후 타이머는 자체적으로 해제된다. 그렇지 않은 경우에는 추후에 반복되는 타이머를 해제해주기 위해서 따로 참조점을 갖고 있어야 한다.

스케줄링 된 타이머는 생성 즉시 런루프에 추가되어 카운트다운을 시작하게 된다. 이와 별도로 특정 시점부터 동작을 시작하는 타이머는 다음과 같이 만들 수 있다.

-(void)startFireDateTimer {
    NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:3600];
    NSTimer *timer = [[NSTimer alloc]
        initWithFireDate:fireDate
        interval:0.5
        target:self
        selector:@selector(timerFiredMethod:)
        userInfo:[self uesrInfo]
        repeat:YES];
    NSRunLoop *theRunLoop = [NSRunLoop currentRunLoop];
    [theRunLoop addTimer:timer forMode:NSDefaultRunLoopMode];
}

시작된 타이머를 멈추기

위와 같이 만들어지고 시작된 타이머는 반복되지 않는다면 종료시 호출해야 하는 액션을 호출하고, 정지된다. 하지만 아직 정해진 시간을 다 채우지 않았거나, 반복해서 실행되는 타이머는 멈출 필요도 있다. 타이머를 멈추기 위해서는 해당 타이머에 invalidate 메시지를 보내면 된다.

-(void)stopRepatedTImer
{
    [self.repeatedTimer invalidate];
    self.repeatedTimer = nil;
}

타이머를 사용하지 않은 예약된 작업 호출

타이머를 사용하지 않고도 특정한 시간 지연 후에 간단히 어떤 기능을 실행할 수 있다. 이 기능은 NSObject에 이미 정의된 메소드이다. (역시 코코아의 루트 클래스가 NSObject라는 사실은 매우 다행스럽다!) 이는 -performSelector: withObject : afterDelay: 이다. 이는 다음과 같이 사용할 수 있다.

    
[self.tableView performSelector:@selector(reloadData)
    withObject:nil
    afterDelay:10.0];

이미 예약된 작업 취소하기

NSObject의 클래스 메소드인 +cancelPreviousPerformRequestsWithTarget:selector:object:+cancelPreviousPerformRequestsWithTarget: 을 사용한다. 이는 지연 후에 일을 하게되는 객체가 이 메시지를 받고 예약된 작업을 실행하지 않게 된다.

[스터디] 후위식을 사용하지 않는 공학용 계산기 로직

공학용 계산기 엔진 만들기

예전에 미진하게 만들다가 말았던 계산기 만들기와 관련해서. 지난 번 글은 사실 스탠포드의 iOS 앱 개발 강의 내용을 바탕으로 만들었다. 물론 일부 내용을 좀 수정하기는 했는데. 그 강의에서는 스택에 숫자와 연산자를 넣는 형태로 동작했다. 예를 들면 3 + 5 를 계산하기 위해서는 3, enter, 5, enter, plus 의 순서로 값을 먼저 입력하고 연산자를 뒤에 입력하는 형태의 약간 이상한 방식으로 동작했다. 이는 사실 그 계산기 프로젝트를 계속 발전시켜 공학용 계산기로 만들기 위해서이다.

보통의 공학용 계산기는 ‘후위식’을 이용한 계산 방법을 사용한다. 우리가 흔히 사용하는 1 + 2와 같은 수식의 형태를 연산자가 가운데에 들어간다고 해서 중위식이라고 하는데 이를 12+ 라 쓰고 뒤에서부터 계산하는 방식을 후위식이라 한다. 중위식을 후위식으로 변환할 때 연산의 우선 순위를 고려하게 되므로, 곱하기/나누기가 더하기/빼기와 섞여있는 식이나 괄호가 들어있는 식도 후위식으로 전환하고 나면 뒤쪽에서부터 차근차근 계산해서 한 번에 계산할 수 있는 장점이 있다. 물론 후위식 자체는 사람이 계산하기에는 무척 불편한 형태의 수식 표현이지만 컴퓨터는 스택에서 연산자와 피연산자를 꺼내서 쉽게 처리할 수 있다.

문제는… 이 후위식으로 변환하는 과정이 꽤나 골치아프다는 게 문제. (연산 우선순위를 정의한 기준과 별도의 스택이 추가로 필요) 그래서 생각한게 사람이 계산하는 방식과 마찬가지로 (단순 무식하게) 각 항별로 버퍼에서 우선 계산하고, 다음 항을 계산해야 할 때 버퍼에 있는 값을 결과값에 더해 버리면 되지 않을까? 하는 것이었다.

즉 이를 정리하면 다음과 같다.

  1. 버퍼의 값은 1로 초기화한다. 버퍼에는 숫자값과 현재 항이 음수인지, 양수인지를 구분하는 플래그, 그리고 새로 버퍼에 넣는 값을 곱할 것인지, 나눌 것인지를 결정할 플래그. 이렇게 3개의 값이 필요하다.
  2. 곱하기 연산자가 수식에서 나오면 다음에 나오는 숫자는 버퍼에 들어가 버퍼값과 곱해진다. 버퍼의 계산방식을 곱하기로 변경한다.
  3. 나누기 연산자가 수식에서 나오면 다음에 나오는 숫자는 버퍼에 들어가면서 기존 버퍼값을 나눈다. 버퍼의 계산 방식을 나누기로 변경한다.
  4. 더하기 연산자가 수식에서 나오면 이는 하나의 항에 대한 처리가 끝나고 다음 항이 시작되기 때문에 버퍼에 계산된 값을 꺼내 결과 값에 더해준다. 다음 버퍼는 “더해지는” 항이므로 버퍼의 부호를 양수로 설정해놓고, 값을 초기화한다.
  5. 빼기 연산자가 수식에서 나오면 이 역시 하나의 항에 대한 처리가 끝났다. 더하기와 같이 처리하고 다음 항의 부호는 음수이므로 버퍼의 부호를 음수로 설정한다.
  6. 숫자값이 나올 때는 이 값을 버퍼에 집어 넣는다. 버퍼의 곱하기/나누기 플래그에 따라 이값이 버퍼에 들어갈 때 기존의 버퍼의 값에 곱하거나 나눈다.

이 규칙에 따라 곱하기나 나누기로 연결된 식은 하나의 항으로 취급되어 버퍼에서 계산되고, 더하기나 빼기가 나타날 때 결과 값에 더해진다. 이 과정을 수식의 전체에 대해 진행하면 자동으로 식이 계산된다.

이 방식을 사용하면 괄호처리 역시 어렵지 않게 할 수 있다. 여는 괄호가 나타나면 그 괄호를 닫을 때까지 (따라서 열린 괄호의 수를 카운트하며 추적해야 한다) 수식에 들어있는 식을 별도의 수식으로 만든다. 그런 다음 이 수식을 수식 처리 함수 (즉 그 함수 자신)에게 넘겨 재귀적으로 처리하고 그 결과값을 버퍼에 집어넣으면 된다.

기타 파이, 로그, 제곱, 제곱근 등의 처리도 버퍼에서 별도의 필터를 통해 기능을 추가해 나갈 수 있을 것이다.

계산기엔진 소스코드 : http://www.box.com/s/2neu29qfmblaeoiaj5a8