Numbers and Value Programming Topics
このトピックスでは、Cデータ型をラップする(包み込む)オブジェクトについて説明します。これらは、NSValue、そのサブクラスの NSNumber、そして NSDecimalNumberで実装されています。ヌル値を表現するNSNull インスタンスについても扱います。
この文章の構成
- 「値をあつかう」では、一般的な値型について説明します。
- 「数値をあつかう」では、スカラー値タイプについて説明します。
- 「10進数をあつかう」では、10を基数としたオブジェクトについて説明します。
- 「NSNull をあつかう」では、NSNull インスタンスに付いて説明します。
値をあつかう
NSValue?オブジェクトは、1つの C もしくは Objective-C データを保持するシンプルなコンテナです。これは、int, float, char, ポインタ、構造体、そして id 型のオブジェクトといったどんなスカラタイプのデータでも保持できます。このクラスの役割は、先述したデータ型のアイテムをオブジェクトへ変換して、構成する要素がオブジェクトではならない NSArray? や NSSet といったコレクションクラスへ加えられるようにすることです。NSValue? オブジェクトは、常に不可変です。
NSValue? オブジェクトは、まず対象とするデータアイテムへのポインタ、加えてそのアイテムの型をあらわすObjective-C でのデータ型エンコーディング、この2つから生成します。このデータ型エンコーディングは特定の C 文字列で、プラットフォーム依存であるために、@encode() 指定子を使って得ます( @encode() と データ型のリストについて、より詳細な情報は、"Type Encordings?" を参照してください)。一例として、NSRange? を持つデータを持つNSValue インスタンスの theValue を生成するだけのコードは
NSRange myRange = {4, 10}; NSValue *theValue = [NSValue valueWithBytes:&myRange objCType:@encode(NSRange)];
次の例では、一般的な C 構造体を符号化して、その後で復号しています。
typedef struct { float real; float imaginary; } ImaginaryNumber;
ImaginaryNumber miNumber; miNumber.real = 1.1; miNumber.imaginary = 1.41;
NSLog(@"real => %f, imaginary => %f", miNumber.real, miNumber.imaginary);
NSValue *miValue = [NSValue valueWithBytes:&miNumber objCType:@encode(ImaginaryNumber)];
NSLog(@"%@", miValue);
ImaginaryNumber miNumber2; [miValue getValue:&miNumber2];
NSLog(@"real => %f, imaginary => %f", miNumber2.real, miNumber2.imaginary);
対象としているデータアイテムは、不変長であるのが必要です。C 文字列といった可変長の配列や構造体、その他の不定長のデータタイプを、NSValue へ格納できません。それらのタイプへは、NSString や NSData オブジェクトを使うべきです。可変長のアイテムを指すポインタは、NSValue へ保存できます。次のコードは、C 文字列を直接 NSValue オブジェクトへ納めようとする、間違った試みです。
/* INCORRECT! */ char *myCString = "This is a string."; NSValue *theValue = [NSValue valueWithBytes:myCString withObjCType:@encode(char *)];
このコードでは myCString の中味を char へのポインタとして解釈されるので、文字列に含まれる最初の4バイトはポインターとして扱われます(ここで使われるバイト数は、ハードウェアアーキテクチャーによって異なります)。つまりこれは、バイト列 "This" はポインタ値として解釈されて、まったく意味がありません。そのようなデータアイテムを正しく NSValue へエンコードするには、NSString オブジェクト(もし文字列をオブジェクトに納めたい場合)を使うか、ポインタへのアドレスを渡します。ポインタ自体を渡してはいけません。
/* Correct. */ char *myCString = "This is a string."; NSValue *theValue = [NSValue valueWithBytes:&&myCString withObjCType:@encode(char **)];
ここでは、myCString のアドレス ( &&myCString ) を渡しているので、文字列の謝意署の文字へのアドレスが theValue に保存されます。
"重要:" NSValue オブジェクトは文字列の中味をコピーするのではなくて、 ポインタ自体をコピーします。 アロケートされたデータアイテムから NSValue を生成したら、 その NSValue オブジェクトが存在している間は アイテムのメモリーを解放してはいけません。
数値をあつかう [#a4650409]
NSNumber は NSValue のサブクラスで、すべての C スカラー値(数値)をあらわす値を提供します。
このクラスは、数値オブジェクトの生成、符号付き無しの char , short int, int, NSInteger, long int, long long init, float, double, BOOLの値へアクセス、といった一連のメソッドを持っています。
NSInteger nine = 9; float ten = 10.0;
NSNumber *nineFromInteger = [NSNumber alloc] initWithInteger:nine]; NSNumber *tenFromFloat = [NSNumber numberWithFloat:ten];
@ を使ったリテラルから直接に、数値オブジェクトを生成できます。
NSNumber *nineFromInteger = @9; NSNumber *tenFromFloat = @10.0; NSNumber *nineteenFromExpression = @(nine + ten);
NSNumber には、ふたつの NSNumber オブジェクトの順序を決定する compare: メソッドがあります。
NSComparisonResult comparison = [nineFromInteger compare:tenFromFloat]; // comparison = NSOrderedAscending
float aFloat = [nineFromInteger floatValue]; // aFloat = 9.0 BOOL ok = [tenFromFloat boolValue]; // ok = YES
NSNumber オブジェクトはそれが作られた数値型を記録していて、異なった数値型の NSNumber オブジェクトと比較するときやCの数値として値を返すとき C ルールに則った型変換を使います。型変換については、C でのリファレンスを参照してください。(一方で、ObjCType については、レシーバーを生成したメソッドの型と返値の型とは、必ずしも一致する必要がありません。)
保持できないほど大きな値をもつ数値型の NSNumber オブジェクトを生成したら、間違えた結果が帰ってきます。たとえば、FLT_MAX よりも大きい double 型の数値 をつかって float 型の数値を生成するとか、NSInteger よりも大きい float 型の数値を使って integer を生成した場合とかです。
NSNumber *bigNumber = @(FLT_MAX); NSInteger badInteger = [bigNumber integerValue]; NSLog(@"bigNumber: %@; badInteger: %d", bigNumber, badInteger); // output: "bigNumber: 3.402823e+38; badInteger: 0"
10進数をあつかう [#e24962fb]
NSDecimalNumber?はNSNumberの不可変サブクラスで、10 進数の算術をするためのオブジェクト指向ラッパーを提供します。インスタンスは、いかなる数字も 仮数 x 10の指数 として表現ができます。ここで、仮数は最大38桁の10進数(整数)で、指数は-128から+128の整数です。
算術の過程で、例えばゼロでの除算をするなどで、メソッドは計算エラーを吐くかもしれません。または、数字を切り捨てるなどから計算違いが発生するかもしれません。メソッドがそのように動作するのは、「振る舞い」と呼ばれています。
振る舞いは、NSDecimalNumberBehaviours?プロトコルにあるメソッドで定義されています。振る舞いと呼ばれるすべてのNSDecimalNumberの引数は、このプロトコルに準拠したオブジェクトである必要があります。振る舞いについてより詳細には、NSDecimalNumberBehaviors?の仕様を参照してください。また、defaultBehaviour?メソッドについての記述も参照してください。
10進数に対するCのインターフェイス
C関数群を通じて、NSDecimalNumberが用意する算術と丸め込みのメソッドを使うことができます。
NSDecimalAdd | Adds two decimal values. |
NSDecimalCompact | Compacts the decimal structure for efficiency. |
NSDecimalCompare | Compares two decimal values. |
NSDecimalCopy | Copies the value of a decimal number. |
NSDecimalDivide | Divides one decimal value by another. |
NSDecimalIsNotANumber | Returns a Boolean that indicates whether a given decimal contains a valid number. |
NSDecimalMultiply | Multiplies two decimal numbers together. |
NSDecimalMultiplyByPowerOf10 | Multiplies a decimal by the specified power of 10. |
NSDecimalNormalize | Normalizes the internal format of two decimal numbers to simplify later operations. |
NSDecimalPower | Raises the decimal value to the specified power. |
NSDecimalRound | Rounds off the decimal value. |
NSDecimalString | Returns a string representation of the decimal value. |
NSDecimalSubtract | Subtracts one decimal value from another. |
10進数をオブジェクトとしてあつかう必要がない場合、Cの数値として扱った方が良いかもしれません。それはつまり、10進数をNSArrayやNSDIctionaryのようなオブジェクト指向のコレクションへ保存する必要がない場合です。また、最高の効率を求めるときにも同様に、Cの数値として扱った方がよいかもしれません。Cの数値は、NSDecimalNumberクラスより、より早く、より少ないメモリで動作します。
可変性が必要なら、Cとオブジェクトの2つを組み合わせて使えます。C インターフェイスの関数を使い数値を取得し、その数値を DecimalNumberのインスタンスへと切り替えればいいでしょう。
NSNull をあつかう [#p431cd47]
NSNullクラスは、nilが値として存在するのが許されていないところで、ヌル値を表すためのシングルトンのオブジェクトとして定義されています。
NSNull *nullValue = [NSNull null]; NSArray *arrayWithNull = @[nullValue]; NSLog(@"arrayWithNull: %@", arrayWithNull); // Output: "arrayWithNull: (<null>)"
NSNullインスタンスは、本質的にNOまたはfalseとは違います。それら2つは両方とも、論理的な値を具現しています。NSNullインスタンスは、値が無いという意味です。NSNullはnilと同義ですが、nilとは等価ではないのが重要なポイントです。ヌルオブジェクトをテストするために、直接オブジェクトを比較してみなくてはなりません。
id aValue = [arrayWithNull objectAtIndex:0]; if (aValue == nil) { NSLog(@"equals nil"); } else if (aValue == [NSNull null]) { NSLog(@"equals NSNull instance"); if ([aValue isEqual:nil]) { NSLog(@"isEqual:nil"); } } // Output: "equals NSNull instance"