GNUstep/Objective-C で KVO(キー・バリュー・オブザービング)


もろもろの勉強を兼ねて、一番初めのObjective-Cプログラム (1/3):Cocoaの素、Objective-Cを知ろう(2) - @IT を改変して、KVO で歌詞変更を監視して自動出力する版への書き換えにチャレンジしてみました。

a Singer は a Song を与えられると、自身をオブザーバーとして a Song に登録します。そして、どこかで歌詞が変更されると、その旨の通知を受け、自発的に sing するというカラクリになっている…はずです。^^;

Singer.m
#import <Foundation/Foundation.h>
#import <Foundation/NSKeyValueObserving.h>
#import <AppKit/AppKit.h>
#import <stdio.h>

@interface Song : NSObject
{
  NSString *lyrics;
}
- (NSString *)lyrics;
- (void)setLyrics:(NSString *)argLyrics;
@end

@implementation Song

- (NSString *)lyrics
{
  return lyrics;
}

- (void)setLyrics:(NSString *)argLyrics
{
  lyrics = argLyrics;
}

@end

@interface Singer : NSObject
{
  Song *song;
}
- (void)setSong:(Song *)argSong;
- (void)sing;
@end

@implementation Singer

- (void)setSong:(Song *)argSong
{
  song = argSong;
  [song addObserver:self
    forKeyPath:@"lyrics"
    options:NSKeyValueObservingOptionNew
    context:NULL];
}

- (void)sing
{
  printf("%s\n", [[song lyrics] UTF8String]);
}

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath isEqual:@"lyrics"])
    {
      [self sing];
    }
}


@end

int main(void)
{

  id song;
  id singer;
  NSAutoreleasePool *pool;
  pool = [NSAutoreleasePool new];

  song = [[Song alloc] init];
  [song setLyrics:@"La La La ..."];

  singer = [[Singer alloc] init];
  [singer setSong:song];
  [singer sing];
  [song setLyrics:@"Lu Lu Lu ..."];

  [pool release];
  return 0;
}
$ ls
GNUmakefile  SingSong.m


$ cat GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make

TOOL_NAME = SingSong
SingSong_OBJC_FILES = SingSong.m

include $(GNUSTEP_MAKEFILES)/tool.make


$ make
Making all for tool SingSong...
 Compiling file SingSong.m ...
 Linking tool SingSong ...


$ obj/SingSong.exe
La La La ...
Lu Lu Lu ...


Squeak Smalltalk で似たようなことを書くとすると次のような感じでしょうか。簡単のため、a Song 向けには特にクラスは作らず、a ValueHolder の contents を lyrics として見立て、それを用いています。

Singerクラスの定義
Object subclass: #Singer
    instanceVariableNames: 'song'

Singer >> setSong: newSong
    song := newSong.
    song addDependent: self

Singer >> sing
    World findATranscript: nil.
    Transcript cr; show: song contents

Singer >> update: aSymbol
    aSymbol == #contents ifTrue: [self sing]
実行スクリプト
| singer song |
song := ValueHolder new contents: 'La La La ...'.
singer := Singer new setSong: song.

singer sing.
song contents: 'Lu Lu Lu ...'
ワークスペースの出力
La La La ...
Lu Lu Lu ...

GNUstep には、Smalltalk 処理系が組み込まれた、こんな派生システムもあるんですね。

Since around June 2008, Étoilé has included a Smalltalk Just-In-Time (JIT) compiler.

Étoilé