ランダマイザを作る【実践的Macintoshプログラミング解説】

印刷用表示 |テキストサイズ 小 |中 |大 |

CoreData版 Repeating Motif Generator の開発 Repeating Motif Wonderland CoreData 実践的 Macintosh プログラミング解説

LinkIconホーム

更新日 2009-05-24

原図のパラメータをランダムに決める

ランダマイザを作る

 原図のパラメータをランダムに決定する機能を持つオブジェクト(ランダマイザ)はアウトレットを必要とせず、アプリケーションに一つ存在すれば充分なので、シングルトンデザインパターンを使用します。

 後の拡張を見越して、原図のランダマイザ+スーパークラスという構成で作っておく事にします。

ランダマイザのスーパークラス

 今回作成するのは原図のランダマイザですが、いずれ別のランダマイザが必要になってくる事は明らかなので、まずランダマイザとしての共通機能を持つスーパークラスを作成します。スーパークラスの名前はシンプルにRandomizerとします。

インターフェイスファイルの実装

 Randomizerのインターフェイスファイルは以下の様になります。サブクラスが使うユーティリティメソッドが一つ定義してあるだけです。

//
//  Randomizer.h
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008 . All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface Randomizer : NSObject
{
}

- (NSNumber *)randomIntBetweenA:(int)a andB:(int)b;

@end

スーパークラスの機能

 Randomizerには以下の機能を持たせます。

  1. 乱数の種をセットする。
  2. ユーザデフォルトとインスタンス変数のバインディングを確立する。インスタンス変数は乱数で決める数値の範囲を表すものとします。環境設定でユーザデフォルトに保存した値をインスタンス変数に反映させ、その値を計算で使います。
  3. シングルトンデザインパターンで必要なメソッドの一部を実装する。
  4. サブクラスで使うユーティリティメソッドを実装する。

バインディングを確立する

 1と2を初期化メソッドで実行します。ここではbindNamesメソッドでnilを返していますが、サブクラスはbindNamesメソッドをオーバーライドして、インスタンス変数の名前が入った配列を返す必要があります。

 ユーザデフォルトに値を保存する際に使った名前とインスタンス変数の名前を一致させる様にしておきます。こうする事でbindWithNameメソッドが使えます。

//
//  Randomizer.m
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008 . All rights reserved.
//

#import "Randomizer.h"


@implementation Randomizer

- (NSArray *)bindNames  {return nil;}

- (void)bindWithName:(NSString *)name
{
    [self bind:name
      toObject:[NSUserDefaultsController sharedUserDefaultsController]
   withKeyPath:[NSString stringWithFormat:@"values.%@",name]
       options:nil];
}

#pragma mark -
#pragma mark Initializer and Deallocator

- (id)init
{
    self = [super init];
    if(self)
    {
        NSEnumerator    *enumerator;
        NSString        *bindName;
        
        srandom(time(NULL));
        enumerator = [[self bindNames] objectEnumerator];
        while(bindName = [enumerator nextObject])
            [self bindWithName:bindName];
    }
    return self;
}

シングルトンデザインパターン

 ランダマイザはアプリケーションに一つ存在すればよい事、アウトレットが必要ない事からシングルトンデザインパターンを適用する事にします。アウトレットが必要な場合は、nibファイルにインスタンス化するという手法を使う事になります。
 シングルトンデザインパターンについては以下のリンクが参考になるでしょう。

 上記の資料を参考にしてスーパークラスに実装できるメソッドを抜き出すと以下の様になります。

 解放して欲しくないオブジェクトを定義するために、retain, release, autoreleaseが何もしない様にオーバーライドしています。コピーを封じるためにcopyWithZone:も何もしない様にオーバーライドします。そしてretainCountが常にunsigned intの最大の数UINT_MAXを返す様にします。

//
//  Randomizer.m
//

#pragma mark -
#pragma mark Singleton Design Pattern

- (id)copyWithZone:(NSZone*)zone    {return self;}
- (id)retain                        {return self;}
- (unsigned)retainCount             {return UINT_MAX;}
- (void)release                     {}
- (id)autorelease                   {return self;}

ユーティリティメソッドを実装する

 ランダマイザとしてサブクラスが共通で必要とするユーティリティメソッドを実装します。将来はもう少し拡充するとして、とりあえずは引数で指定された二つの数の間の整数を返すメソッドrandomIntBetweenA:andB:だけを実装しておきます。

//
//  Randomizer.m
//

#pragma mark -
#pragma mark Random Object

- (NSNumber *)randomIntBetweenA:(int)a andB:(int)b
{
    int range = b - a < 0 ? b - a - 1 : b - a + 1;
    int value = (int)(range*((float)random()/(float)LONG_MAX));
    return  [NSNumber numberWithInt:(value == range ? a : a + value)];
}

原図のランダマイザ

 Randomizerのサブクラスとして、原図のパラメータをランダムに決める事をサポートする機能を持ったクラスMasterMotifRandomizerを作成します。

インターフェイスファイルの実装

 インターフェイスファイルを以下に示します。インスタンス変数として、原図のパラメータを変化させる範囲を表す24個の変数を定義します。この名前はユーザデフォルトで使っている名前と同じにしてあります。こうする事でスーパークラスのbindWithNameメソッドを使う事ができます。

 またこれらのインスタンス変数に対応するアクセサメソッドは実装していません。これはvalueForKey:メソッドやsetValue:forKey:メソッドが、アクセサメソッドがない場合にインスタンス変数を探し出して代わりにそれを使ってくれるという機能を持っているので、それを活用しているためです。

 残りは外部から呼び出す必要があるメソッドの定義です。

//
//  MasterMotifRandomizer.h
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008 . All rights reserved.
//

#import <Cocoa/Cocoa.h>


#import "Randomizer.h"

@interface MasterMotifRandomizer : Randomizer
{
    int     amplitudeMinX1,amplitudeMinX2,amplitudeMinX3;
    int     amplitudeMaxX1,amplitudeMaxX2,amplitudeMaxX3;
    int     amplitudeMinY1,amplitudeMinY2,amplitudeMinY3;
    int     amplitudeMaxY1,amplitudeMaxY2,amplitudeMaxY3;
    
    int     frequencyMinX1,frequencyMinX2,frequencyMinX3;
    int     frequencyMaxX1,frequencyMaxX2,frequencyMaxX3;
    int     frequencyMinY1,frequencyMinY2,frequencyMinY3;
    int     frequencyMaxY1,frequencyMaxY2,frequencyMaxY3;
}

+ (id)sharedRandomizer;

- (NSNumber *)randomAmplitude:(NSNumber *)order;
- (NSNumber *)randomFrequency:(NSNumber *)order;
- (NSNumber *)randomPhaseLag;

@end

バインディングが必要な変数名を返す

 サブクラスでbindNamesを実装し、バインディングが必要なインスタンス変数の名前が入った配列を返すと、スーパークラスの初期化メソッドでバインディングを設定してくれます。ただしbindWithNameメソッドはユーザデフォルトで保存の際に使用した名前とインスタンス変数の名前が一致している事を前提にしています。

 bindNamesは以下の様に実装します。

//
//  MasterMotifRandomizer.m
//  RepeatingMotifGenerator
//
//  Copyright NovemberKou 2008 . All rights reserved.
//

#import "MasterMotifRandomizer.h"


@implementation MasterMotifRandomizer

- (NSArray *)bindNames
{
    return  [NSArray arrayWithObjects:
             @"amplitudeMinX1", @"amplitudeMinX2", @"amplitudeMinX3",
             @"amplitudeMaxX1", @"amplitudeMaxX2", @"amplitudeMaxX3",
             @"amplitudeMinY1", @"amplitudeMinY2", @"amplitudeMinY3",
             @"amplitudeMaxY1", @"amplitudeMaxY2", @"amplitudeMaxY3",
             @"frequencyMinX1", @"frequencyMinX2", @"frequencyMinX3",
             @"frequencyMaxX1", @"frequencyMaxX2", @"frequencyMaxX3",
             @"frequencyMinY1", @"frequencyMinY2", @"frequencyMinY3",
             @"frequencyMaxY1", @"frequencyMaxY2", @"frequencyMaxY3",
             nil];
}

シングルトンデザインパターン

 シングルトンデザインパターンに必要なメソッドのうち、static変数を使う関係上スーパークラスで実装できなかったメソッドを実装します。

//
//  MasterMotifRandomizer.m
//

#pragma mark -
#pragma mark Singleton Design Pattern

static id randomizer = nil;

+ (id)sharedRandomizer
{
    @synchronized(self)
    {
        if(randomizer == nil)
            [[self alloc] init];
    }
    return randomizer;
}

+ (id)allocWithZone:(NSZone*)zone
{
    @synchronized(self)
    {
        if(randomizer == nil)
        {
            randomizer = [super allocWithZone:zone];
            return randomizer;
        }
    }
    return nil;
}

パラメータをランダムに決める

 amplitude, frequency, phaseLagをランダムに決めるメソッドを実装します。phaseLagは変動範囲が固定ですが、amplitudeとfrequencyは変動範囲をインスタンス変数によって制約します。その変動範囲は発振器によって異なるので発振器を特定する必要がありますが、それを引数のorderで行なっています。

 引数のorderは発振器のorderである事を想定しています。発振器のorderは以下の様に設定しています。

order
役割
0
X軸の第一の発振器
1
X軸の第二の発振器
2
X軸の第三の発振器
3
Y軸の第一の発振器
4
Y軸の第二の発振器
5
Y軸の第三の発振器

//
//  MasterMotifRandomizer.n
//

#pragma mark -
#pragma mark Random Property

- (NSNumber *)randomAmplitude:(NSNumber *)order
{
    switch([order intValue])
    {
        case 0:     return  [self randomIntBetweenA:amplitudeMinX1 andB:amplitudeMaxX1];
        case 1:     return  [self randomIntBetweenA:amplitudeMinX2 andB:amplitudeMaxX2];
        case 2:     return  [self randomIntBetweenA:amplitudeMinX3 andB:amplitudeMaxX3];
        case 3:     return  [self randomIntBetweenA:amplitudeMinY1 andB:amplitudeMaxY1];
        case 4:     return  [self randomIntBetweenA:amplitudeMinY2 andB:amplitudeMaxY2];
        case 5:     return  [self randomIntBetweenA:amplitudeMinY3 andB:amplitudeMaxY3];
    }
    return  nil;
}

- (NSNumber *)randomFrequency:(NSNumber *)order
{
    switch([order intValue])
    {
        case 0:     return  [self randomIntBetweenA:frequencyMinX1 andB:frequencyMaxX1];
        case 1:     return  [self randomIntBetweenA:frequencyMinX2 andB:frequencyMaxX2];
        case 2:     return  [self randomIntBetweenA:frequencyMinX3 andB:frequencyMaxX3];
        case 3:     return  [self randomIntBetweenA:frequencyMinY1 andB:frequencyMaxY1];
        case 4:     return  [self randomIntBetweenA:frequencyMinY2 andB:frequencyMaxY2];
        case 5:     return  [self randomIntBetweenA:frequencyMinY3 andB:frequencyMaxY3];
    }
    return  nil;
}

- (NSNumber *)randomPhaseLag    {return  [self randomIntBetweenA:0 andB:359];}