Start Developing iOS Apps Today(sup.3)〜基礎プログラミングスキルの習得

2012. 12. 11
Foundationフレームワークは、その名前が示唆するように、iOSとOS X双方における全てのプログラミングの基礎となるツールキットです。
これらのプラットフォームで成功した開発者になるためには、このツールキットに精通する必要があります。

Foundationは様々な目的のために多数のクラスやプロトコルを定義していますが、クラスやプロトコルには特に基礎として傑出している3つのカテゴリがあります。

  • ルートクラスと関連プロトコル
    ルートクラスNSObjectは同名のプロトコルと共に、全てのObjective-Cオブジェクトの基本的なインターフェイスと動作を指定します。
    また、クラスのインスタンスをコピーし、その状態をエンコードできるクライアントにするために、クラスが採用することができるプロトコルもあります。

  • 値クラス
    値クラスは文字列、数値、日付、またはバイナリデータなどのプリミティブなデータ型のためのオブジェクト指向のラッパーで、(値オブジェクトと呼ばれる)インスタンスを生成します。
    同じ値クラスのインスタンスと同じカプセル化された値は等しいとみなされます。

  • コレクションクラス
    (コレクションと呼ばれる)コレクションクラスのインスタンスは、オブジェクトのグループを管理します。
    コレクションの特定の型を識別することは、格納されているオブジェクトにどのようにアクセスするかということです。
    多くの場合、コレクション内の項目は値オブジェクトです。

コレクションと値オブジェクトは、メソッドの引数や戻り値として頻繁に取得するため、Objective-Cプログラミングでは非常に重要です。



●ルートクラスとObjective-Cオブジェクト

クラス階層では、ルートクラスは他のクラスから継承されておらず、階層内の他の全てのクラスは最終的にルートクラスから継承されています。
NSObjectはObjective-Cクラス階層のルートクラスです。
他のクラスはNSObjectからObjective-Cのランタイムシステムへの基本的なインターフェイスを継承します。
これらのクラスのインスタンスは、Objective-Cオブジェクトとしての基本的な性質をNSObjectから導出しています。

ただしNSObject自体のインスタンスは単純なオブジェクトであり、それ以外の何か有用なことができるわけではありません。
貴方のプログラムに固有のロジックやプロパティを追加するには、NSObject、またはNSObjectから直接あるいは間接的に継承された1つ以上のクラスを作成する必要があります。

NSObjectNSObjectプロトコルを採用しており、全てのオブジェクトのインターフェイスに共通する追加メソッドを宣言しています。
更に(NSObjectのクラス定義を含むヘッダファイル)NSObject.hには、NSCopyingNSMutableCopying、そしてNSCodingプロトコルの宣言が含まれています。
クラスがこれらのプロトコルを採用した場合、オブジェクト・コピーやオブジェクト・エンコーディングの機能を備える、オブジェクトの基本的な動作を強化します。
モデルクラス(そのインスタンスがアプリケーションデータをカプセル化し、データを管理するクラス)では、しばしばオブジェクト・コピーやオブジェクト・エンコーディングプロトコルを採用します。

NSObjectクラスと関連するプロトコルは、オブジェクトの生成や、継承チェーンのナビゲート、特性や機能についての問い合わせ、オブジェクトの比較、そしてオブジェクトのコピーやエンコーディングをするためのメソッドを定義しています。
この記事の残りのほとんどは、これらのタスクの基本的な要件について説明しています。



●オブジェクトの観点で考える

実行時、アプリはオブジェクトが連携したネットワークであり、これらのオブジェクトがアプリの処理を行うため相互に通信します。
各オブジェクトは少なくとも1つは他のオブジェクトと接続し、少なくとも1つは応答する役割を担っています。
(孤立しているオブジェクトは、ほとんど価値がありません。)
下図に示すように、ネットワーク内のオブジェクトにはフレームワークオブジェクトとアプリケーションオブジェクトの両方が含まれます。
アプリケーションオブジェクトは、通常はフレームワークをスーパークラスとする、カスタムサブクラスのインスタンスです。
オブジェクトのネットワークは、一般的にオブジェクトグラフとして知られています。

app_as_object_network_2x.png

貴方は参照を介して、これらのオブジェクト間のリレーションシップや接続を確立します。
参照の多くの言語形式には、インスタンス変数、グローバル変数、そして(限られたスコープ内の)ローカル変数があります。
リレーションシップは、一対一または多対一にすることができ、所有権やや親子対応の概念を表現することができます。
これは、あるオブジェクトへのアクセス、通信、または他のオブジェクトの制御を意味します。
参照されるオブジェクトは、自然にメッセージのレシーバとなります。

アプリ内でのオブジェクト間のメッセージは、アプリの機能の一貫性にとって重要です。
オーケストラの音楽家と同様に、アプリ内の各オブジェクトには役割があり、アプリに寄与する動作の限定的な設定を持っています。
オブジェクトはタップに応答する楕円面の表示や、データを持つオブジェクトのコレクションの管理、あるいはアプリの活動の中での主要なイベントを調整することがあります。
しかし実際にそれらに寄与するためには、他のオブジェクトに対して通信できることが必要になります。
つまりアプリ内の他のオブジェクトにメッセージを送信したり、他のオブジェクトからのメッセージを受信できる必要があります。

強く結合したオブジェクト(直接参照を介して接続されたオブジェクト)のメッセージ送信は簡単なことです。
しかしアプリの弱い結合のオブジェクト(オブジェクトグラフ内で遠くに離れたオブジェクト)では、いくつかの他の通信手段が必要になります。
Cocoa TouchとCocoaフレームワークには、(下図に示すような)弱い結合のオブジェクト間の通信を可能にする技術とメカニズムが多く備わっています。
これらのメカニズムと技術は(後で詳しく学ぶ)全てのデザインパターンに基づいており、堅牢で拡張可能なアプリの効率的な構築を可能にします。

communication_loosely_coupled_2x.png



●オブジェクトの生成

オブジェクトは通常、割り当ててから初期化して生成します。
これらは2つの別個な行程ですが、密接にリンクしています。
また多くのクラスは、クラスのファクトリメソッドを呼び出すことによってオブジェクトを生成します。


割り当てと初期化によるオブジェクトの生成

オブジェクトを割り当てるには、オブジェクトのクラスにallocメッセージを送信し、そのクラスの『生の』(初期化されていない)インスタンスを取得します。
オブジェクトを割り当てる時、Objective-Cランタイムはアプリケーションの仮想メモリからオブジェクトに対して十分なメモリを割り当てます。
メモリの割り当てに加えて、ランタイムは全てのインスタンス変数をゼロに設定するなど、いくつかの他の事も行います。

生のインスタンスを割り当てた後、すぐに初期化する必要があります。
初期化は、オブジェクトの初期状態(インスタンス変数やプロパティ)を適切に設定し、オブジェクトを返します。
初期化の目的は、使用可能なオブジェクトを返す事です。

フレームワークでは、オブジェクトを初期化するイニシャライザと呼ばれるメソッドを多く見かけるでしょうが、それらは全て類似性を持っています。
イニシャライザはinitで始まるインスタンスメソッドで、id型のオブジェクトを返します。
ルートクラスNSObjectでは、他の全てのクラスが継承しているinitメソッドを宣言しています。
他のクラスは、それぞれ独自のキーワードや引数型を持つ独自のイニシャライザを宣言しています。
例えばNSURLクラスでは次のようなイニシャライザを宣言しています。

- (id)initFileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir

オブジェクトの割り当てと初期化を行うには、初期化呼び出しの内側に割り当てをネストします。
例として上記のイニシャライザを使用した場合、下記のようになります。

NSURL *aURL = [[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory() isDirectory:YES];

安全なプログラミングの慣習として、生成されたオブジェクトを照合するため、返されたオブジェクトをテストすることができます。
オブジェクトの生成を妨げるような何かが、どちらかの段階で発生した場合、イニシャライザはnilを返します。
ただし、Objective-Cは否定的な結果を除いて(例えば、例外を投じること無く)nilにメッセージを送信することがあり、メソッドが呼び出されなかったために貴方のコードが想定している処理を行わない場合があります。
allocメソッドによって返されたものの代わりに、イニシャライザによって返されたインスタンスを使用する必要があります。


クラスファクトリメソッドの呼び出しによるオブジェクトの生成

クラスファクトリメソッド(割り当て、初期化、自身のインスタンス返すことを目的とするクラスメソッド)を呼び出すことによって、オブジェクトを生成することもできます。
クラスファクトリメソッドは、2つではなく1つの行程でオブジェクトを生成することを許可しているため便利です。
それらの形式は次の通りです。

    + (type)className... (where className excludes any prefix)

Objective-Cフレームワークのいくつかのクラスは、クラスのイニシャライザに対応したクラスファクトリメソッドを定義しています。
例えば、NSStringでは以下の2つのメソッドを宣言しています。

- (id)initWithFormat:(NSString *)format, ...;
+ (id)stringWithFormat:(NSString *)format, ...;

このNSStringのクラスファクトリの使用法の例を示します。

NSString *myString = [NSString stringWithFormat:@"Customer: %@", self.record.customerName];



●メモリリークを回避するためのオブジェクトグラフの管理

Objective-Cプログラム内のオブジェクトは、オブジェクトグラフ(各オブジェクトと他のオブジェクトとのリレーションシップ(または参照)によって形成された、オブジェクトのネットワーク)を構成します。
オブジェクトの参照には一対一、または(コレクションオブジェクトを介しての)多対一があります。
オブジェクトの寿命の要因であるため、オブジェクトグラフは重要です。
コンパイラはオブジェクトグラフ内の参照の強さを検査し、必要に応じて保持や解放のメッセージを追加します。

:Objective-Cランタイムの最新バージョンでは、自動参照カウント(Automatic Reference Counting:ARC)を実装しています。
ARCでは明示的なメモリの管理(つまりオブジェクトの保持や解放)が不要です。
デフォルトの場合、新規アプリケーションプロジェクトのARCは常に使用する必要があります。

貴方はグローバル変数やインスタンス変数、ローカル変数などの基本的なCやObjective-Cの構造体を介して、オブジェクト間の参照を形成します。
これらの構造体はそれぞれ暗黙のスコープを保持しており、例えばローカル変数によって参照されるオブジェクトのスコープは、宣言された機能ブロックです。
重要なのはオブジェクト間の参照には、強い参照と弱い参照があるということです。
強い参照は所有権を示し、参照しているオブジェクトは参照されているオブジェクトの所有権を所有しています。
弱い参照は参照しているオブジェクトは参照されているオブジェクトを所有していないことを意味します。
オブジェクトの寿命は、どのくらい多くの強い参照があるかによって決定されます。
オブジェクトは強い参照が存在する限り解放されません。

Objective-Cでの参照は、デフォルトでは強い参照になります。
通常は、オブジェクトを使用している間は解放されないようにするために、コンパイラが実行時のオブジェクトの寿命を管理できるため、良いことです。
ただし注意しないと下図の左側に示すように、オブジェクト間の強い参照で途切れないチェーンが形成される場合があります。
このような途切れないチェーンが形成されると、それぞれに強い参照が存在するため、実行時にオブジェクトの解放が行われない可能性があります。
その結果、強い参照のサイクルでプログラムがメモリリークを発生させる可能性があります。

strong-ref-cycle-weak-ref_2x.png

図のオブジェクトにおいて、AとB間の参照を切断した場合、B、C、D、Eで形成されている部分は強い参照のサイクルによって互いに結合されているため、『永遠に』存在することになります。
EからBへ弱い参照を導入すると、この強い参照のサイクルを切断することができます。

強い参照のサイクルを弱い参照を使用して修正することは、賢明です。
ランタイムは、オブジェクトの弱い参照と強い参照の両方を追跡します。
オブジェクトへの強い参照が存在しなくなった後、オブジェクトは解放され、全てのオブジェクトへの弱い参照はnilに設定されます。
(グローバル、インスタンス、そしてローカル)変数では、弱い参照を表すために変数名の前に__weak修飾語を使用します。
プロパティの場合は、weakオプションを使用します。
以下の種類の参照では、弱い参照を使用する必要があります。

  • デリゲート
    @property(weak) id delegate;
    貴方はデザインパターンの記事『Streamline Your App with Design Patterns』でデリゲートとターゲットについて学びます。

  • アウトレットはトップレベルのオブジェクトへの参照ではない
    @property(weak) IBOutlet NSString *theName;
    アウトレットは、アプリがストーリーボードまたはnibファイルを読み込む時に復元された、ストーリーボードまたはnibファイル内のアーカイブされたオブジェクト間の接続(または参照)です。
    ストーリーボードまたはnibファイル内のトップレベルのオブジェクト(通常はウィンドウ、ビュー、ビューコントローラ、あるいは他のコントローラ)のためのアウトレットは、(デフォルトでは指定されませんが)強い参照である必要があります。

  • ターゲット
    (void)setTarget:(id __weak)target

  • ブロック内のselfへの参照
    __block typeof(self) tmpSelf = self;
    [self methodThatTakesABlock:^ {
        [tmpSelf doSomething];
    }];
    ブロックは獲得する変数への強い参照を形成しています。
    ブロック内でselfを使用する場合、ブロックはselfに強い参照を形成しますが、(通常は)selfもブロックに強い参照を持ち、結果として強い参照のサイクルとなります。
    サイクルを回避するためには上記の例のように、ブロックの外側でselfに弱い参照(または__block)を生成する必要があります。



●可変性オブジェクトの管理

可変オブジェクトは、オブジェクトを生成した後にその状態を変更することができます。
通常はプロパティやアクセサメソッドによって変更を行います。
不変オブジェクトは、オブジェクトを生成した後にカプセル化された状態を変更することができません。
Objective-Cフレームワークのほとんどのクラスで生成するインスタンスは可変であり、不変は少数です。
不変オブジェクトは以下のような利点を提供します。
  • 不変オブジェクトは使用中に突然値が変更されることはありません。

  • オブジェクトの多くの型は、オブジェクトが不変の場合、アプリケーションのパフォーマンスを向上させます。
Objective-Cフレームワークでは、不変クラスのインスタンスは通常、配列や文字列などの離散またはバッファされた値のコレクションをカプセル化したものです。
これらのクラスには通常、名前に『Mutable』の付いた可変のクラスがあります。
例えば、(不変の)NSStringクラスとNSMutableStringクラスがあります。
カプセル化した離散値の不変オブジェクトであるNSNumberNSDateなどのいくつかのクラスは、可変クラスを持たないことに注意してください。

オブジェクトの内容を頻繁に増加する変更が予想される場合は、不変クラスの代わりに可変クラスを使用してください。
不変オブジェクトとして型付けされたフレームワークのオブジェクトを受信した場合、戻り値の型は尊重し、オブジェクトに変更を加えようとはしないでください。



●値オブジェクトの生成と使用

値オブジェクトは、(Cのデータ型の)プリミティブ値をカプセル化したオブジェクトで、値に関連したサービスを提供します。
値オブジェクトはオブジェクトの形式の内、スカラー型を表します。
Foundationフレームワークは、文字列、バイナリデータ、日付と時刻、数値などの値オブジェクトを生成する以下のクラスを提供します。

    NSStringNSMutableString
    NSDataNSMutableData
    NSDate
    NSNumber
    NSValue

値オブジェクトはObjective-Cプログラミングにおいて重要です。
貴方はアプリケーションが呼び出すメソッドや関数の引数や戻り値として、これらのオブジェクトに頻繁に遭遇するでしょう。
値オブジェクトを渡すことによって、フレームワークの異なる部分や異なるフレームワークでも、データを交換することができます。
値オブジェクトはスカラー値を表しているので、オブジェクトを必要とするコレクション内でもどこでも使用することができます。
しかしプリミティブ値の持つ共通性やそれに伴う必要性以上に、カプセル化するプリミティブ型よりも優れた利点を持ちます。
値オブジェクトは、単純かつ効率的な方法でカプセル化された値を一定の操作で実行することができます。
例えばNSStringクラスには、部分文字列の検索と置換や、ファイルや(より望ましい)URLへの文字列の書き込み、あるいはファイルシステムパスを構築するメソッドがあります。

時には、より効率的で分かり易い(intやfloatなどで型付けされた値の)プリミティブ型を使用しているのを見かけるでしょう。
そのような状況の主な例は、値の計算です。
したがってNSNumberNSValueオブジェクトは、フレームワークメソッドの引数や戻り値として、あまり一般的には使用されません。
しかしNSIntegerCGFloatなど、多くのフレームワークは独自の数値データ型を宣言し、引数や戻り値としてこれらの型を使用しています。
基盤となるプラットフォームから離れた抽象的なコードから貴方を助けるため、これらのフレームワークで定義されている適切な型を使用する必要があります。


値オブジェクトの基本的な使用法

値オブジェクトを生成する基本的なパターンは、プリミティブ型のデータから貴方のコードまたはフレームワークのコードに対して(おそらくメソッドの引数として渡すために)生成する場合です。
貴方のコードでは、後にオブジェクトからカプセル化したデータへアクセスします。
NSNumberクラスは、このアプローチの最も明確な例を提供します。

int n = 5;    // Value assigned to primitive type
NSNumber *numberObject = [NSNumber numberWithInt:n];    // Value object created from primitive type
int y = [numberObject intValue];    // Encapsulated value obtained from value object (y == n)

ほとんどの『値』クラスは、そのインスタンスを生成するためのイニシャライザとクラスファクトリメソッドの両方を宣言しています。
また一部のクラス(特にNSStringNSData)はメモリ内のデータからだけではなく、ローカルまたはリモートファイルに格納されたプリミティブデータから、そのインスタンスを生成するためのイニシャライザとクラスファクトリメソッドを提供します。
また、これらのクラスはファイルまたはURLで指定された場所に、文字列やバイナリデータを書き込む補完的なメソッドも提供します。
以下の例のコードでは、URLオブジェクトによって指定された場所にあるファイルの内容から、NSDataオブジェクトを生成するinitWithContentsOfURL:メソッドを呼び出し、データを使用した後、ファイルシステムにデータオブジェクトを書き戻します。

NSURL *theURL = // Code that creates a file URL from a string path...
NSData *theData = [[NSData alloc] initWithContentsOfURL:theURL];
// use theData...
[theData writeToURL:theURL atomically:YES];

ほとんどの値クラスは値オブジェクトの生成だけでなく、カプセル化された値へのアクセスや、オブジェクトの比較などの単純な操作のためのメソッドを提供します。

プロパティとして値クラスのインスタンスを宣言する際には、copyオプションを使用する必要があります。


文字列とNSStringリテラル

CのスーパーセットであるObjective-Cは、Cと同様に文字列を指定するための同じ規則をサポートしています。
(単一文字は単一引用符で、文字の文字列は二重引用符で囲みます。)
ただし、通常のObjective-CフレームワークではC文字列を使用しないでください。
代わりにNSStringオブジェクトを使用します。

初めてのiOSアプリケーション』でHelloWorldアプリを生成した際に、貴方はフォーマットされた文字列を生成しました。

NSString *greeting = [[NSString alloc] initWithFormat:@"Hello, %@!", nameString];

NSStringクラスは文字列のためのオブジェクトラッパーを提供し、それによって異なる文字エンコーディング(特にUnicode)や、printfスタイルのフォーマットユーティリティのサポート、任意長の文字列を格納するための組み込みのメモリ管理などの便宜を提供します。
文字列は一般的に使用されるため、Objective-Cは定数値からNSStringオブジェクトを生成するための簡単な表記法を提供しています。
このNSStringリテラルを使用するには、以下の例に示すように、通常は二重引用符で囲んだ文字列の前にアットマーク(@)を付けます。

// Create the string "My String" plus carriage return.
NSString *myString = @"My String\n";
// Create the formatted string "1 String".
NSString *anotherString = [NSString stringWithFormat:@"%d %@", 1, @"String"];
// Create an Objective-C string from a C string.
NSString *fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];


NSNumberリテラル

Objective-Cはまた、NSNumberオブジェクトを生成するための簡単な表記法も提供しており、そのようなオブジェクトを生成するためにイニシャライザやクラスファクトリメソッドを呼び出す必要がありません。
単純に数値の前にアットマーク(@)を置くだけで、必要に応じて値の型を示すインジケータを付加します。
例えば、整数値と倍精度値をカプセル化したNSNumberオブジェクトの生成は、以下のように記述します。

NSNumber *myIntValue = @32;
NSNumber *myDoubleValue = @3.22346432;

同様に、NSNumberリテラルを使用してブール値や文字値をカプセル化して生成することもできます。

NSNumber *myBoolValue = @YES;
NSNumber *myCharValue = @'V';

値の表記に文字『U』『L』『LL』『F』を付加することによって、それぞれ符号無し整数、long整数、long long整数、浮動小数点値を表すNSNumberオブジェクトを生成することができます。
例えば、浮動小数点値をカプセル化したNSNumberオブジェクトの生成は、以下のように記述します。

NSNumber *myFloatValue = @3.2F


日付と時刻

NSDateオブジェクトはプリミティブ値として時間特有の性質を持つため、他の種類の値オブジェクトとは異なります。
日付オブジェクトは、基準日からの感覚を秒単位でカプセル化します。
その基準日は、GMTの2001年1月1日の最初の瞬間です。

NSDateのインスタンス自体で行えることは僅かです。
それは時間の内で現在の瞬間を表しますが、暦やタイムゾーン、ロケールの時間規則によって提供されるコンテキスト無しで表されています。
幸いにも、これらの概念の物を表すFoundationクラスがあります。

  • NSCalendarNSDateComponents
    暦で日付を関連付けた後、暦から日付の年、月、時、日、週などの時間の単位を導出することができます。
    また暦の計算を実行することもできます。

  • NSTimeZone
    日付や時刻に地域のタイムゾーンを反映する必要がある場合は、暦にタイムゾーンオブジェクトを関連付けることができます。

  • NSLocale
    ロケールオブジェクトは、時間に関連する文化的・言語的規則をカプセル化します。

以下のコードでは、必要とする情報(この場合、現在の時間の時、分、秒を出力)を取得するために、NSDateオブジェクトと他のオブジェクトをどのように使用することができるかを例示しています。

NSDate *now = [NSDate date];    // 1
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];    // 2
[calendar setTimeZone:[NSTimeZone systemTimeZone]];    // 3
NSDateComponents *dc = [calendar components:(NSHourCalendarUnit|NSMinuteCalendarUnit|
     NSSecondCalendarUnit) fromDate:now];    // 4
NSLog(@"The time is %d:%d:%d", [dc hour], [dc minute], [dc second]);    // 5

コードの各番号の行を解説します。
  1. 現在の瞬間を表す日付オブジェクトを生成します。
  2. グレゴリオ暦を表すオブジェクトを生成します。
  3. システム環境設定で指定されたタイムゾーンを表すオブジェクトで、暦オブジェクトを設定します。
  4. 手順1で生成された日付オブジェクトを渡し、暦オブジェクトのcomponents:fromDate:メソッドを呼び出します。
    この呼び出しは日付オブジェクトの時、分、秒要素を含むオブジェクトを返します。
  5. コンソールへ現在の時、分、秒を出力します。
この例では結果をログに出力していますが、アプリのユーザインターフェイスで日付情報を表示するためには、日付フォーマッタ(NSDateFormatterクラスのインスタンス)を使用したアプローチが推奨されます。
暦計算では常に適切なクラスとメソッドを使用する必要があり、分、時、日などの要素を数値でハードコードしてはいけません。



●コレクションの生成と使用

コレクションは特定の方法で他のオブジェクトを格納し、クライアントがそれらのオブジェクトへアクセスできるようにするオブジェクトです。
貴方はしばしばメソッドや関数の引数としてコレクションを渡したり、メソッドや関数の戻り値としてコレクションを取得したりします。
多くの場合コレクションは値オブジェクトを格納しますが、全てのオブジェクトの型を格納できるわけではありません。
ほとんどのコレクションは、含まれているオブジェクトへの強い参照を持っています。

Foundationフレームワークにはいくつかのコレクションの型がありますが、Cocoa TouchとCocoaプログラミングにおいては、配列、辞書、そしてセットの3つが特に重要です。
これらのコレクションのクラスには不変と可変の2種類があります。
可変コレクションではオブジェクトの追加と削除が可能ですが、不変コレクションは生成時のオブジェクトしか含めることはできません。
全てのコレクションはコンテンツを列挙することができ、言い換えれば格納されているオブジェクトは順番に検査されるということになります。

コレクションには様々な型があり、特有の方法でオブジェクトを格納します。

  • NSArrayNSMutableArray
    配列は順序付けされたオブジェクトのコレクションです。
    オブジェクトには配列内の位置(つまりインデックス)を指定することによってアクセスします。
    配列の最初の要素のインデックスは0(ゼロ)です。

  • NSDictionaryNSMutableDictionary
    辞書はキーと値をペアとしてエントリを格納します。
    キーは一意の識別子で通常は文字列であり、値は格納したいオブジェクトです。
    キーを指定することによってオブジェクトにアクセスします。

  • NSSetNSMutableSet
    セットは各オブジェクトが一度だけ出現する、順不同のオブジェクトのコレクションです。
    一般的にはテストまたはフィルタを適用することによって、セット内のオブジェクトにアクセスします。

collections_2x.png

このように格納、アクセス、実行の特性により、あるコレクションのタイプは他のタイプよりも特定のタスクに適したものにすることができます。

配列や辞書の生成と含まれている値へのアクセスは、NSArrayNSDictionaryのメソッドを呼び出すことによって、または特別なObjective-Cのコンテナリテラルやサブスクリプティング技術を使用することによってすることができます。
以下の項目では両方のアプローチに付いて説明します。


配列内に特定の順序でオブジェクトを格納

配列は順序付けられたシーケンス内にオブジェクトを格納します。
したがって、コレクション内のオブジェクトの順序が重要な場合に配列を使用します。
例えば多くのアプリケーションでは、テーブルビューの行やメニューの項目へコンテンツを提供するために、配列が使用されます。
インデックス0のオブジェクトが最初の行に対応し、インデックス1のオブジェクトが2行目に対応するという形になります。
配列内のオブジェクトへのアクセス時間は、セット内のオブジェクトよりも遅くなります。


配列の生成

NSArrayクラスには配列を生成・初期化するための多くのイニシャライザやクラスファクトリメソッドがありますが、いくつかのメソッドは特に一般的で実用的です。
arrayWithObjects:count:arrayWithObjects:メソッド(と対応するイニシャライザ)で、一連のオブジェクトから配列を生成することができます。
前者のメソッドでは第二引数に第一引数のオブジェクトの数を指定しますが、後者のメソッドではnilで終端されたコンマ区切りの一連のオブジェクトを指定します。

// Compose a static array of string objects
NSString *objs[3] = {@"One", @"Two", @"Three"};
// Create an array object with the static array
NSArray *arrayOne = [NSArray arrayWithObjects:&(*objs) count:3];
// Create an array with a nil-terminated list of objects
NSArray *arrayTwo = [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];

可変配列を生成する場合は、配列の生成にarrayWithCapacity:(またはinitWithCapacity:)メソッドを使用することができます。
capacity引数は予想される配列のサイズについてクラスにヒントを与えるもので、実行時により効率的に配列を作成することができます。
配列は指定した容量を超えることができます。

またコンテナリテラル@[...]を使用し、角括弧内にコンマ区切りでオブジェクトを指定して、配列を生成することもできます。
例えば、文字列、数値、そして日付を格納した配列の生成は、下記のように記述することができます。

NSArray *myArray = @[ @"Hello World", @67, [NSDate date] ];


配列内のオブジェクトへのアクセス

一般的に、配列内における(ゼロベースの)インデックス位置を指定することによって、配列内オブジェクトにアクセスするにはobjectAtIndex:メソッドを呼び出します。

NSString *theString = [arrayTwo objectAtIndex:1];    // returns second object in array

NSArrayは、配列内のオブジェクトまたは配列のインデックスにアクセスする他のメソッドも提供します。
例えば、lastObjectfirstObjectCommonWithArray、そしてindexOfObjectPassingTest:などがあります。

配列内のオブジェクトへのアクセスにNSArrayのメソッドを使用する代わりに、添字表記を使用することもできます。
例えば、(上記で生成した)myArray内の2番目のオブジェクトへアクセスするには、以下のような記述をすることもできます。

id theObject = myArray[1];

配列におけるもう一つの一般的なタスクは、配列内の各オブジェクトで何かを行う、列挙として知られている処理です。
多くの場合、1つまたは複数のオブジェクトが特定の値または条件に一致するかどうかを判別するために配列を列挙し、合致した場合にアクションを完了します。
配列を列挙するには、高速列挙、ブロックベースの列挙、またはNSEnumeratorオブジェクトを使用するという3つのアプローチを採ることができます。
高速列挙はその名の通り、通常は配列内のオブジェクトへアクセスする他の技術を使用するよりも高速です。
高速列挙は特定の構文を必要とする言語の機能です。

for (type variable in array){ /* inspect variable, do something with it */ }

例えば、

NSArray *myArray =    // get array
for (NSString *cityName in myArray) {
    if ([cityName isEqualToString:@"Cupertino"]) {
        NSLog(@"We're near the mothership!");
        break;
    }
}

いくつかのNSArrayメソッドはブロックで配列を列挙でき、enumerateObjectsUsingBlock:は最も単純なメソッドです。
ブロックには現在のオブジェクト、そのインデックス、そして(YESに設定された場合に列挙を終了するための)参照されるブール値の3つの引数があります。
ブロック内のコードは、高速列挙文の中括弧間のコードとして、同じ処理を正確に実行します。

NSArray *myArray =    // get array
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([obj isEqual:@"Cupertino"]) {
        NSLog(@"We're near the mothership!");
        *stop = YES;
    }
}];


可変配列の管理

NSArrayは配列のソート、配列の検索、そして配列内の各オブジェクトのメソッドを呼び出す、他のメソッドを持っています。

addObject:メソッドを呼び出すことによって、可変配列にオブジェクトを追加することができます。
オブジェクトは配列の最後に置かれます。
また可変配列内の特定の場所にオブジェクトを配置するには、insertObject:atIndex:を使用することができます。
removeObject:メソッドまたはremoveObjectAtIndex:メソッドを呼び出すことによって、可変配列からオブジェクトを削除することができます。

また可変配列内の特定の場所にオブジェクトを挿入するために、添字記法を使用することもできます。

NSMutableArray *myMutableArray = [NSMutableArray arrayWithCapacity:1];
NSDate *today = [NSDate date];
myMutableArray[0] = today;


辞書内にキーと値のペアを格納

キーと値をペアとして(つまり識別子(キー)とオブジェクト(値)を組み合わせて)オブジェクトを格納するために辞書を使用することができます。
キーと値のペアは任意の順序で指定できるため、辞書は順不同のコレクションです。
キーには実質的になんでも指定できますが、一般的には(文字列定数である)NSFileModificationDateUIApplicationStatusBarFrameUserInfoKeyなど、値を説明する文字列が使用されます。
公開キーが存在する場合、辞書はオブジェクト間のあらゆる種類の情報を渡すための素晴らしい方法です。


辞書の生成

NSDictionaryクラスはイニシャライザやクラスファクトリメソッドを介して辞書を生成する多くの方法を提供していますが、特に一般的なのはdictionaryWithObjects:forKeys:dictionaryWithObjectsAndKeys:(またはそれに対応するイニシャライザ)の2つのクラスメソッドです。
前者のメソッドは、オブジェクトの配列とキーの配列を渡します。
(キーと値の位置関係は一致する必要があります。)
後者のメソッドは、最初のオブジェクトの値とそのキー、次に二番目のオブジェクトの値とそのキー・・・と指定します。
このオブジェクトの一連の終端はnilで通知します。

// First create an array of keys and a complementary array of values
NSArray *keyArray = [NSArray arrayWithObjects:@"IssueDate", @"IssueName", @"IssueIcon", nil];
NSArray *valueArray = [NSArray arrayWithObjects:[NSDate date], @"Numerology Today",
    self.currentIssueIcon, nil];
// Create a dictionary, passing in the key array and value array
NSDictionary *dictionaryOne = [NSDictionary dictionaryWithObjects:valueArray forKeys:keyArray];
// Create a dictionary by alternating value and key and terminating with nil
NSDictionary *dictionaryTwo = [[NSDictionary alloc] initWithObjectsAndKeys:[NSDate date],
    @"IssueDate", @"Numerology Today", @"IssueName", self.currentIssueIcon, @"IssueIcon", nil];

配列の場合と同様に、コンテナリテラル@{key : value, ...}(『...』は任意の数のキーと値の組)を使用することによって、NSDictionaryオブジェクトを生成することができます。
例として、3つのキーと値の組で可変辞書オブジェクトを生成するコードを下記に示します。

NSDictionary *myDictionary = @{
    @"name" : NSUserName(),
    @"date" : [NSDate date],
    @"processInfo" : [NSProcessInfo processInfo]
};


辞書内のオブジェクトへのアクセス

objectForKey:メソッドで引数としてキーを指定することによって、辞書内のオブジェクトの値にアクセスすることができます。

NSDate *date = [dictionaryTwo objectForKey:@"IssueDate"];

また添字を使用して辞書内のオブジェクトにアクセスすることもできます。
キーは辞書変数の右後ろの角括弧間に記述します。

NSString *theName = myDictionary[@"name"];


可変辞書の管理

setObject:forKey:removeObjectForKey:メソッドを呼び出すことによって、可変辞書に項目の挿入や削除を行うことができます。
setObject:forKey:メソッドは、指定されたキーの既存の値を置換します。
これらのメソッドは高速です。

また添字を使用して、可変辞書にキーと値の組を追加することもできます。
キーは代入の左辺に添字で、値は右辺に指定します。

NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
mutableDict[@"name"] = @"John Doe";


セット内へ順不同でオブジェクトを格納

セットは、格納されている項目が順序付けられている代わりに順不同になっていることを除けば、配列に類似したコレクションのオブジェクトです。
セット内のオブジェクトへのアクセスは、インデックスで場所を指定したりキーを介して行うのではなく、コレクションを列挙したり、またはセットにフィルタやテストを適用することによって、(anyObjectメソッドを使用して)ランダムにアクセスします。

セットオブジェクトは、辞書や配列などと比べてObjective-Cプログラミングでは一般的ではありませんが、特定の技術において重要なコレクション型です。
(データ管理技術である)Core Dataでは、対多関係のプロパティを宣言する場合、NSSetまたはNSOrderedSetのプロパティ型である必要があります。
またセットは、UIKitフレームワークのネイティブのタッチイベント処理でも重要です。
例えば、

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
    // handle the touch...
}

順序付きセットは、セットの基本的な定義の例外です。
順序付きセットでは、セット内の項目の順序が重要になります。
順序付きセットのメンバーシップのテストは、配列よりも高速です。



●実行時のオブジェクト機能の照合

Objective-CとNSObjectクラスの強力かつ便利な機能は、実行時にオブジェクトに関する特定の事柄について学ぶことができます。
これにより、認識していないオブジェクトへのメッセージの送信や、継承していない特定のクラスを継承しようとした場合など、貴方のコードの間違いを回避することができます。

実行時にオブジェクト自身について開示できる情報には、3つの重要なタイプがあります。
  • 特定のクラスまたはサブクラスのインスタンスかどうか
  • メッセージに応答するかどうか
  • プロトコルに準拠しているかどうか

オブジェクトが特定のクラスまたはそのサブクラスのインスタンスかどうかを知る

オブジェクトがクラスまたはそのサブクラスのインスタンスかどうかを知るには、そのオブジェクトでisKindOfClass:メソッドを呼び出します。
アプリが(実装または継承していて)応答できるメッセージを知りたい場合に、アプリは時々このチェックを行います。

static int sum = 0;
for (id item in myArray) {
    if ([item isKindOfClass:[NSNumber class]]) {
        int i = (int)[item intValue];
        sum += i;
    }
}

isKindOfClass:メソッドは、引数としてClass型のオブジェクトを取ります。
このオブジェクトを取得するには、クラスのシンボルにclassメソッドを呼び出します。
このメソッドによって返されるブール値を評価し、それに応じて進みます。

NSObjectはオブジェクトの継承に関する情報を発見するために、他のメソッドも宣言しています。
例えば、isKindOfClass:がオブジェクトのクラスまたはその派生クラスのいずれかであるかを伝えるのに対し、isMemberOfClass:メソッドはオブジェクトが指定したクラスのインスタンスであるかを伝えます。


オブジェクトがメッセージに応答するかどうかを知る

オブジェクトがメッセージに応答するかどうかを知るには、そのオブジェクトでrespondsToSelector:メソッドを呼び出します。
アプリのコードは、しばしばオブジェクトにメッセージを送信する前に、オブジェクトがメッセージに応答するかどうかを照合します。

if ([item respondsToSelector:@selector(setState:)]) {
    [item setState:[self.arcView.font isBold] ? NSOnState : NSOffState];
}

respondsToSelector:メソッドは、引数としてセレクタを取ります。
セレクタは、メソッド実行時の識別子のためのObjective-Cのデータ型で、@selectorコンパイラディレクティブを使用してセレクタを指定します。
コードでは、このメソッドによって返されるブール値を評価し、それに応じて進みます。

オブジェクトがメッセージに応答するかを識別する際、一般的にクラス型を評価するよりもrespondsToSelector:を呼び出した方が便利です。
例えばクラスの最新バージョンでは、以前のバージョンでは発見されなかったメソッドが実装されている場合があります。


オブジェクトがプロトコルに準拠しているかどうかを知る

オブジェクトがプロトコルに応答するかどうかを知るには、そのオブジェクトでconformToProtocol:メソッドを呼び出します。

- (void) setDelegate:(id __weak) obj {
    NSParameterAssert([obj conformsToProtocol:
        @protocol(SubviewTableViewControllerDataSourceProtocol)]);
    delegate = obj;
}

conformToProtocol:メソッドは、引数として実行時のプロトコルの識別子を取ります。
この識別子には、@protocolコンパイラディレクティブを使用して指定します。
このメソッドによって返されるブール値を評価し、それに応じて進みます。
オブジェクトは、オプションのメソッドを実装していなくてもプロトコルに準拠することができることに注意してください。



●オブジェクトの比較

isEqual:メソッドを使用することによって、2つのオブジェクトを比較することができます。
メッセージを受信するオブジェクトは渡されたオブジェクトと比較され、それらが同じ場合、メソッドはYESを返します。
例えば、

BOOL objectsAreEqual = [obj1 isEqual:obj2];
if (objectsAreEqual) {
    // do something...
}

オブジェクトの等価性は、オブジェクトの同一性と異なることに注意してください。
後者の場合、2つの変数が同じインスタンスを示すかどうかを、等価演算子 == を使用してテストします。

同じクラスの2つのオブジェクト比較する場合、どうなるでしょうか?
それはクラスに依存します。
ルートクラスであるNSObjectの場合、比較の基準としてポインタの等価性を使用しています。
どのレベルのサブクラスでも、オブジェクトの状態としてクラス固有の基準に基く比較を元に、そのスーパークラスの実装をオーバーライドすることができます。
例えば架空のPersonオブジェクトは、両方のオブジェクトの名、姓、誕生日の属性が一致している場合、別のPersonオブジェクトと等しい可能性があります。

Foundationフレームワークの値とコレクションクラスは、isEqualToString:isEqualToDictionary:など、isEqualToType:形式(Typeはクラス型からNS接頭辞の除いたもの)の比較メソッドを宣言しています。
比較メソッドは渡されたオブジェクトが指定した型であり、そうでない場合は例外が発生すると仮定しています。



●オブジェクトのコピー

copyメッセージを送信することによって、オブジェクトのコピーを作成することができます。

NSArray *myArray = [yourArray copy];

コピーするには、受信側のオブジェクトのクラスがNSCopyingプロトコルに準拠している必要があります。
オブジェクトをコピーできるようにしたい場合は、このプロトコルを採用し、copyメソッドを実装する必要があります。

時々、プログラム内の他の場所から取得したオブジェクトについて、使用している間はオブジェクトの状態が変更されないようにしたい場合は、オブジェクトをコピーします。

コピー動作はクラス固有のものであり、インスタンスの特定の性質に依存します。
ほとんどのクラスは深いコピーを実装しており、全てのインスタンス変数とプロパティの複製を作成しますが、(コレクションクラスなど)いくつかのクラスは浅いコピーを実装しており、インスタンス変数とプロパティの参照のみ複製します。

不変と可変の種類があるクラスでは、オブジェクトの可変コピーを生成するためのmutableCopyメソッドを宣言しています。
例えばNSStringオブジェクト上でmutableCopyを呼び出した場合、NSMutableStringのインスタンスを取得します。



参考文献

Apple/Start Developing iOS Apps Today

Apple/Acquire Foundational Programming Skills

0 CommentsPosted in 資料





Lifestyle 650 home entertainment system
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
10 | 2017/11 | 12
Sun Mon Tue Wed Thu Fri Sat
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

Lifestyle 650 home entertainment system
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR