Objective-Cによるプログラミング (TP )

Similar documents
プログラミング基礎I(再)

プログラミング実習I

Microsoft PowerPoint - ruby_instruction.ppt

Javaプログラムの実行手順

JavaプログラミングⅠ


JavaプログラミングⅠ

Prog1_6th

GEC-Java

Microsoft PowerPoint Java基本技術PrintOut.ppt [互換モード]

PowerPoint プレゼンテーション

Prog2_12th

(1) プログラムの開始場所はいつでも main( ) メソッドから始まる 順番に実行され add( a,b) が実行される これは メソッドを呼び出す ともいう (2)add( ) メソッドに実行が移る この際 add( ) メソッド呼び出し時の a と b の値がそれぞれ add( ) メソッド

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション

デザインパターン第一章「生成《

た場合クラスを用いて 以下のように書くことが出来る ( 教科書 p.270) プログラム例 2( ソースファイル名 :Chap08/AccountTester.java) // 銀行口座クラスとそれをテストするクラス第 1 版 // 銀行口座クラス class Account String name

kantan_C_1_iro3.indd

Microsoft PowerPoint - chap10_OOP.ppt

Microsoft PowerPoint - 09.pptx

/*Source.cpp*/ #include<stdio.h> //printf はここでインクルードして初めて使えるようになる // ここで関数 average を定義 3 つの整数の平均値を返す double 型の関数です double average(int a,int b,int c){

バイオプログラミング第 1 榊原康文 佐藤健吾 慶應義塾大学理工学部生命情報学科

Microsoft PowerPoint - prog03.ppt

.NETプログラマー早期育成ドリル ~VB編 付録 文法早見表~

書式に示すように表示したい文字列をダブルクォーテーション (") の間に書けば良い ダブルクォーテーションで囲まれた文字列は 文字列リテラル と呼ばれる プログラム中では以下のように用いる プログラム例 1 printf(" 情報処理基礎 "); printf("c 言語の練習 "); printf

Java講座

Microsoft PowerPoint ppt

02: 変数と標準入出力

レコードとオブジェクト

プログラミング基礎

Microsoft PowerPoint - lec10.ppt

V8.1新規機能紹介記事

Java Scriptプログラミング入門 3.6~ 茨城大学工学部情報工学科 08T4018Y 小幡智裕

02: 変数と標準入出力

C プログラミング演習 1( 再 ) 2 講義では C プログラミングの基本を学び 演習では やや実践的なプログラミングを通して学ぶ

Microsoft PowerPoint - 計算機言語 第7回.ppt

PowerPoint プレゼンテーション

Microsoft PowerPoint ppt

ポインタ変数

Java知識テスト問題

PYTHON 資料 電脳梁山泊烏賊塾 PYTHON 入門 関数とメソッド 関数とメソッド Python には関数 (function) とメソッド (method) が有る モジュール内に def で定義されて居る物が関数 クラス内に def で定義されて居る物がメソッドに成る ( 正確にはクラスが

Prog1_2nd

Prog1_10th

メソッドのまとめ

PowerPoint Presentation

02: 変数と標準入出力

メソッドのまとめ

Microsoft Word - no11.docx

JavaプログラミングⅠ

ファイナライザを理解する ~ ファイナライザに起因するトラブルを避けるために ~ 2013 年 11 月 25 日 橋口雅史 Java アプリケーションでファイナライザ (finalize() メソッド ) を使用したことがあるプログラマーは多いと思います しかし ファイナライザの仕組みや注意点につ

プログラミング入門1

Microsoft Word - VBA基礎(6).docx

ご利用のコンピュータを設定する方法 このラボの作業を行うには 事前設定された dcloud ラボを使用するか 自身のコンピュータをセットアップします 詳細については イベントの事前準備 [ 英語 ] とラボの設定 [ 英語 ] の両方のモジュールを参照してください Python を使用した Spar

プレポスト【問題】

第 2 章インタフェース定義言語 (IDL) IDL とは 言語や OS に依存しないインタフェース定義を行うためのインタフェース定義言語です CORBA アプリケーションを作成する場合は インタフェースを定義した IDL ファイルを作成する必要があります ここでは IDL の文法や IDL ファイ

(2) 構造体変数の宣言 文法は次のとおり. struct 構造体タグ名構造体変数名 ; (1) と (2) は同時に行える. struct 構造体タグ名 { データ型変数 1; データ型変数 2;... 構造体変数名 ; 例 : struct STUDENT{ stdata; int id; do

関数の動作 / printhw(); 7 printf(" n"); printhw(); printf("############ n"); 4 printhw(); 5 関数の作り方 ( 関数名 ) 戻り値 ( 後述 ) void である. 関数名 (

Sort-of-List-Map(A)

目次 はじめに 4 概要 4 背景 4 対象 5 スケジュール 5 目標点 6 使用機材 6 第 1 章 C# 言語 7 C# 言語の歴史 7 基本構文 8 C 言語との違い 9 Java 言語との違い 10.Netフレームワーク 10 開発資料 10 第 2 章 Mono 11 Monoの歴史 1

使用する前に

演算増幅器

02: 変数と標準入出力

Prog1_3rd

ガイダンス

Java言語 第1回

プログラミング入門1

第1章 ビジュアルプログラミング入門

プログラミング入門1

Microsoft PowerPoint - 04_01_text_UML_03-Sequence-Com.ppt

Microsoft Word - no02.doc

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション

Java の ConcurrentHashMap における同期化 バッドケースとその対処法 2013 年 9 月湊隆行 1. はじめに表 1.1 に示すように Java の Collections Framework には 3 つの世代があります バージョン 1.0 から存在するレガシー API バ

RX ファミリ用 C/C++ コンパイラ V.1.00 Release 02 ご使用上のお願い RX ファミリ用 C/C++ コンパイラの使用上の注意事項 4 件を連絡します #pragma option 使用時の 1 または 2 バイトの整数型の関数戻り値に関する注意事項 (RXC#012) 共用

2006年10月5日(木)実施

Java プログラミング Ⅰ 3 回目変数 変数 変 数 一時的に値を記憶させておく機能型 ( データ型 ) と識別子をもつ 2 型 ( データ型 ) 変数の種類型に応じて記憶できる値の種類や範囲が決まる 型 値の種類 値の範囲 boolean 真偽値 true / false char 2バイト文

数はファイル内のどの関数からでも参照できるので便利ではありますが 変数の衝突が起こったり ファイル内のどこで値が書き換えられたかわかりづらくなったりなどの欠点があります 複数の関数で変数を共有する時は出来るだけ引数を使うようにし グローバル変数は プログラムの全体の状態を表すものなど最低限のものに留

02: 変数と標準入出力

プログラミング基礎

第 1 章 : はじめに RogueWave Visualization for C++ の Views5.7 に付属している Views Studio を使い 簡単な GUI アプリケーションの開発手順を紹介します この文書では Windows 8 x64 上で Visual Studio2010

Microsoft PowerPoint - prog04.ppt

6 関数 6-1 関数とは少し長いプログラムを作るようになると 同じ処理を何度も行う場面が出てくる そのたびに処 理を書いていたのでは明らかに無駄であるし プログラム全体の見通しも悪くなる そこで登場す るのが 関数 である 関数を使うことを 関数を呼び出す ともいう どのように使うのか 実際に見て

Microsoft PowerPoint - handout07.ppt [互換モード]

Microsoft PowerPoint - CproNt02.ppt [互換モード]

7 ポインタ (P.61) ポインタを使うと, メモリ上のデータを直接操作することができる. 例えばデータの変更 やコピーなどが簡単にできる. また処理が高速になる. 7.1 ポインタの概念 変数を次のように宣言すると, int num; メモリにその領域が確保される. 仮にその開始のアドレスを 1

Delphi/400バージョンアップに伴う文字コードの違いと制御

生成された C コードの理解 コメント元になった MATLAB コードを C コード内にコメントとして追加しておくと その C コードの由来をより簡単に理解できることがよくありま [ 詳細設定 ] [ コード外観 ] を選択 C コードのカスタマイズ より効率的な C コードを生成するベストプラクテ

PowerPoint プレゼンテーション

講習No.1

プログラミング入門1

Microsoft Word - no103.docx

PowerPoint プレゼンテーション

UMLプロファイル 機能ガイド

概要 プログラミング論 変数のスコープ, 記憶クラス. メモリ動的確保. 変数のスコープ 重要. おそらく簡単. 記憶クラス 自動変数 (auto) と静的変数 (static). スコープほどではないが重要.

Microsoft PowerPoint - kougi2.ppt

Microsoft PowerPoint - prog06.ppt

4 月 東京都立蔵前工業高等学校平成 30 年度教科 ( 工業 ) 科目 ( プログラミング技術 ) 年間授業計画 教科 :( 工業 ) 科目 :( プログラミング技術 ) 単位数 : 2 単位 対象学年組 :( 第 3 学年電気科 ) 教科担当者 :( 高橋寛 三枝明夫 ) 使用教科書 :( プロ

PowerPoint Presentation

Javaの作成の前に

PowerPoint プレゼンテーション

Transcription:

Objective-C プログラミング言語

目次 Objective-C について 7 はじめに 7 アプリケーションはさまざまなオブジェクトが絡み合ったネットワークとして構築する 7 カテゴリで既存のクラスを拡張する 8 プロトコルはメッセージのやり取り方法 ( 契約 ) を定義する 8 値やコレクションの多くは Objective-C のオブジェクトとして表す 8 ブロックを使えばよくあるタスクを簡潔に記述できる 9 エラーオブジェクトを使って実行時に発生した問題を処理する 9 Objective-C のコードは所定の規約に基づいて記述する 9 必要事項 10 関連項目 10 クラスの定義 12 クラスはオブジェクトの 設計図 である 12 オブジェクトが表す値には 可変か不変か という区別がある 13 クラスは他のクラスの性質を継承することができる 13 ルートクラスには基本機能が実装されている 14 クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している 15 基本的な構文 16 プロパティはオブジェクトの値に対するアクセスを制御する 16 メソッド宣言はオブジェクトが受け付け可能なメッセージを表す 17 クラス名は一意でなければならない 20 クラスの実装は その内部的な振る舞いを規定する 21 基本的な構文 21 メソッドを実装する 21 Objective-C ではクラスもオブジェクトの一種である 23 練習問題 24 オブジェクトの操作 25 オブジェクトはメッセージを送受信する 25 ポインタを使ってオブジェクトを追跡管理する 26 メソッドの引数としてオブジェクトを渡すことができる 27 メソッドは値を返すことができる 28 オブジェクトは自分自身にメッセージを送信できる 30 2

目次 オブジェクトはスーパークラスに実装されたメソッドを呼び出すことができる 31 オブジェクトは動的に生成される 32 初期化メソッドは引数を取ることができる 34 ファクトリメソッドは 割り当てと初期化を記述する代替として使える 35 初期化に引数が必要なければ newでオブジェクトを生成できる 35 リテラルはオブジェクトを生成するための短縮構文である 35 Objective-Cは動的言語である 36 オブジェクトの等価性を判断する 37 nilの取り扱い 38 練習問題 40 データのカプセル化 42 プロパティにはオブジェクトの値をカプセル化する働きがある 42 公開プロパティを宣言してデータを公開する 42 アクセサメソッドを使ってプロパティ値を取得 / 設定する 43 ドット構文を使うとアクセサメソッドの呼び出しを簡潔に記述できる 44 プロパティの多くはインスタンス変数を使って実装されている 45 初期化メソッドから直接インスタンス変数にアクセスする 47 独自のアクセサメソッドを実装することもできる 50 プロパティは一般にアトミックである 51 所有と責任の観点からオブジェクトグラフを管理する 52 強い参照の循環を回避する 54 強い宣言と弱い宣言で所有関係を管理する 55 弱い参照が使えないクラスに対して 安全でない ( 保持されない ) 参照を使う 58 copy プロパティは複製を作って保持する 58 練習問題 60 既存のクラスのカスタマイズ 62 カテゴリには既存のクラスにメソッドを追加する働きがある 62 カテゴリのメソッド名の衝突を回避する 65 クラス拡張はクラスの内部的な実装を拡張する 66 非公開にしたい情報をクラス拡張で隠蔽する 67 クラスをカスタマイズする他の手段を検討する 69 Objective-C の実行時ルーチンと直接やり取りする 70 練習問題 70 プロトコルの使い方 71 プロトコルはメッセージのやり取り方法 ( 契約 ) を定義する 71 必須ではないメソッドもプロトコルとして指定できる 73 プロトコルに 他のプロトコルに準拠する ( 継承する ) 旨を指定できる 75 3

目次 プロトコルに準拠する 75 Cocoa や Cocoa Touch には多数のプロトコルが定義されている 76 プロトコルは匿名化のためにも使う 77 値とコレクション 79 C の基本型は Objective-C でも使える 79 Objective-C にはほかにも基本型が定義されている 80 C の構造体には基本型の値を保持できる 81 オブジェクトは基本型の値を表現できる 82 文字列は NSString クラスのインスタンスで表す 82 数値は NSNumber クラスのインスタンスで表す 83 NSValue クラスのインスタンスで他の値を表現する 84 コレクションの多くはオブジェクトである 85 配列は順序つきコレクションである 86 集合は順序なしコレクションである 90 辞書はキーと値の組をまとめたものである 90 nil を NSNull で表す 92 コレクションを利用してオブジェクトグラフの永続性を保つ 93 コレクションの効率的な列挙技法を利用する 94 高速列挙の機能により コレクションの各要素に関する反復処理を容易に記述できる 94 多くのコレクションは列挙子オブジェクトにも対応している 95 コレクションの多くはブロックベースの列挙にも対応している 96 ブロックの使い方 97 ブロックの構文 97 ブロックは 引数を取り 値を返すことができる 98 ブロックは外側のスコープから値を取り込むことができる 99 ブロックを引数としてメソッドや関数に渡すことができる 101 型定義を使ってブロック構文を簡潔に表現する 103 オブジェクトはプロパティを使ってブロックを追跡管理する 104 self を取り込む際には強い参照の循環を避けなければならない 105 ブロックを使えば列挙が簡潔に記述できる 106 ブロックを使えば並行タスクを簡潔に記述できる 108 オペレーションキューを使ってブロックを実行する 108 ディスパッチキューを介して GCD(Grand Central Dispatch) 上でブロックをスケジューリング する 109 エラー処理 111 エラーの多くは NSError で表す 111 デリゲートメソッドの中にはエラーを警告してくれるものがある 112 4

目次 メソッドの中にはエラーオブジェクトの参照を返すものがある 112 可能ならばエラーを解消し そうでなければユーザに通知する 113 独自のエラーオブジェクトを生成する 113 例外はプログラミング上のエラーを示すために使う 115 コーディング規約 116 名前の中にはアプリケーション全体で一意的でなければならないものがある 116 クラス名はアプリケーション全体で一意的でなければならない 116 メソッド名はその機能を簡潔に表すもので クラス内で一意的でなければならない 117 局所変数は同一スコープ内で一意でなければならない 118 ある使い方のメソッドは 所定の命名規約に従わなければならない 119 アクセサメソッド名は規約に従わなければならない 119 オブジェクト生成メソッドの名前は規約に従わなければならない 120 書類の改訂履歴 121 5

図 クラスの定義 12 図 1-1 種と種との生物分類学的な関係 13 図 1-2 NSMutableString クラスの継承関係 14 図 1-3 UIButton のクラス階層 15 オブジェクトの操作 25 図 2-1 メッセージ送信時の 基本的なプログラムの流れ 26 図 2-2 self にメッセージを送信したときの プログラムの流れ 31 図 2-3 オーバーライドしたメソッドを呼び出す場合のプログラムの流れ 32 図 2-4 super にメッセージを送るようにしたときのプログラムの流れ 32 図 2-5 alloc メッセージと init メッセージが入れ子になっている様子 34 データのカプセル化 42 図 3-1 初期化の処理 48 図 3-2 強い参照関係 53 図 3-3 Name Badge Maker アプリケーション 53 図 3-4 XYZPerson オブジェクトを最初に生成した時点のオブジェクトグラフの要点 53 図 3-5 名前を変更したときのオブジェクトグラフの要点 53 図 3-6 バッジビュー更新後のオブジェクトグラフの要点 53 図 3-7 テーブルビューとそのデリゲートの強い参照関係 54 図 3-8 強い参照の循環 54 図 3-9 テーブルビューとそのデリゲートの正しい関係 55 図 3-10 強い参照の循環を回避 55 図 3-11 デリゲートの解放 55 プロトコルの使い方 71 図 5-1 独自の円グラフビュー 72 値とコレクション 79 図 6-1 Objective-C のオブジェクトの配列 86 図 6-2 オブジェクトの集合 90 図 6-3 オブジェクトの辞書 91 6

Objective-C について OS X/iOS 用ソフトウェアの開発には主としてObjective-Cを使います プログラミング言語 Cの仕様を包含する上位言語であり オブジェクト指向の概念に基づく機能と 動的実行時ルーチンを備えています 構文 基本型 制御構造をCから受け継ぎ クラスやメソッドの定義構文を追加しています さらに オブジェクトグラフ管理やオブジェクトリテラルの機能が言語仕様の一部として組み込まれているほか 動的型付けとバインディングの能力を備え さまざまな事項をコンパイル時ではなく実行時に判断しながら動作するようになっています はじめに この資料は プログラミング言語 Objective-Cを紹介し さまざまな使い方の例を示すものです クラスを定義して独自のオブジェクトを記述する方法や Cocoa/Cocoa Touchに付属するフレームワーククラスの使い方も説明します フレームワーククラスは 言語そのものとは独立していますが Objective-Cで記述するコードと密接に関連しており 言語レベルの機能の多くがこのクラスの動作に依存しています アプリケーションはさまざまなオブジェクトが絡み合ったネットワークとして構築する OS X/iOS 用アプリケーションを構築する場合 開発時間の多くはオブジェクトを対象とする作業に費 やされます このオブジェクトは Objective-C の クラス のインスタンスですが クラスには Cocoa/Cocoa Touch に付属しているものと 独自に開発するものがあります 独自にクラスを開発する場合 まずはその仕様 すなわち 当該クラスのインスタンスが提供する 公開インターフェイスを記述することから始めます インターフェイスには 関係データをカプセル 化するための公開プロパティや メソッド群などがあります メソッド宣言は オブジェクトがどの ようなメッセージを受け付けるか を表すものです 呼び出し時に指定する引数に関する情報もここ で記述します その後 クラスを実装することになります その大部分を占めるのが インターフェ イスとして宣言した各メソッドの 実行可能なコードです 7

Objective-C についてはじめに 関連する章 : クラスの定義 (12 ページ ) オブジェクトの操作 (25 ページ ) デー タのカプセル化 (42 ページ ) カテゴリで既存のクラスを拡張する 既存のクラスにちょっとした機能を追加する場合 まったく新たにクラスを実装する代わりに カテゴリを定義し 特有の機能だけを追加定義する という方法があります カテゴリを使えば どのクラスにでもメソッドを追加できます NSStringのようなフレームワーククラスなど 実装を記述したソースコードがなくても構いません 一方 ソースコードが手元にある場合は クラス拡張の機能を使ってプロパティを追加する あるいは既存のプロパティの属性を変更することができます クラス拡張は一般に 同じソースファイル内 あるいはカスタムフレームワークの非公開の実装内で 非公開の動作を隠蔽するために使います 関連する章 : 既存のクラスのカスタマイズ (62 ページ ) プロトコルはメッセージのやり取り方法 ( 契約 ) を定義する Objective-Cで開発したアプリケーションでは 処理の多くが オブジェクトどうしがメッセージをやり取りする結果として起動されます このメッセージは 通常 クラスのインターフェイスとして メソッドを明示的に宣言することにより定義します ただし 特定のクラスに直接結びつけることなく 関連する一連のメソッドを定義できれば役に立つことがあります Objective-Cではこれを プロトコル を使って行います 定義されたメソッドは たとえばそのデリゲートに対して呼び出すことができます メソッドには必須のものとそうでないものがあります クラスには あるプロトコルに準拠している旨を宣言できます この場合 プロトコルで必須と宣言されているメソッドはすべて実装しなければなりません 関連する章 : プロトコルの使い方 (71 ページ ) 値やコレクションの多くは Objective-C のオブジェクトとして表す Objective-Cでは CocoaやCocoa Touchのクラスを使って値を表すことも少なくありません NSString クラスは文字列 NSNumberクラスは各種の数値 ( 整数 浮動小数点数など ) NSValueクラスはCの構造体など他の値を表すために使います 値はCの基本型 (int float charなど ) で表すことも可能です 8

Objective-C についてはじめに コレクションは通常 NSArray NSSet NSDictionary などといった コレクションクラスのインス タンスとして表します いずれも 他の Objective-C のオブジェクトを 所定のやり方で集めたもので す 関連する章 : 値とコレクション (79 ページ ) ブロックを使えばよくあるタスクを簡潔に記述できる ブロックとは CやObjective-C C++ の言語に組み込まれた機能で 処理の単位を表します あるひとかたまりのコードを 取り込んだ状態とともにカプセル化し 他のプログラミング言語におけるクロージャのように扱うことができます コレクションに対する反復処理 ( 列挙 ) 整列 テストなど よくあるタスクを簡潔に記述するために使います また GCD(Grand Central Dispatch) のような技術を用い タスクをスケジューリングして並行処理や非同期処理を行う処理も 記述しやすくなります 関連する章 : ブロックの使い方 (97 ページ ) エラーオブジェクトを使って実行時に発生した問題を処理する Objective-Cには例外処理の記述構文がありますが CocoaやCocoa Touchでは例外を プログラミング上の誤り ( 配列の境界を越えたアクセスなど アプリケーション出荷前に修正するべきもの ) を捕捉するためだけに使います その他のエラー状態 ( ディスク容量が足りない ウェブサービスにアクセスできないなど 実行時の問題を含む ) は NSErrorクラスのインスタンスで表します アプリケーションを開発する際には こういったエラーについても考慮し 問題が起こってもできるだけユーザが困らない処理方法を決めておかなければなりません 関連する章 : エラー処理 (111 ページ ) Objective-C のコードは所定の規約に基づいて記述する Objective-Cのコードを記述する際には さまざまなコーディング規約を頭に入れておかなければなりません たとえば メソッド名は先頭を小文字にし 複数の語から成る場合は dosomething dosomethingelse のようにキャメルケースで表す などといった規約です ただし重要なのは 単なる大文字 / 小文字の使い分けだけではありません コードはできるだけ読みやすくすること すなわち メソッド名はそれだけで処理内容が分かる しかも冗長すぎないものにすることが大切です 9

Objective-C について必要事項 ほかにも 言語やフレームワークの機能を活かすために必要な規約がいくつかあります たとえば キー値コーディング (KVC Key-Value Coding) キー値監視 (KVO Key-Value Observing) などの技 術を活用するためには プロパティのアクセサ名を 厳密な規約に従って決めなければなりません 関連する章 : コーディング規約 (116 ページ ) 必要事項 OS X/iOS 用アプリケーションの開発が初めてであれば まず Start Developing ios Apps Today または Start Developing Mac Apps Today に一通り目を通してから この資料を読んでください アプリケーション開発の流れを概観できます さらに 各章の末尾にある練習問題に取り組む際には Xcode の使い方に慣れておく必要があるでしょう XcodeはiOS/OS X 用アプリケーションの構築に用いる統合開発環境です コードを記述し ユーザインターフェイスを設計し 動作をテストし 問題箇所をデバッグする という一連の作業がこの上で可能です C あるいはこれをベースとしたJavaやC# などの言語にある程度慣れていれば望ましいのですが この資料では 制御構文などCの基本的な機能も コード例の形で説明しています RubyやPythonなど 他の高レベルプログラミング言語に関する知識があれば より深く理解できるでしょう 一般的なオブジェクト指向言語の原理も 特にObjective-Cに当てはまる事項についてはある程度説明しますが 基本的な概念について最小限の知識はあるものと想定しています こういった概念がよく分からない方は Concepts in Objective-C Programming の該当する章を読んでください 関連項目 この資料の記載内容は Xcode 4.4 以降を対象としたものです OS X v10.7 以降 あるいはiOS 5 以降で動作するアプリケーションを開発するものと想定します Xcodeについて詳しくは Xcode User Guide を参照してください 言語機能の有無については Objective-C Feature Availability Index を参照してください Objective-Cのアプリケーションは オブジェクトの寿命を判断するために参照カウントを使います 多くの場合この問題は コンパイラの自動参照カウント (ARC Automatic Reference Counting) 機能に委ねてしまって構いません ARCを活用できない状況の場合や 独自の方式でメモリを管理する旧来のコードを保守し あるいは新しい方式に書き直す手順については AdvancedMemoryManagement Programming Guide を参照してください 10

Objective-C について関連項目 Objective-Cは コンパイラの機能だけではなく 実行時システムを使って動的なオブジェクト指向の動作を実現しています 通常 Objective-Cがどのように実装されているか意識する必要はありませんが この実行時システムと直接やり取りすることは可能です Objective-C Runtime Programming Guide Objective-C Runtime Reference を参照してください 11

クラスの定義 OS X/iOS 用ソフトウェアを記述する場合 その時間の多くはオブジェクトを対象とする作業に費やされます Objective-Cのオブジェクトも 他のオブジェクト指向言語と同様に データとその振る舞いを組にしてパッケージ化したものです アプリケーションは オブジェクトが相互に接続された巨大な生態系として構築されます オブジェクトは相互に通信しあって 画面を表示する ユーザの入力に応答する 情報を保存する などといった具体的な問題に対処します OS X/iOS 用アプリケーション開発では あらゆる問題に対して一からオブジェクトを生成しなければならない ということはありません OS X 用にはCocoa ios 用にはCocoa Touchとして さまざまなオブジェクトの巨大なライブラリが用意されています 文字列や数値といった基本データ型 あるいはボタンやテーブルビューのようなユーザインターフェイス要素など 即座に使えるオブジェクトも少なくありません 一方 必要に応じて独自のコードを組み込み カスタマイズして使うよう設計されているものもあります アプリケーション開発プロセスには フレームワークが提供するオブジェクトをどのようにカスタマイズし あるいはどのように独自のオブジェクトと組み合わせて 必要な機能を実装するか という意思決定が必要です オブジェクト指向プログラミングの用語では オブジェクト とは クラス の インスタンス のことです この章では Objective-Cでクラスを定義する例を示します インターフェイスを宣言することにより クラスやそのインスタンスをどのように使うものと想定しているか を記述するのです インターフェイスとして クラスが受け取って応答できるメッセージを列挙します これに対応して クラスの実装 すなわち メッセージを受けたときに実行するコードも必要になります クラスはオブジェクトの 設計図 である クラスには ある型のオブジェクトに共通の振る舞いやプロパティを記述します たとえば文字列オブジェクト (Objective-CではNSStringクラスのインスタンス) の場合 オブジェクト内に保持している 文字列を表現するデータに対して 検証する 変換するなど さまざまな処理を行う手段をクラスが提供します 同様に 数値オブジェクトを表すクラス (NSNumber) には 保持してイル数値データに対して 別の数値型に変換するなどの手段が実装されています 同じ設計図からビルを何棟も建設すればすべて同じ構造になるのと同様に 同じクラスのインスタンスはすべて プロパティや振る舞いが共通になります たとえばNSStringのインスタンスは 内部に保持している文字列がそれぞれ違っても まったく同じように振る舞います 12

クラスの定義クラスはオブジェクトの 設計図 である 個々のオブジェクトは 所定の方法で使うよう設計されます 文字列オブジェクトはもちろんある特定の文字列を表すのですが 実際にこれを格納する 正確な仕組みは知らなくても構いません また オブジェクト自身が文字列を直接操作する内部的な振る舞いも 知っている必要はありません ただし 特定の文字が含まれるかどうか調べる 文字をすべて大文字に変換した新しいオブジェクトを生成する などといった操作を行う方法は知っておく必要があります Objective-Cではクラスインターフェイスとして その型 ( クラス ) に属するオブジェクトを 他のオブジェクトがどのように使うものと想定しているか 正確に指定できるようになっています すなわち 当該クラスのインスタンスと外部世界との 公開インターフェイスを定義するのです オブジェクトが表す値には 可変か不変か という区別がある クラスの中には 不変なオブジェクトを表すものがあります 内部の値が生成時に決まってしまい 後で変更することはできないオブジェクトです Objective-Cの場合 NSStringやNSNumberのような基本オブジェクトはいずれも不変です 別の数値を表現するためには 新たにNSNumberのインスタンスを生成しなければなりません 不変クラスに対応して 可変なクラスが用意されているものもあります ネットワーク経由で受け取った文字列を順次追記していくなど 実行時に文字列の中身を変える必要がある場合は NSMutableStringクラスのインスタンスを使ってください このクラスのインスタンスは NSString オブジェクトとまったく同じように振る舞うほか 文字列を書き替える機能が追加されています NSStringとNSMutableStringは異なるクラスですが 似た点もたくさんあります このようなクラスは それぞれ独立に一から実装するよりも 継承 機能を活用するとよいでしょう クラスは他のクラスの性質を継承することができる 生物分類学では自然界の生物を 種 属 科などに分類します この分類は階層的になっています いくつかの種が同じ属に含まれ さらにいくつかの属が同じ科に含まれる という関係です たとえばゴリラ ヒト オランウータンには さまざまな類似性があります それぞれ異なる種に含まれ 属 族 亜科も異なりますが 同じ ヒト 科に属しています ( 図 1-1 (13 ページ ) を参照 ) 図 1-1 種と種との生物分類学的な関係 オブジェクト指向プログラミングの世界では オブジェクトは階層グループとして分類されます 階層レベルごとに ( 種 属のような ) 異なる語を取り入れるのではなく 単にオブジェクトが属するクラスを階層分けして管理するようになっています ヒトがヒト科の一員としていくつかの特性を継承しているように クラス ( 子 ) は別のクラス ( 親 ) から機能を継承できるようになっています 13

クラスの定義クラスはオブジェクトの 設計図 である この場合子クラスは 親クラスに定義されている振る舞いやプロパティをすべて受け継ぎます その上で 独自の振る舞いやプロパティを追加定義したり 親の振る舞いを変更 ( オーバーライド ) したりすることもあります Objective-Cの文字列クラスの場合 NSMutableStringのクラス記述には NSStringを継承する旨が記述されています ( 図 1-2 (14 ページ ) を参照 ) したがって ある文字が含まれるかどうか調べる 大文字に変換した新しい文字列を生成するなど NSStringの機能はすべてNSMutableStringでも使えます ただしNSMutableStringには 文字列の一部や特定の文字を追記する 挿入する 置換する 削除するなどのメソッドが追加されています 図 1-2 NSMutableString クラスの継承関係 ルートクラスには基本機能が実装されている あらゆる生物が共有する 生きていく上で欠かせない基本的な特性があるように Objective-Cのあらゆるオブジェクトに共通の機能がいくつかあります Objective-Cのオブジェクトが 他のクラスのインスタンスと連携する際には そのクラスにも基本的な特性や振る舞いが備わっていると想定します そこでObjective-Cでは NSObjectというルートクラスを定義し 他の大部分のクラスがこれを直接間接に継承するようにしています したがってどのオブジェクトとでも NSObjectクラスに記述された 最低限の基本機能を使ってやり取りできるのです 独自のクラスを定義する場合 少なくともNSObjectの機能は継承してください 一般には Cocoa/Cocoa Touchに付属の 必要な機能を提供するオブジェクトを探し これを継承するとよいでしょう たとえばiOSアプリケーションで使う独自のボタンを定義する場合 UIButtonクラスではカスタマイズ可能な属性が足りず要件を満たすことができなければ UIButtonではなくNSObjectを継承する形で新しいクラスを定義することになるでしょう NSObjectしか継承しなかった場合 ユーザが想定するようなボタンの振る舞いを実現するためには UIButtonに定義されているのと同じ複雑な視覚効果や通信の機能を すべて重ねて実装しなければなりません しかも UIButtonを継承していれば 将来 UIButtonの内部的な振る舞いに改善が施されたり バグが修復されたりしたとき 自動的にその恩恵を受けることができます 14

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している UIButtonクラス自身はUIControlを継承しています これは ios 上のユーザインターフェイス部品すべてに共通の 基本的な振る舞いを記述したものです さらにUIControlクラスは 画面に表示されるオブジェクトすべてに共通の機能を提供する UIViewを継承しています UIViewはUIResponder から タップ ジェスチャ シェイクなどのユーザ入力に応答する機能を継承しています 最後に継承階層のルートに到って UIResponderはNSObjectを継承します ( 図 1-3 (15 ページ ) を参照 ) 図 1-3 UIButton のクラス階層 このような継承の連鎖があるので UIButtonのカスタムサブクラスはUIButton 自身だけでなく その上位にある各スーパークラスからもさまざまな機能を継承します したがって ボタンのように振る舞い 画面上に自分自身を表示し ユーザ入力に応答し Cocoa Touchの他の基本的なオブジェクトと通信するという 多くの能力を備えたオブジェクトのクラスを実装できることになります 重要なのは あるクラスにどのような機能があるかを調べる際 継承の連鎖があることを頭に入れておくことです CocoaやCocoa Touchに付属するクラスのリファレンス資料は どのクラスからも スーパークラスを順次たどって参照できるようになっています あるクラスのインターフェイスやリファレンス資料に探している機能が見つからなくても スーパークラスを順次たどっていけば 定義や解説が見つかるかもしれません クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している オブジェクト指向プログラミングにはさまざまな利点がありますが そのひとつは既に説明しまし た クラスを使うためには そのインスタンスと互いにやり取りする方法さえ分かればよい という ことです さらに言えば オブジェクトはその内部の実装詳細を 外に見せないように設計しなけれ ばなりません たとえば ios アプリケーションで標準の UIButton を使うと どのようにピクセルが操作され 画面 にボタンが現れるかを意識する必要はなくなります 知っておかなければならないのは ラベルや色 などの属性を変更できることだけで あとは画面 ( 視覚インターフェイス ) に追加すれば適切に表示 され 想定通りに動作するはずです 独自のクラスを定義する際には まずこのような公開の属性や振る舞いを決めなければなりません 外部からアクセスできる属性にはどのようなものがあるでしょうか? その属性は変更できるようにし ますか? 外部のオブジェクトはどのようにして 当該クラスのインスタンスと通信するのでしょう か? 15

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している このような情報が クラスのインターフェイスになります 当該クラスのインスタンスと 他のオブジェクトがやり取りをする方法を定義するのです 公開するインターフェイスは クラスの内部的な振る舞い すなわち実装とは分けて記述します Objective-Cでは通常 インターフェイスと実装を別々のファイルに記述して インターフェイスだけを公開できるようにします 基本的な構文 Objective-C では 次のような構文でクラスのインターフェイスを宣言します @interface SimpleClass : NSObject @end この例では SimpleClassという名前のクラスを NSObjectを継承するものとして宣言しています 公開のプロパティや振る舞いを @interface 宣言内に定義します この例では スーパークラスのプロパティや振る舞いにまだ何も追加していないので SimpleClassのインスタンスに対しては NSObjectから継承した機能しか使えません プロパティはオブジェクトの値に対するアクセスを制御する オブジェクトには多くの場合 公開アクセスを意図したプロパティがあります たとえば住民登録アプリケーションで 人材を表すクラスの定義には その人の姓名を表す文字列のプロパティが必要になるでしょう このようなプロパティの宣言は 次のように インターフェイスの内部に追加します @interface Person : NSObject @property NSString *firstname; @property NSString *lastname; @end この例で Personクラスには2つの公開プロパティが宣言されており どちらもNSStringクラスのインスタンスです どちらもObjective-Cのオブジェクトなので Cのポインタであることを表すアスタリスクを置いています これはCの変数宣言と同じく文でもあり したがって末尾にセミコロンが必要です 16

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している さらに 誕生年を表すプロパティも追加してみましょう こうすれば 名前だけでなく生年を基準に 整列することも可能になります この場合 数値オブジェクトでプロパティ値を表すことも考えられ ます @property NSNumber *yearofbirth; ただし 単なる数値を格納するだけであれば 過剰仕様と言えるかもしれません 代替として 整数 などのスカラ値を格納する C の基本型を使うことも可能です @property int yearofbirth; プロパティ属性はデータのアクセス性やストレージ管理方法を表すこれまでに例示したプロパティは 外部に対して完全に公開するものとして宣言しました 他のオブジェクトが自由にプロパティ値を取得 変更できます 外部からの変更を許さないプロパティが必要になることもあります 現実世界では 人が改名しようとすれば膨大な書類作業が必要になるでしょう 公的な住民登録アプリケーションでは 人の名前を表す公開プロパティは読み取り専用にするべきかもしれません 変更が必要な場合は 変更要求を検証して承認か却下かを判断する 中継オブジェクトを介して行うようにします Objective-Cのプロパティ宣言にはプロパティ属性を指定できます 読み取り専用という属性もそのひとつです 公的な住民登録アプリケーションで Personクラスのインターフェイスは次のようになるでしょう @interface Person : NSObject @property (readonly) NSString *firstname; @property (readonly) NSString *lastname; @end プロパティ属性は @property キーワードの後に 括弧で囲んで指定します 詳しくは 公開プロパ ティを宣言してデータを公開する (42 ページ ) を参照してください メソッド宣言はオブジェクトが受け付け可能なメッセージを表す ここまでの例は 典型的なモデルオブジェクト すなわち 主としてデータのカプセル化を意図したオブジェクトを記述するクラスでした Personクラスの場合 宣言した2つのプロパティにアクセスする以上の機能は必要ないかもしれません ただし多くのクラスには 宣言したプロパティ以外に その振る舞いを追加する必要があります 17

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している Objective-Cのソフトウェアはオブジェクトの巨大なネットワークから構築されるので オブジェクトどうしが相互にメッセージをやり取りできる ということが重要です Objective-Cの用語では あるオブジェクトが他のオブジェクトに メッセージを送信 することにより 当該オブジェクトのメソッドを呼び出す と言います Objective-Cのメソッドは 考え方としてはCその他のプログラミング言語の標準関数に似ていますが 構文はまったく異なります Cの関数宣言は次のようになります void SomeFunction(); これと同等な Objective-C のメソッド宣言は次のようになります - (void)somemethod; この場合 メソッドには引数がありません Cのvoidキーワードを 括弧で囲んで宣言の先頭に置くことにより 処理が終了しても値を返さないことを表します メソッド名の前にあるマイナス符号 (-) は これがインスタンスメソッド すなわち このクラスのインスタンスに対して呼び出せるメソッドであることを表します クラス自身に対して呼び出す クラスメソッド ( Objective-Cではクラスもオブジェクトの一種である (23 ページ ) を参照 ) とは違います Objective-Cのクラスインターフェイス内に記述するメソッド宣言も Cの関数プロトタイプと同様に 文 であるので 末尾にセミコロンが必要です メソッドは引数を取ることができる 引数をいくつか取るメソッドを宣言する構文は 典型的な C の関数とはかなり違ったものになります C の関数の場合 引数は括弧内に 次のように記述します void SomeFunction(SomeType value); Objective-C のメソッド定義では 引数仕様も名前の一部であって コロンを使って次のように表しま す - (void)somemethodwithvalue:(sometype)value; 戻り値型と同様に 引数の型も括弧で囲み 標準 C の型キャストのような書き方で指定します 18

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している 引数を複数与える場合 構文はやはり C の関数とはまったく異なります C の関数の場合 引数をす べて括弧で囲み コンマで区切って表します 一方 Objective-C の場合 2 つの引数を取るメソッド の宣言は次のようになります - (void)somemethodwithfirstvalue:(sometype)value1 secondvalue:(anothertype)value2; この例でvalue1とvalue2は メソッドが呼び出されたときに実行される実装コードで 渡された値に変数のようにアクセスするために使う名前です プログラミング言語の中には いわゆる名前付き引数が使えるものがありますが Objective-Cはこれに当たらないことに注意してください メソッド呼び出しにおける引数の順序は メソッド宣言のそれと一致しなければなりません 実のところ メソッド宣言の secondvalue: 部分は メソッド名の一部なのです somemethodwithfirstvalue:secondvalue: これは Objective-C のプログラムを読みやすくする特徴のひとつです メソッド呼び出し時に渡す値 は メソッド名の対応する部分のすぐ後にインラインで記述できるからです ( メソッドの引数とし てオブジェクトを渡すことができる (27 ページ ) を参照 ) 注意 : 上の例で 値の名前であるvalue1とvalue2の部分は 厳密に言うとメソッド宣言の一部ではありません したがって 実装コードを記述する際には まったく同じ名前でなくても構いません 要件はシグニチャが一致すること すなわち メソッド名と 引数や戻り値の型が一致することだけです たとえば次のメソッドは 先に示したものと同じシグニチャです - (void)somemethodwithfirstvalue:(sometype)info1 secondvalue:(anothertype)info2; 一方 次のメソッドはシグニチャが違っています - (void)somemethodwithfirstvalue:(sometype)info1 anothervalue:(anothertype)info2; - (void)somemethodwithfirstvalue:(sometype)info1 secondvalue:(yetanothertype)info2; 19

クラスの定義クラスのインターフェイスは そのクラスとどのようなやり取りが可能か を定義している クラス名は一意でなければならない 各クラスの名前は アプリケーション全体で一意でなければなりません これはインクルードして用いるライブラリやフレームワークも含みます プロジェクトに既に存在するクラスと同名の新しいクラスを定義しようとしても コンパイル時にエラーになります そこで 3 文字以上のプレフィックスを決めておき 自分が定義するクラスの名前すべてにつけるよう推奨します 開発中のアプリケーションにちなんだ文字列でも 再利用可能なコードを集めたフレームワークの名前や 単に開発者の名前の頭文字を取ったものでも構いません 以下 この資料で示す例では クラス名に次のようなプレフィックスを使います @interface XYZPerson : NSObject @property (readonly) NSString *firstname; @property (readonly) NSString *lastname; @end 歴史にまつわる注記 : NSというプレフィックスがついたクラスがよく目につきますが どうしてでしょうか その理由を説明するためには CocoaやCocoa Touchの歴史を遡らなければなりません Cocoaはもともと NeXTStepというオペレーティングシステム上で動作するアプリケーションを構築するためのフレームワークでした 1996 年 AppleがNeXTを買収した結果 NeXTStepのかなりの部分がOS Xに組み込まれ 既存のクラス名もそのまま残りました Cocoa Touch は CocoaをiOS 用に移植したものから始まったため 両者に共通のクラスも多いのですが それぞれのプラットフォームに固有のクラスもあります NSや (iosのユーザインターフェイス要素のクラスに用いる)uiのような2 文字のプレフィックスは Appleが予約しています これに対し メソッド名やプロパティ名は 定義されるクラス内でのみ一意でなければなりません アプリケーション全体で一意的でなければならないCの関数名とは違い Objective-Cでは クラスが違えば同じ名前のメソッドがあっても問題なく むしろ望ましいと考えられる場合も少なくありません もちろん 同じクラス定義内に 二重にメソッドを定義することはできません 一方 親クラスから継承したメソッドをオーバーライドする場合は 完全に同じ名前を使う必要があります メソッドと同様に オブジェクトのプロパティやインスタンス変数 ( プロパティの多くはインスタンス変数を使って実装されている (45 ページ ) を参照 ) も 定義されるクラス内で一意であれば構いません もっとも 大域変数の名前は アプリケーションやプロジェクト全体で一意である必要があります 名前の付け方にはほかにも規約や推奨事項があります ( コーディング規約 (116 ページ ) を参照 ) 20

クラスの定義クラスの実装は その内部的な振る舞いを規定する クラスの実装は その内部的な振る舞いを規定する クラスのインターフェイス ( 公開するプロパティやメソッド ) を定義したので 次にクラスの振る舞いを実装するコードを記述します 先に説明したように クラスのインターフェイスは通常 それ専用のファイルに記述します これをヘッダファイルとも呼び ファイル拡張子は通常.h とします 一方 クラスの実装は ファイル拡張子を.m としたソースファイルに記述します インターフェイスをヘッダファイルに定義し 実装コードをソースファイルに記述してコンパイルする際には 事前にヘッダファイルを読み込んで分析するよう コンパイラに伝えなければなりません Objective-Cにはその目的で #importというプリプロセッサ指示子があります Cの #includeという指示子に似ていますが コンパイル中に1 度しかインクルードしないようになっています なお プリプロセッサ指示子はCの文と違って 末尾にセミコロンを置かないことに注意してください 基本的な構文 クラスを実装するための基本的な構文は次の通りです #import "XYZPerson.h" @implementation XYZPerson @end クラスのインターフェイスに宣言したメソッドはすべて このファイルに実装しなければなりませ ん メソッドを実装する 例として メソッドが 1 つだけの単純なクラスを考えましょう @interface XYZPerson : NSObject - (void)sayhello; @end 実装の記述は次のようになります 21

クラスの定義クラスの実装は その内部的な振る舞いを規定する #import "XYZPerson.h" @implementation XYZPerson - (void)sayhello { NSLog(@"Hello, World!"); @end この例では 関数 NSLog() を使ってコンソールにメッセージを出力しています 標準 Cライブラリの関数 printf() のように可変個の引数を取ることができますが 第 1 引数はObjective-Cの文字列でなければなりません メソッドの実装は Cの関数定義と同様に 波括弧で囲んだ範囲にコードを記述します また メソッド名 引数や戻り値の型は プロトタイプと一致しなければなりません Objective-Cは Cと同様に大文字と小文字を区別します - (void)sayhello { したがってこのように記述すると 先に示したsayHelloメソッドとは別のものとして扱われます 一般に メソッド名の先頭は小文字とします Objective-Cの規約では 典型的なCの関数に比べ その内容をよく説明するメソッド名が推奨されています メソッド名が複数の語から成る場合は 読みやすいよう キャメルケース (2つめ以降の各語の先頭のみ大文字で表す書き方) にしてください また 空白類の置き方は自由度が高くなっています ブロック内のコードは タブや空白で字下げするのが通例です また 開き波括弧は次のように独立した行にすることも珍しくありません - (void)sayhello { NSLog(@"Hello, World!"); OS X/iOSソフトウェア開発用にAppleが提供する統合開発環境 (IDE Integrated Development Environment) Xcodeには 環境設定に基づき 自動的にコードを字下げする機能が組み込まれています 詳しくは Xcode Workspace Guide の Changing the Indent and Tab Width in Xcode Workspace Guide を参照してください 22

クラスの定義 Objective-C ではクラスもオブジェクトの一種である メソッドの実装例は 次の章 オブジェクトの操作 (25 ページ ) にいくつも出てきます Objective-C ではクラスもオブジェクトの一種である Objective-Cでは クラスそれ自身もClassという不透過型のオブジェクトです インスタンスについては先にプロパティの宣言構文を示しましたが クラスにはプロパティを定義できません ただしメッセージを受け取ることは可能です クラスメソッドの典型的な用途として ファクトリメソッドというものがあります インスタンス生成は一般に 割り当てと初期化のメソッドを組み合わせて行いますが その代替として使えます ( オブジェクトは動的に生成される (32 ページ ) を参照 ) たとえばNSStringクラスには 空の文字列オブジェクトを生成する 指定した文字列を使って初期化した文字列オブジェクトを生成する などさまざまなファクトリメソッドがあります + (id)string; + (id)stringwithstring:(nsstring *)astring; + (id)stringwithformat:(nsstring *)format, ; + (id)stringwithcontentsoffile:(nsstring *)path encoding:(nsstringencoding)enc error:(nserror **)error; + (id)stringwithcstring:(const char *)cstring encoding:(nsstringencoding)enc; このように クラスメソッドにはプラス記号 (+) をつけて マイナス記号 (-) のインスタンスメソッドと区別します クラスメソッドのプロトタイプは インスタンスメソッドの場合と同様に クラスインターフェイス内に記述できます 実装も同様に クラスの @implementationブロック内に記述します 23

クラスの定義練習問題 練習問題 注意 : 各章末の練習問題に取り組む際には Xcodeのプロジェクトを作成しておくとよいでしょう 実際にコンパイルし 誤りが出ないことを確認できます Xcodeの New Project テンプレートウインドウで OS Xアプリケーション用のプロジェクトテンプレートをもとに Command Line Tool を作成してください プロジェクトの種類を訊ねられるので Foundation を選択します 1. Xcodeの New File テンプレートウインドウで インターフェイスファイル 実装ファイルを作成してください クラス名はXYZPersonで NSObjectを継承するものとします 2. XYZPersonクラスインターフェイスにプロパティを追加してください 姓 名前 誕生日 (NSDate クラスで表す ) の3つです 3. sayhelloメソッドを宣言し ( この章の本文に示したように ) 実装してください 4. person というファクトリメソッドの宣言を追加してください 実装は次の章を読んでからで構いません 注意 : 実装を記述していないので 実際にコンパイルしようとすると Incomplete implementation という警告が現れます 24

オブジェクトの操作 Objective-Cアプリケーションの処理の多くは オブジェクトの 生態系 におけるメッセージのやり取りという形で起こります オブジェクトには Cocoa/Cocoa Touchに付属するクラスのインスタンスと 独自に開発したクラスのインスタンスがあります 前章では クラスのインターフェイスや実装を定義する構文を説明しました メソッドの実装 すなわち メッセージに応答して実行されるコードの構文についても述べました この章では オブジェクトにメッセージを送信する手順を説明します また Objective-Cの動的機能 すなわち 動的型付けや 起動するメソッドを実行時に決めるための機構についても解説します オブジェクトを使うためには まずこれを適切に生成する必要があります プロパティを管理するためのメモリ領域を割り当て 初期設定する という処理になります この章では 割り当てと初期化という2つのメソッドを入れ子にして記述し オブジェクトを適切に生成する方法を解説します オブジェクトはメッセージを送受信する Objective-C には オブジェクト間でメッセージを送信する方法が何通りかありますが 最も一般的な のは 次のように角括弧を用いた基本的な構文です [someobject dosomething]; 左側に記述したもの ( この例では someobject ) がメッセージの受け手です 右側の dosomething は この受け手に対して呼び出すメソッドの名前です したがって 上記のコードを実行すると someobjectに対してdosomethingというメッセージが送信されることになります 前章で クラスのインターフェイスの定義方法を説明しました @interface XYZPerson : NSObject - (void)sayhello; @end また 当該クラスの実装は次のようになります 25

オブジェクトの操作オブジェクトはメッセージを送受信する @implementation XYZPerson - (void)sayhello { NSLog(@"Hello, world!"); @end 注意 : この例ではObjective-Cの文字列リテラル @"Hello, world!" を使っています 文字列はObjective-Cのクラス型のひとつで リテラル構文という簡潔な書き方で生成できます @"Hello, world!" という記述は 概念的には Hello, world! という文字列を表す Objective-Cの文字列オブジェクト と同等です リテラルやオブジェクトの生成については この章の後半 オブジェクトは動的に生成される (32 ページ ) を参照してください XYZPerson オブジェクトが生成済みであるとすれば 次のように記述することにより このオブジェ クトに sayhello メッセージを送信できます [someperson sayhello]; Objective-C のメッセージ送信は 考え方としては C の関数呼び出しと似ています 図 2-1 (26 ページ ) に sayhello メッセージを送信したときの 実質的なプログラムの流れを示します 図 2-1 メッセージ送信時の 基本的なプログラムの流れ メッセージの受け手を指定するためには オブジェクトを参照するために使うポインタについて理解 しておかなければなりません ポインタを使ってオブジェクトを追跡管理する CやObjective-Cでは 値を追跡管理するために変数を使います これは多くのプログラミング言語に共通です 標準 Cには 整数 浮動小数点数 文字など 基本的なスカラ変数型がいくつか定義されています その型の変数は次のように宣言し 初期値を設定します int someinteger = 42; float somefloatingpointnumber = 3.14f; 26

オブジェクトの操作オブジェクトはメッセージを送受信する メソッドや関数の内部で宣言する局所変数は 次のように記述します - (void)mymethod { int someinteger = 42; この変数が有効なのは 定義したメソッドの内部だけです この例でsomeIntegerは mymethod 内の局所変数として宣言されています メソッドの閉じ波括弧に制御が到達すると someintegerにはアクセスできなくなります 局所スカラ変数 (int float など ) が消えれば その値もなくなってしまいます 一方 Objective-Cのオブジェクトは 少し違う方式で割り当てられます オブジェクトは通常 メソッドの範囲 ( スコープ ) よりも長い寿命を保たなければなりません 実際 オブジェクトを追跡管理する目的でメソッド内に宣言した局所変数よりも オブジェクト自身の方が寿命が長いことも多いので オブジェクトのメモリ領域は動的に割り当て 解放するようになっています 注意 : スタックやヒープという用語を知っているならば 局所変数はスタックに オブジェ クトはヒープ上に確保される と言えば分かりやすいでしょう そのためには ( メモリ上のアドレスを保持する )C のポインタで 次のようにメモリ上の場所を追 跡する必要があります - (void)mymethod { NSString *mystring = // get a string from somewhere... [...] ポインタ変数 mystring( アスタリスクはこれがポインタであることを示す ) のスコープはmyMethod の内部に限られますが これが指す文字列オブジェクト自身の寿命は このスコープよりも広い範囲であることも考えられます 元から存在しているか あるいは別のメソッドであらかじめこのオブジェクトに渡しておく必要があるかもしれません メソッドの引数としてオブジェクトを渡すことができる メッセージを送信する際 オブジェクトを渡す必要がある場合は 引数としてそのポインタを指定し ます 前章で 引数を 1 つ取るメソッドの宣言構文を説明しました 27

オブジェクトの操作オブジェクトはメッセージを送受信する - (void)somemethodwithvalue:(sometype)value; 文字列オブジェクトを引数とするメソッドの宣言は したがって次のようになります - (void)saysomething:(nsstring *)greeting; saysomething: メソッドの実装は たとえば次のようになります - (void)saysomething:(nsstring *)greeting { NSLog(@"%@", greeting); greeting ポインタは局所変数のように振る舞い そのスコープは saysomething: メソッド内に限ら れます もちろん文字列オブジェクト自身は このメソッドの呼び出し前にも また メソッドの処 理が終了した後も存在します 注意 : NSLog() は書式指定文字列を使って 字句をどのように置換して出力するかを指定します これはCの標準ライブラリ関数 printf() と同じ方式です コンソールに出力されるのは 書式指定文字列 ( 第 1 引数 ) に 値 ( 第 2 引数以降 ) を挿入して変形した結果です Objective-Cでは オブジェクトを表す %@ という書式指定子が追加されています 実行時に 渡されたオブジェクトのdescriptionWithLocale: メソッド ( もしあれば ) または descriptionメソッドを呼び出し その結果を埋め込む旨の指定です descriptionメソッドはnsobjectに実装されており 当該オブジェクトのクラス名とメモリ上のアドレスから成る文字列を返しますが Cocoa/Cocoa Touchのクラスの多くは より有用な情報を返すようにオーバーライドしています NSStringの場合 descriptionメソッドは 単に自分自身が表す文字列を返します NSLog() やNSStringクラスの書式指定子について詳しくは String Format Specifiers を参照してください メソッドは値を返すことができる メソッドは 引数として値を渡すだけでなく 値を返すことも可能です これまでに見てきたメソッドは 戻り値型がvoidとなっています Cのキーワード void は メソッドが何も返さないことを表します 戻り値型としてintを指定すれば そのメソッドはスカラの整数値を返すことになります 28

オブジェクトの操作オブジェクトはメッセージを送受信する - (int)magicnumber; メソッドの実装では C の return 文を使って次のように 処理終了後に返す値を指定します - (int)magicnumber { return 42; メソッドから返される値は 単に無視してもまったく問題ありません この場合 magicnumber メ ソッドは値を返す以外になんら有用な処理を行いませんが 次のように呼び出すこと自体は構わない のです [someobject magicnumber]; 戻り値を保持しておく必要がある場合は 次のように変数を宣言し メソッドの戻り値を代入してく ださい int interestingnumber = [someobject magicnumber]; 同様にして オブジェクトを戻り値にすることも可能です たとえば NSString クラスには uppercasestring というメソッドがあります - (NSString *)uppercasestring; スカラ値を返すメソッドと使い方は同じですが 結果を保持するためにはポインタ変数が必要です NSString *teststring = @"Hello, world!"; NSString *revisedstring = [teststring uppercasestring]; このメソッドを呼び出すと revisedstring は HELLO WORLD! という文字列を表す NSString オ ブジェクトを指すようになります オブジェクトを返すメソッドの実装は次のようになります - (NSString *)magicstring { NSString *stringtoreturn = // create an interesting string... 29

オブジェクトの操作オブジェクトはメッセージを送受信する return stringtoreturn; 呼び出し元に制御が戻ると stringtoreturnポインタは消えてしまいますが 戻り値として返した文字列オブジェクトは存在し続けることに注意してください ここで メモリ管理に関して いくつか考慮するべき事項があります ( ヒープに生成された ) 戻り値オブジェクトは メソッドの呼び出し元が使っている間は存在し続ける必要がありますが だからといって永遠に存在し続けると メモリリークに陥ってしまいます 多くの場合この問題は Objective-C コンパイラの自動参照カウント (ARC Automatic Reference Counting) 機能に委ねてしまって構いません オブジェクトは自分自身にメッセージを送信できる メソッドの実装コードからは self という外部からは見えない値にアクセスできます 概念的には selfは このメッセージを受信したオブジェクト を表します これはポインタで その点では上記のgreetingと同じように扱えますが メッセージを受信したオブジェクト自身に対してメソッド呼び出しをするために使います XYZPersonクラスのsayHelloメソッドの実装をリファクタリングしてみましょう 上記の saysomething: メソッドを使ってNSLog() の呼び出し部分を分離するのです こうしておけば たとえばsayGoodbyeというメソッドを追加し どちらも実際の出力処理はsaySomething: メソッドに委ねる という構成が可能になります 後になって ログではなく画面上のテキストフィールドに文字列を表示することになっても saysomething: メソッドを修正するだけで済みます 2つのメソッドを個別に修正する必要はありません この新しい実装では 次のようにselfを使って 当該オブジェクトにメッセージを送信します @implementation XYZPerson - (void)sayhello { [self saysomething:@"hello, world!"]; - (void)saysomething:(nsstring *)greeting { NSLog(@"%@", greeting); @end 30

オブジェクトの操作オブジェクトはメッセージを送受信する XYZPerson オブジェクトに sayhello メッセージを送信して この新しい実装を呼び出した場合 処 理の流れは図 2-2 (31 ページ ) のようになります 図 2-2 self にメッセージを送信したときの プログラムの流れ オブジェクトはスーパークラスに実装されたメソッドを呼び出すことができる もうひとつ重要なキーワードとして Objective-C には super というものがあります メッセージ を super に送信すると 継承の連鎖をたどって スーパークラスに定義されたメソッドの実装を呼び 出すことができます super が最も多用されるのは メソッドをオーバーライドするコードにおいて です たとえば 叫ぶ人 を表す新しいクラスを考えましょう この人は挨拶がすべて大文字で表示されま す XYZPerson クラスを丸ごと複製し 大文字で出力するよう各メソッドを修正する という方法も 考えられます しかし最も簡単なのは XYZPerson を継承する派生クラスを定義し saysomething: メソッドだけを 大文字に変換して出力するようオーバーライドする という方法です @interface XYZShoutingPerson : XYZPerson @end @implementation XYZShoutingPerson - (void)saysomething:(nsstring *)greeting { NSString *uppercasegreeting = [greeting uppercasestring]; NSLog(@"%@", uppercasegreeting); @end この実装例では もうひとつ文字列ポインタ uppercasegreeting を宣言し 元の greeting オブジェ クトに uppercasestring メッセージを送信して返された値を代入しています これは 先に説明した ように 元の文字列の各文字を大文字に変換し 新たに構築した文字列オブジェクトです 31

オブジェクトの操作オブジェクトは動的に生成される sayhelloはxyzpersonに実装されていますが XYZShoutingPersonはXYZPersonの派生クラスとして定義されているので sayhelloオブジェクトに対してもxyzshoutingpersonを呼び出すことができます XYZShoutingPersonに対してsayHelloを呼び出すと [self saysomething:...] が実行されますが ここで実際に呼び出されるのはオーバーライドされた実装の方なので 大文字の表示になります プログラムの流れを図 2-3 (32 ページ ) に示します 図 2-3 オーバーライドしたメソッドを呼び出す場合のプログラムの流れ ただし新しい実装は 理想的とは言えません 後になってXYZPersonのsaySomething: の実装を変更し NSLog() を使わず画面上に出力することになった場合 XYZShoutingPersonの実装も修正しなければならないからです より優れているのは XYZShoutingPersonのsaySomething: が スーパークラス (XYZPerson) の実装を使って挨拶文を出力する という方法です @implementation XYZShoutingPerson - (void)saysomething:(nsstring *)greeting { NSString *uppercasegreeting = [greeting uppercasestring]; [super saysomething:uppercasegreeting]; @end XYZShoutingPerson オブジェクトに sayhello メッセージを送ったときのプログラムの流れは 図 2-4 (32 ページ ) のように変わります 図 2-4 super にメッセージを送るようにしたときのプログラムの流れ オブジェクトは動的に生成される この章で既に説明したように Objective-Cのオブジェクトを格納するメモリ領域は動的に確保されます オブジェクトを生成するためにまず必要なのはメモリ領域の割り当てです 当該オブジェクトのクラスに定義されたプロパティだけでなく 継承の連鎖をたどって 各スーパークラスに定義されているプロパティの領域も確保する必要があります ルートクラスであるNSObjectには この処理を実行する allocというクラスメソッドがあります + (id)alloc; 32

オブジェクトの操作オブジェクトは動的に生成される このメソッドの戻り値型がidであることに注意してください これは 何らかのオブジェクト を意味する Objective-Cの特別なキーワードです (NSObject *) と同様にオブジェクトを指すポインタですが アスタリスクが必要ないという点で特別です これについては節を改めて説明します ( Objective-Cは動的言語である (36 ページ ) を参照 ) allocメソッドにはもうひとつ重要な役割があります プロパティ用に割り当てたメモリ領域に0を埋めて初期化することです これにより たまたまその領域に残っていた ごみ の値になってしまう という問題は避けられますが これだけではオブジェクトの初期化として不充分です そこで allocだけでなく やはりinitのメソッドであるNSObjectを呼び出します - (id)init; initメソッドには オブジェクト生成時に 各プロパティに適切な初期値を与える という役割があります ( 詳しくは次の章で説明 ) なお initの戻り値型もidとなっています あるメソッドがオブジェクトポインタを返せば これに対して別のメッセージを送るという形で入れ子にし 複数のメソッド呼び出しを組み合わせてひとつの文にすることができます オブジェクトの領域割り当てと初期化は 次のように allocの呼び出しをの内側 initに入れ子にして記述するとよいでしょう NSObject *newobject = [[NSObject alloc] init]; この例では 変数 newobject が 新たに生成された NSObject のインスタンスを指すようになります 33

オブジェクトの操作オブジェクトは動的に生成される 内側の呼び出しから順に実行されるので 最初にNSObjectクラスに対してallocメソッドを呼び出して メモリ領域を割り当てたNSObjectのインスタンスを得ます 次に このオブジェクトに対して initメッセージを送ると その結果がnewObjectポインタに返されます ( 図 2-5 (34 ページ ) を参照 ) 図 2-5 alloc メッセージと init メッセージが入れ子になっている様子 注意 : initがallocで生成したのとは別のオブジェクトを返すこともあるので 上記のように呼び出しを入れ子にする記述が最も優れています オブジェクトを初期化するが その結果をポインタに代入し直さない という書き方は避けてください たとえば次のような記述です NSObject *someobject = [NSObject alloc]; [someobject init]; init が別のオブジェクトを返す実装であるとすれば ポインタが指すのは 割り当てただ けで初期化を施していない領域になってしまいます 初期化メソッドは引数を取ることができる オブジェクトには 指定した値で初期化しなければならないものがあります たとえばNSNumberオブジェクトは それが表す数値を指定して生成しなければなりません NSNumberクラスには 次のようにいくつか初期化メソッドが定義されています - (id)initwithbool:(bool)value; - (id)initwithfloat:(float)value; - (id)initwithint:(int)value; - (id)initwithlong:(long)value; 引数を取る初期化メソッドも 引数なしの init メソッドと同じように呼び出します NSNumber オブ ジェクトを割り当て 初期化するコードは たとえば次のようになります NSNumber *magicnumber = [[NSNumber alloc] initwithint:42]; 34

オブジェクトの操作オブジェクトは動的に生成される ファクトリメソッドは 割り当てと初期化を記述する代替として使える 前章で説明したように クラスにファクトリメソッドを定義することもできます これは alloc] init] という記述に代わるものとして使えますが 2つのメソッドを入れ子にする必要はありません NSNumberクラスには 初期化メソッドに対応して 次のようなファクトリメソッドも定義されています + (NSNumber *)numberwithbool:(bool)value; + (NSNumber *)numberwithfloat:(float)value; + (NSNumber *)numberwithint:(int)value; + (NSNumber *)numberwithlong:(long)value; ファクトリメソッドは次のように使います NSNumber *magicnumber = [NSNumber numberwithint:42]; これは実質的に alloc] initwithint:] という形で記述した先の例と同じ処理になります ファクトリメソッドは通常 alloc と init を順に呼び出す処理を 簡潔に書けるようにしただけのも のです 初期化に引数が必要なければ new でオブジェクトを生成できる インスタンスをnewというクラスメソッドで生成することも可能です これはNSObjectに実装されており サブクラスの側でオーバーライドする必要はありません 実質的に allocと引数なしのinitを組にして呼び出すのと同じことになります XYZObject *object = [XYZObject new]; // is effectively the same as: XYZObject *object = [[XYZObject alloc] init]; リテラルはオブジェクトを生成するための短縮構文である いくつかのクラスは インスタンスを生成するために リテラルという短縮構文が使えるようになっています たとえばNSStringのインスタンスは 次のようなリテラル記法で生成できます 35

オブジェクトの操作 Objective-C は動的言語である NSString *somestring = @"Hello, World!"; これは実質的に NSString 用の領域を割り当てて初期化する あるいは次のファクトリメソッドを 実行するのと同等です NSString *somestring = [NSString stringwithcstring:"hello, World!" encoding:nsutf8stringencoding]; NSNumber クラスにも同様に さまざまなリテラル記法があります NSNumber *mybool = @YES; NSNumber *myfloat = @3.14f; NSNumber *myint = @42; NSNumber *mylong = @42L; これもやはり 割り当て / 初期化メソッドやファクトリメソッドと実質的に同等です さらに NSNumber の場合 次のように式を埋め込んだ記法も可能です NSNumber *myint = @(84 / 2); この場合 式を評価し その結果に基づいて NSNumber のインスタンスを生成することになります Objective-C には 不変オブジェクトである NSArray や NSDictionary を生成するリテラルもあります ( 値とコレクション (79 ページ ) を参照 ) Objective-C は動的言語である 先に説明したように メモリ上のオブジェクトを追跡管理するためにはポインタが必要です Objective-C は動的特性を備えた言語であって ポインタが指すオブジェクトの具体的な型 ( クラス ) は何であっても構いません メッセージを送信すれば 当該オブジェクトに結びついた 正しいメソッドが呼び出されます id 型は 汎用的なオブジェクトポインタです 変数をid 型として宣言することも可能ですが オブジェクトに関するコンパイル時情報は失われます たとえば次のコードを考えてみましょう 36

オブジェクトの操作 Objective-C は動的言語である id someobject = @"Hello, World!"; [someobject removeallobjects]; この場合 someobjectはnsstringのインスタンスを指しますが コンパイラには それが何らかのオブジェクトであることしか認識できません removeallobjectsメッセージは Cocoa/Cocoa Touch のいくつかのオブジェクト (NSMutableArrayなど) に定義されているので コンパイル時に問題が顕在化することはありません ただし実行の段階になると NSStringオブジェクトは removeallobjectsに応答できないので 例外が発生します これを静的な型に書き替えてみましょう NSString *someobject = @"Hello, World!"; [someobject removeallobjects]; これはコンパイル時にエラーになります removeallobjectsの公開インターフェイスにnsstringは宣言されていないからです オブジェクトが属するクラスは実行時に決まるので インスタンスを生成し あるいは操作する際 どの型の変数に代入しても 何の違いも生じません 先に例示したXYZPersonクラスと XYZShoutingPersonクラスを使うコードは たとえば次のようになるでしょう XYZPerson *firstperson = [[XYZPerson alloc] init]; XYZPerson *secondperson = [[XYZShoutingPerson alloc] init]; [firstperson sayhello]; [secondperson sayhello]; firstpersonもsecondpersonも静的にはxyzperson 型のオブジェクトですが secondperson 実行時には はXYZShoutingPersonオブジェクトを指します 各オブジェクトに対してsayHelloメソッドを呼び出すと それぞれに応じて正しい実装が使われます すなわち secondpersonについては XYZShoutingPersonのコードが実行されるのです オブジェクトの等価性を判断する あるオブジェクトが別のオブジェクトと同じものであるかどうか判断する際には ポインタを介して操作している ということを頭に入れておくことが重要です 標準 Cの等価演算子 == は 次のように 2つの変数の値が等しいかどうかを調べるものでした if (someinteger == 42) { 37

オブジェクトの操作 Objective-C は動的言語である // someinteger has the value 42 オブジェクトどうしを == 演算子で比較すると 2 つのポインタが同じオブジェクトを指している か を調べることになります if (firstperson == secondperson) { // firstperson is the same object as secondperson 2 つのオブジェクトが同じデータを表しているかどうか調べたい場合は NSObject に実装されてい る isequal: などのメソッドを呼び出す必要があります if ([firstperson isequal:secondperson]) { // firstperson is identical to secondperson あるオブジェクトの値が 別のオブジェクトの値よりも大きい / 小さいかどうか比較したい場合 標 準 C の比較演算子 > や < は使えません Foundation の基本型である NSNumber NSString NSDate には代わりに compare: メソッドが実装されています if ([somedate compare:anotherdate] == NSOrderedAscending) { // somedate is earlier than anotherdate nil の取り扱い スカラ変数は宣言時に初期化するのがよい習慣です そうでないと スタック領域にたまたま残って いた ごみ の値になってしまいます BOOL success = NO; int magicnumber = 42; オブジェクトを指すポインタについては これは必要ありません 初期値を指定しなければ コンパ イラが自動的に nil に初期化するからです 38

オブジェクトの操作 Objective-C は動的言語である XYZPerson *someperson; // someperson is automatically set to nil nil 値は 特定の値を与えないオブジェクトの初期値として最も安全です Objective-C では nil に対 してメッセージを送信することは まったく正当な処理だからです nil にメッセージを送信しても 当然ながら何も起こりません 注意 : nil にメッセージを送信してその戻り値を取得すると その型がオブジェクトであれ ば nil 数値型であれば 0 BOOL 型であれば NO となります 戻り値の構造体は メンバーが すべて 0 で初期化されています あるオブジェクトが nil でないこと ( メモリ上のあるオブジェクトを指していること ) を確認したい 場合 2 つの方法があります ひとつは C の非等価演算子を使う方法です if (someperson!= nil) { // someperson points to an object 単にその変数を条件式として記述してしまうという方法もあります if (someperson) { // someperson points to an object 変数 someperson の値が nil であれば 条件式としての値は 0(false) となります アドレスであれば 0 ではないので 条件式としての値は true になります 同様に 変数値が nil であることを確認したい場合 等価演算子を使う方法があります if (someperson == nil) { // someperson does not point to an object その変数を式として扱い 否定演算子を適用する という方法もあります if (!someperson) { 39

オブジェクトの操作練習問題 // someperson does not point to an object 練習問題 1. 前章の練習問題で作成したプロジェクトのmain.mファイルを開き 関数 main() を見つけてください Cで記述したプログラムと同様 この関数がアプリケーションの開始点になります allocとinitを使ってxyzpersonのインスタンスを生成し sayhelloメソッドを呼び出してください 注意 : ヘッダファイルが自動的に提示されない場合 XYZPerson の先頭に ヘッダファ イル (main.m のインターフェイスを記述したもの ) をインポートする旨の記述が必要 です 2. この章に例示したsaySomething: メソッドを実装し これを使うようにsayHelloメソッドを書き替えてください 何か適当な挨拶をするメソッドを追加し 先に生成したインスタンスに対して呼び出してみましょう 3. XYZShoutingPersonクラスを定義する新しいクラスファイルを作り XYZPersonを継承するよう記述してください 次に 挨拶文を大文字に変換して表示するよう saysomething: メソッドをオーバーライドします XYZShoutingPersonのインスタンスに対して呼び出して 想定通りに動作するかテストしてください 4. 前章で宣言した XYZPersonクラスのファクトリメソッドpersonを実装してください XYZPerson クラスのインスタンス領域を正しく割り当て 初期化して返すものとします 次に main() の実装 (allocとinitを入れ子にして記述したもの) を このファクトリメソッドを使う形に書き替えてください 40

オブジェクトの操作練習問題 ヒント : ファクトリメソッドで [[XYZPerson alloc] init] ではなく [[self alloc] init] と記述してみましょう ファクトリメソッド内では selfは当該クラス自身を表します したがって XYZShoutingPersonでpersonメソッドをオーバーライドしなくても 正しいインスタンスを生成できることになります このことを確認してみましょう XYZShoutingPerson *shoutingperson = [XYZShoutingPerson person]; このコードで 正しい型のオブジェクトが生成されるはずです 5. XYZPerson ポインタ型の局所変数をもうひとつ宣言してください ただし値は代入しません 適当な条件分岐 (if 文 ) を記述して 変数に自動的に nil が代入されていることを確認してくだ さい 41

データのカプセル化 前章ではメッセージの送受信にまつわるオブジェクトの振る舞いを説明しました オブジェクトにはほかに プロパティの形でデータをカプセル化する という働きがあります この章では オブジェクトにプロパティを宣言するための Objective-Cの構文を説明します プロパティを宣言すれば アクセサメソッドとインスタンス変数が自動的に生成されるのが既定の動作ですが これについても解説します プロパティの実装にインスタンス変数を用いる場合 どの初期化メソッドでも適切に設定されるようにしなければなりません プロパティを用いて他のオブジェクトへのリンクを保持する場合は オブジェクト間にどのような性質の関係があるのか検討することが重要です Objective-Cでは多くの場合 オブジェクトのメモリ管理を自動参照カウント (ARC Automatic Reference Counting) により処理するのですが 強い参照のループが生じるとメモリリークに陥る という問題があるので その回避方法を知っておくことも大切です この章では オブジェクトのライフサイクル およびオブジェクト間の関係が成すグラフの管理についても説明します プロパティにはオブジェクトの値をカプセル化する働きがある 多くのオブジェクトは そのタスクを実行するため 関係する情報を保持し 追跡管理する必要があ ります たとえば いくつかの値を モデル化 するよう設計されたオブジェクトがあります たと えば Cocoa の NSNumber クラスは数値を保持し XYZPerson クラスは人の姓と名前をモデル化して保持 する といった具合です より広い視野の処理を行うオブジェクトもあって たとえばユーザイン ターフェイス部品や画面に表示する情報をやり取りするのですが その場合でも インターフェイス 要素や関係するモデルオブジェクトを追跡管理しなければなりません 公開プロパティを宣言してデータを公開する Objective-Cのプロパティは クラスにカプセル化して管理する情報を定義する手段となります プロパティはオブジェクトの値に対するアクセスを制御する (16 ページ ) で説明したように プロパティ宣言はクラスのインターフェイスに記述します @interface XYZPerson : NSObject @property NSString *firstname; 42

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある @property NSString *lastname; @end この例でXYZPersonクラスには 姓と名前を保持する文字列プロパティが宣言されています オブジェクト指向プログラミングには オブジェクトの内部処理を公開インターフェイスの奥に隠蔽する という重要な原則があります これに従えば オブジェクトのプロパティには 公開されている何らかの手段を介してアクセスする ( 内部に保持されている値に直接アクセスしない ) ことが重要です アクセサメソッドを使ってプロパティ値を取得 / 設定する オブジェクトのプロパティ値は アクセサメソッドを使って取得 / 設定できます NSString *firstname = [someperson firstname]; [someperson setfirstname:@"johnny"]; 特に指定しなければ アクセサメソッドはコンパイラが自動生成するようになっているので クラスインターフェイスに @propertyで宣言する以外の記述は必要ありません 自動生成されるメソッドは 次の命名規約に従います 値を取得するメソッド ( いわゆるゲッタメソッド ) は プロパティと同じ名前です したがって firstnameというプロパティのゲッタメソッドの名前は firstname となります 値を設定するメソッド ( いわゆるセッタメソッド ) は set の後に プロパティ名の先頭を大文字にして連結した名前です したがって firstnameというプロパティのセッタメソッドの名前は setfirstname: となります セッタメソッドでプロパティ値を変更できないようにしたい場合は プロパティ宣言の属性として readonly を指定してください @property (readonly) NSString *fullname; 属性には プロパティに関してどのような操作ができるか 他のオブジェクトが認識できるようにするだけでなく どのアクセサメソッドを生成するか コンパイラに指示する役割があります この場合コンパイラは fullnameというゲッタメソッドを生成しますが setfullname: メソッドは生成しません 43

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある 注意 : readonly の反対は readwrite です これが既定値なので 明示的に readwrite とい う属性を指定する必要はありません アクセサメソッドを別の名前にしたい場合は 属性の形で指定できます ブール型 ( 値がYESまたは NO) のプロパティには ゲッタメソッドを is で始まる名前にする慣習があります たとえば finishedというプロパティのゲッタメソッドは isfinished となります これもプロパティの属性として指定できます @property (getter=isfinished) BOOL finished; 複数の属性を指定する場合は 次のようにコンマで区切ってください @property (readonly, getter=isfinished) BOOL finished; この場合 コンパイラは isfinished メソッドのみを生成し setfinished: メソッドは生成しませ ん 注意 : 一般にプロパティのアクセサメソッドは キー値コーディング (KVC Key-Value Coding) に準拠するため その命名規約に従う必要があります 詳細については Key-Value Coding Programming Guide を参照してください ドット構文を使うとアクセサメソッドの呼び出しを簡潔に記述できる アクセサメソッドの呼び出しを明示的に記述する代わりに ドット構文を使ってもオブジェクトのプロパティにアクセスできます ドット構文では次のように記述します NSString *firstname = someperson.firstname; someperson.firstname = @"Johnny"; これは アクセサメソッドの呼び出しを簡潔に記述する 単なる便宜のために提供されている機能です ドット構文とは別に 上述のゲッタ / セッタメソッドを使って値を取得 / 設定することはもちろん可能です 値を取得する someperson.firstname という記述は [someperson firstname] と同等 44

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある 値を設定する someperson.firstname = @"Johnny" という記述は [someperson setfirstname:@"johnny"] と同等 ドット構文による取得 / 設定の可否も プロパティの属性で制御できます 実際 readonly 属性が指 定されていれば ドット構文で値を設定しようとしてもコンパイル時にエラーが発生します プロパティの多くはインスタンス変数を使って実装されている 何も指定しなければ readwrite 属性が指定されたプロパティは その実装のためにインスタンス変数を使い これもコンパイラが自動生成するようになっています インスタンス変数は オブジェクトが 生きて いる間 その値を保持するための変数です インスタンス変数用のメモリ領域は オブジェクトの生成時に (allocで) 確保され 消滅時に解放されます 特に指定しなければ このインスタンス変数の名前は プロパティ名の先頭にアンダースコアを置いたものになります たとえば firstname というプロパティに対応するインスタンス変数は _firstname となります オブジェクトが自分自身のプロパティにアクセスする場合も アクセサメソッドやドット構文を使うべきですが クラスに実装するインスタンスメソッド内では インスタンス変数に直接アクセスすることも可能です アンダースコアがついているので たとえば局所変数ではなく インスタンス変数であることがひと目で分かります - (void)somemethod { NSString *mystring = @"An interesting string"; _somestring = mystring; この例の場合 mystringは局所変数 _somestringはインスタンス変数であることが明らかです 一般に プロパティにアクセスする際は アクセサメソッドまたはドット構文を使うべきです 当該オブジェクト自身の実装コード内からであっても同様で この場合はselfを使わなければなりません - (void)somemethod { NSString *mystring = @"An interesting string"; self.somestring = mystring; 45

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある // or [self setsomestring:mystring]; ただし 初期化や解放 独自のアクセサメソッドについては この規則は当てはまりません ( 詳しく は後述 ) 自動生成されるインスタンス変数の名前を変更できる先に説明したように 特に指定がなければ 値の設定が可能なプロパティのインスタンス変数は _propertyname という名前になります 別の名前にしたい場合は 実装コードに次のような構文で指定してください @implementation YourClass @synthesize propertyname = instancevariablename;... @end 以下に例を挙げます @synthesize firstname = ivar_firstname; この場合 プロパティ名は firstname のままであって firstname や setfirstname: と いうアクセサメソッド あるいはドット構文でアクセスできますが その値を保持するインスタンス 変数は ivar_firstname となります 46

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある 重要 : 次のように インスタンス変数名を指定せずに @synthesize を記述することもできま す @synthesize firstname; この場合 インスタンス変数名はプロパティ名と同じになります この例では インスタンス変数名もアンダースコアのない firstname となります プロパティとは関係ないインスタンス変数を定義できる値や他のオブジェクトを追跡管理したい場合は プロパティを使うのがよい方法です プロパティを宣言せずに独自のインスタンス変数を定義する場合は 次のように クラスインターフェイスまたは実装の先頭 波括弧内に記述してください @interface SomeClass : NSObject { NSString *_mynonpropertyinstancevariable;... @end @implementation SomeClass { NSString *_anothercustominstancevariable;... @end 注意 : クラス拡張の先頭に記述することも可能です ( クラス拡張はクラスの内部的な実装 を拡張する (66 ページ ) を参照 ) 初期化メソッドから直接インスタンス変数にアクセスする セッタメソッドには 値を変える以外にも副作用があります KVC 通知のトリガとなるほか 独自に メソッドを記述すれば ほかの処理を起動することも可能です 47

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある 初期化メソッド内では インスタンス変数に直接アクセスしなければなりません プロパティを設定する時点で オブジェクトの残り部分が完全に初期化済みでない恐れがあるからです 独自のアクセサメソッドを与えていない あるいは副作用がないことが分かっている場合でも 将来 サブクラスでその動作をオーバーライドする可能性があります 典型的なinitメソッドは次のような形になります - (id)init { self = [super init]; if (self) { // initialize instance variables here return self; initメソッドは スーパークラスの初期化メソッドを呼び出し その結果をselfに代入してから 独自の初期化処理をする必要があります スーパークラスが初期化に失敗してnilを返す可能性もあるので selfがnilでないことを確認しなければなりません メソッドの先頭で [super init] を呼び出しているので ルートクラスから順次 サブクラス階層をたどってinitの実装コードが実行されることになります 図 3-1 (48 ページ ) に XYZShoutingPerson オブジェクトを初期化する処理の流れを示します 図 3-1 初期化の処理 前章で説明したように オブジェクトの初期化は initを呼び出すか または指定した値で初期化するメソッドを呼び出すことにより行います XYZPersonクラスの場合 姓と名前を指定する初期化メソッドを提供することには意味があるでしょう - (id)initwithfirstname:(nsstring *)afirstname lastname:(nsstring *)alastname; 実装はたとえば次のようになります - (id)initwithfirstname:(nsstring *)afirstname lastname:(nsstring *)alastname { 48

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある self = [super init]; if (self) { _firstname = afirstname; _lastname = alastname; return self; 特命初期化メソッドとは 当該クラスの一義的な初期化メソッドであるオブジェクトに複数の初期化メソッドを宣言する場合 そのうちの1つを特命初期化メソッドとする必要があります 一般には 最も詳細な指定ができる ( 引数の個数が多い ) メソッドを特命初期化メソッドとし 他の初期化メソッドは 内部的にこれを援用する ( 呼び出す ) 形で実装します この場合 initもオーバーライドし 適切な既定値を与えて特命初期化メソッドを呼び出す形にしてもよいでしょう XYZPersonに誕生日を表すプロパティもあるとすれば 特命初期化メソッドは次のようになります - (id)initwithfirstname:(nsstring *)afirstname lastname:(nsstring *)alastname dateofbirth:(nsdate *)adob; このメソッドは 先に説明したように 対応するインスタンス変数の値を設定します 便宜のため 姓と名前だけを指定する初期化メソッドも用意する場合は 次のように 特命初期化メソッドを内部 的に呼び出す形で実装できます - (id)initwithfirstname:(nsstring *)afirstname lastname:(nsstring *)alastname { return [self initwithfirstname:afirstname lastname:alastname dateofbirth:nil]; さらに 適当な既定値を与えて 標準的な init メソッドを実装することも可能です - (id)init { return [self initwithfirstname:@"john" lastname:@"doe" dateofbirth:nil]; 49

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある init 系のメソッドが複数あるクラスのサブクラスを定義し その初期化メソッドを実装する場合は スーパークラスの特命初期化メソッドをオーバーライドして独自の初期化処理を追加するか または独自の初期化メソッドを追加することになるでしょう いずれにしても スーパークラスの特命初期化メソッドを ([super init]; の代わりに ) 呼び出した後に 独自の初期化処理を実装することになります 独自のアクセサメソッドを実装することもできる プロパティの中には 対応するインスタンス変数がないものもあります たとえば XYZPerson クラスに 読み取り専用の 氏名を表すプロパティを定義するとしましょう @property (readonly) NSString *fullname; 姓や名前が変わる都度 fullname プロパティを更新するのではなく 必要時に文字列を連結して氏 名を表す文字列を生成する 独自のアクセサメソッドを実装する方が容易かもしれません - (NSString *)fullname { return [NSString stringwithformat:@"%@ %@", self.firstname, self.lastname]; この簡単な例では ( 前章で説明した ) 書式指定文字列と書式指定子を使い 空白をはさんで名前と 姓を連結しています 注意 : これは便宜上示した例ですが 適切な連結方法はロケール ( 地域 ) によって違いま す この実装は名前の後に姓を置く習慣の国でしか通用しません 独自にプロパティのアクセサメソッドを実装し これがインスタンス変数を用いる場合 このメソッ ド内では インスタンス変数に直接アクセスする必要があります たとえば次のように プロパティ の初期化を 最初に値が要求された時点で行うことが考えられます ( 遅延評価 ) - (XYZObject *)someimportantobject { if (!_someimportantobject) { _someimportantobject = [[XYZObject alloc] init]; return _someimportantobject; 50

データのカプセル化プロパティにはオブジェクトの値をカプセル化する働きがある このメソッドは まずインスタンス変数 _someimportantobject が nil かどうかを確認し 必要なら ばオブジェクトの領域を割り当て 初期化してから値を返しています 注意 : コンパイラは アクセサメソッドをひとつでも生成する必要がある場合 自動的にインスタンス変数も生成します ただし readwrite 属性のプロパティに対してゲッタとセッタの両方を実装し あるいはreadonly 属性のプロパティに対してゲッタを実装した場合 コンパイラは当該プロパティに関する実装がすべて揃っているものと判断し インスタンス変数を自動生成しません インスタンス変数がなお必要であれば 次のように記述して 生成するよう指示する必要があります @synthesize property = _property; プロパティは一般にアトミックである Objective-C のプロパティは 特に指定しなければアトミックになります @interface XYZObject : NSObject @property NSObject *implicitatomicobject; // atomic by default @property (atomic) NSObject *explicitatomicobject; // explicitly marked atomic @end すなわち 自動生成されたアクセサは 異なるスレッドからいくつか同時に呼び出したとしても 完全な形で値を取得 / 設定できるのです アトミックなアクセサメソッドの内部的な実装や同期機構は非公開なので 自動生成されたアクセサと 独自に実装したアクセサメソッドを組み合わせることはできません atomicかつreadwriteのプロパティに対して たとえばセッタを独自実装し ゲッタは自動生成に任せようとすると コンパイラから警告されることになります nonatomicというプロパティ属性を指定すれば 自動生成されるアクセサは 単に値を直接取得 / 設定するだけのものになります 異なるスレッドから同時にアクセスしたときの結果は保証されません その代わりnonatomicなプロパティはatomicなプロパティと違ってアクセス処理が高速であり また たとえばゲッタのみ独自に実装して組み合わせることも可能です 51

データのカプセル化所有と責任の観点からオブジェクトグラフを管理する @interface XYZObject : NSObject @property (nonatomic) NSObject *nonatomicobject; @end @implementation XYZObject - (NSObject *)nonatomicobject { return _nonatomicobject; // setter will be synthesized automatically @end 注意 : プロパティのアトミック性は スレッドセーフであるかどうかとは別の概念です あるスレッドが アトミックなアクセサを使って XYZPersonオブジェクトの姓と名前を変更する という状況を考えてみましょう 同時に別のスレッドが姓と名前を取得しようとすれば アトミックなゲッタは ( クラッシュすることなく ) 完全な文字列を返しますが それが正しい組み合わせである保証はありません 変更前に姓にアクセスし 変更後に名前にアクセスすると 正しく対応していない氏名が得られることになるのです これは非常に単純な例ですが スレッドセーフか否かの問題は 多くのオブジェクトが絡む状況では非常に複雑になります スレッドセーフ性について詳しくは Concurrency Programming Guide を参照してください 所有と責任の観点からオブジェクトグラフを管理する これまでにも説明したように Objective-Cでは オブジェクトのメモリ領域は ( ヒープ上に ) 動的に割り当てられます したがって ポインタを使ってそのアドレスを追跡管理する必要があります スカラー値と違って オブジェクトの寿命を あるポインタ変数のスコープに基づいて判断できるとは限りません それどころか 他のオブジェクトから必要とされている間は メモリ上に置いておかなければならないのです 各オブジェクトの寿命を個別に管理する代わりに オブジェクト間の関係を考えることにしましょう たとえばXYZPersonオブジェクトの場合 firstnameとlastnameという2つの文字列プロパティは 実質的にXYZPersonのインスタンスが 所有 しています すなわち XYZPersonオブジェクトがメモリ上にある限り 2つのプロパティもメモリ上になければならないのです 52

データのカプセル化所有と責任の観点からオブジェクトグラフを管理する このように あるオブジェクトが他のオブジェクトに依存している ( 実質的に 他のオブジェクトを所有している ) 場合 第 1のオブジェクトからもう一方のオブジェクトへの強い参照がある と言います Objective-Cでは 他のオブジェクトからの強い参照が1つでもあれば 寿命を保つようになっています XYZPersonインスタンスと2つのNSStringオブジェクトとの関係を図 3-2 (53 ページ ) に示します 図 3-2 強い参照関係 XYZPersonがメモリ上から解放されれば 2つの文字列オブジェクトも ほかに強い参照が残っていない限り解放されます もう少し複雑な例を見てみましょう 図 3-3 (53 ページ ) のようなアプリケーションのオブジェクトグラフを考えてみます 図 3-3 Name Badge Maker アプリケーション Update ボタンを押すと 姓と名前の情報を反映してバッジのプレビューが更新されます 最初にある人の氏名を入力して Update ボタンを押すと オブジェクトグラフは図 3-4 (53 ペー ジ ) のようになります ( 要点のみ ) 図 3-4 XYZPerson オブジェクトを最初に生成した時点のオブジェクトグラフの要点 ここでユーザが名前を変更すると オブジェクトグラフは図 3-5 (53 ページ ) のように変わります 図 3-5 名前を変更したときのオブジェクトグラフの要点 バッジを表示するビューからは 元の @"John" という文字列オブジェクトに対する強い参照が残っています 一方 XYZPersonオブジェクトは別のfirstNameを指しています すなわち @"John" オブジェクトはなおメモリ上にあり バッジビューが名前を表示するために使っているのです ここで再びユーザが Update ボタンを押すと バッジビューはXYZPersonオブジェクトに合わせて 内部に保持しているプロパティを更新します その結果 オブジェクトグラフは図 3-6 (53 ページ ) のようになります 図 3-6 バッジビュー更新後のオブジェクトグラフの要点 この時点で 元の @"John" オブジェクトはどこからも強い参照がなくなるので メモリから削除され ます 53

データのカプセル化所有と責任の観点からオブジェクトグラフを管理する Objective-C のプロパティや変数は 別途指定しなければ オブジェクトに対する強い参照を保持しま す 多くの場合これで問題ないのですが 強い参照の循環が発生すると問題が起こります 強い参照の循環を回避する オブジェクト間の強い参照は 1 方向の関係であればうまく働くのですが 相互に接続されたオブジェクト群を操作する際には注意が必要です いくつかのオブジェクトが強い参照で結ばれて循環が生じると その外部からは強い参照がなくても 相互に寿命を保ちあう関係になってしまいます 循環参照が生じうる分かりやすい例として テーブルビューオブジェクト (iosのuitableview OS XのNSTableView) とそのデリゲートの関係があります 汎用的なテーブルビュークラスは さまざまな状況で使えるようにするため いくつかの判断を外部オブジェクトに委譲します 何を表示するか ある欄をユーザが操作したときに何をするか という判断を 別のオブジェクトに委ねているのです そこで テーブルビューがデリゲートを参照し 同時にデリゲートもテーブルビューを参照している という状況がよく発生します ( 図 3-7 (54 ページ ) を参照 ) 図 3-7 テーブルビューとそのデリゲートの強い参照関係 ここで 他のオブジェクトからテーブルビューやデリゲートに対する強い参照がなくなると 問題が 起こります ( 図 3-8 (54 ページ ) を参照 ) 図 3-8 強い参照の循環 オブジェクトをメモリ上に置いておく必要はない ( テーブルビューやデリゲートに 相互の参照以外に強い参照がない ) にもかかわらず 強い参照が残っているため オブジェクトは生き残ってしまうのです この状況を強い参照の循環と言います これを解消するためには 一方の強い参照を弱い参照に置き換えなければなりません これは 2つのオブジェクト間の 所有や責任を伴わない参照関係のことであり したがって参照先オブジェクトの寿命を保つ働きはありません 54

データのカプセル化所有と責任の観点からオブジェクトグラフを管理する テーブルビューからデリゲートへの関係を弱い参照に置き換えると オブジェクトグラフは図 3-9 (55 ページ ) のようになり UITableView や NSTableView は問題を解消できるようになります 図 3-9 テーブルビューとそのデリゲートの正しい関係 他のオブジェクトからテーブルビューやデリゲートへの強い関係が消えると デリゲートに対する強 い参照はなくなります ( 図 3-10 (55 ページ ) を参照 ) 図 3-10 強い参照の循環を回避 したがってデリゲートは解放され その結果 テーブルビューに対する強い参照もなくなります ( 図 3-11 (55 ページ ) を参照 ) 図 3-11 デリゲートの解放 デリゲートが解放されれば テーブルビューに対する強い参照もなくなるので これも解放されま す 強い宣言と弱い宣言で所有関係を管理する 通常 オブジェクトのプロパティは次のように宣言します @property id delegate; 自動生成されるインスタンス変数は 強い参照を管理するものになります 弱い参照を宣言したい場 合は プロパティに属性を追加して次のように記述します @property (weak) id delegate; 注意 : weak の反対は strong です これが既定値なので 明示的に strong という属性を指定 する必要はありません 局所変数 ( およびプロパティではないインスタンス変数 ) も 通常はオブジェクトの強い参照を保持 します したがって次のコードは想定通りに動作するでしょう NSDate *originaldate = self.lastmodificationdate; self.lastmodificationdate = [NSDate date]; 55

データのカプセル化所有と責任の観点からオブジェクトグラフを管理する NSLog(@"Last modification date changed from %@ to %@", originaldate, self.lastmodificationdate); この例で 局所変数 originaldateは 当初のlastModificationDateオブジェクトの強い参照を保持しています lastmodificationdateプロパティを更新すると このプロパティは元の日付に対する強い参照を保持しなくなります ただしこの日付にはoriginalDateからの強い参照があるので なお寿命を保ちます 注意 : 変数があるオブジェクトの強い参照を保持しているのは その変数のスコープ内か あるいは他のオブジェクトや nil を代入されるまでの間です 強い参照を保持しない変数が欲しければ 次のように weak をつけて宣言してください NSObject * weak weakvariable; 弱い参照があるだけでは オブジェクトの寿命を保つことはできないので まだ使用中なのに参照先オブジェクトが解放されてしまうこともありえます 解放済みオブジェクトが過去に占めていたメモリ領域を指す いわゆる ぶら下がり ポインタが起こらないようにするため オブジェクトを解放すると 弱い参照は自動的にnilになります したがって先の日付の例で 弱い参照を使うことはできません NSDate * weak originaldate = self.lastmodificationdate; self.lastmodificationdate = [NSDate date]; 変数 originaldateはnilになってしまう可能性があります self.lastmodificationdateに新しい日付オブジェクトを代入した時点で 元の日付オブジェクトに対する強い参照はなくなります したがって ほかに強い参照がなければ元の日付は解放され originaldateはnilになってしまうのです 弱い変数は 特に次のように記述した場合 混乱のもとになるでしょう NSObject * weak someobject = [[NSObject alloc] init]; この例で 新しく割り当てたオブジェクトに対する強い参照は存在しません したがって即座に解放 され someobject の値は nil になります 56