11月, 2011

Objective-Cのクラスの前方宣言がないと困ること

投稿者:アールケー開発 この記事は 2011年11月30日 に公開されました。

クラス名の前方宣言について質問をもらったので少しまとめます。

Objective-Cでクラス名を前方宣言したいときには「@class」文を使用します。例えば「MyObject」という名前のクラスであれば、次のように書きます。

@class MyObject;

ヘッダファイルの中で、Objective-Cのクラスのインスタンスをメンバー変数にしたいときはクラスが存在することを知らなければいけません。このようなときに「#import」文をヘッダファイルに書いてクラスの宣言を読み込みます。例えば次のようなコードです。(このヘッダファイルを「MyObjectB.h」ファイルとします)

#import "Cocoa/Cocoa.h"

#import "MyObject.h"

@interface MyObjectB : NSObject {

MyObject    *_myObject;

}

@end

このとき、もしも「MyObject.h」ファイルに次のようなコードが書かれていると問題が起こります。

#import "Cocoa/Cocoa.h"

#import "MyObjectB.h"

@interface MyObject : NSObject {

MyObjectB   *_object;

}

@end

このコードをビルドしようとすると、コンパイラは次のようなエラーを表示します。

Complie MyObject.m

Expected specifier-qualifer-list before "MyObject"

Compile MyObjectB.m

Expected specifier-qualifier-list before "MyObjectB"

エラーメッセージの意味は次の2つです。

  • 「MyObject.m」ファイル(「MyObject.h」ファイルを#importしている)のビルドで、「MyObject」の定義が無い。
  • 「MyObjectB.m」ファイル(「MyObjectB.h」ファイルを#importしている)のビルドで、「MyObjectB」の定義が無い

「#import」文で、それぞれのクラスのインターフェイスを読み込んでいるのになぜ「定義が無い」といわれてしまうのでしょうか?

実は、ビルドのときにプリプロセッサによってどのような処理が行われているかを考えてみると原因が分かります。プリプロセッサはビルドするときに「#import」文などを実行して、次のような動きをします。(「Cocoa/Cocoa.h」ファイルはここでは関係ないので、その内容は割愛します)

「MyObject.h」ファイルの処理(「MyObject.m」ファイルのコンパイル)

  1. 「Cocoa/Cocoa.h」ファイルを読み込む
  2. 「MyObjectB.h」ファイルを読み込む(この時点では「MyObject」クラスは定義されていない
  3. 「MyObjectB.h」ファイルの中の「#import “MyObject.h”」文でファイルの読み込みを行おうとする。しかし、「#import」文では一回しかヘッダファイルは読まれないので、「MyObjectB.h」ファイルの中の「#import “MyObject.h”」文は無視される。
  4. 「MyObjectB」クラスの中の「MyObject」クラス型のメンバー変数の定義文で「MyObject」クラスの宣言が、まだ、無いからエラー(この時点では2の実行中なので、まだ、「MyObject」クラスの宣言まで行っていません)

「MyObjectB.h」ファイルの処理(「MyObjectB.m」ファイルのコンパイル)

  1. 「Cocoa/Cocoa.h」ファイルを読み込む
  2. 「MyObject.h」ファイルを読み込む
  3. 「MyObject.h」ファイルの中の「#import “MyObjectB.h”」文でファイルの読み込みを行おうとする。しかし、「#import」文では一回しかヘッダファイルは読み込まれないので、「MyObject.h」ファイルの中の「#import “MyObjectB.h”」文は無視される。
  4. 「MyObject」クラスの中の「MyObjectB」クラス型のメンバー変数の定義分で「MyObjectB」クラスの宣言が、まだ、無いからエラー(この時点では2の実行中なので、まだ、「MyObjectB」クラスの宣言まで行っていません)

このような、循環インクルード(循環インポート)における問題を防止するために使用するのが「@class」文です。上記の2つのコードを次のように修正します。

「MyObject.h」ファイル

#import "Cocoa/Cocoa.h"

//#import "MyObjectB.h"

@class MyObjectB;

@interface MyObject : NSObject {

MyObjectB   *_object;

}

@end

「MyObjectB.h」ファイル

#import "Cocoa/Cocoa.h"

//#import "MyObject.h"

@class MyObject;

@interface MyObjectB : NSObject {

MyObject    *_myObject;

}

@end

このようにすると、お互いのヘッダファイルを参照しないので、循環する恐れが無く、クラスが存在することがわかっているのでメンバー変数も定義できます。

同じようにプロトコルも前方宣言ができます。次のようなコードです。

@protocol ProtoclName;

デリゲートメソッドをプロトコルで定義したときなど、クラスを先に書くか、プロトコルを先に書くかのどちらを行ってもお互いを知らないからエラーになるのを防止できます。

ただし、一つ注意。前方宣言では名前のみなので、インスタンスを確保する(「alloc」メソッドを呼ぶとき)とか、クラスを継承するとかのときには使えません。サブクラスを定義するときには必ず親クラスの定義が必要です。ですので、ヘッダファイルでは親クラスのヘッダファイルの読み込みは必須です。

それと、前方宣言することでのメリットは循環インクルード(循環インポート)の防止だけではなくコンパイル時間の削減にも効果あります。ヘッダファイルが大量のヘッダファイルをインポートしていて、インポートされているヘッダファイルも別のヘッダファイルを…などとなっていくると、ビルド時間が長くなっていきます。前方宣言ならそのようなことが起きないので、ビルド時間が指数関数的に増えるということはなくなります。もちろん、インクルードではなく、そもそものコードが膨大な場合にはビルド時間がかかるというのはかわりません。また、ヘッダファイルに依存関係が凄まじいと1つファイルを変更すると全体フルビルドみたいな状況になるので、できるだけヘッダファイルの依存関係を減らしておくというのは精神衛生上もよいことだと思います。

検証に使ったプロジェクトファイル(上記のコードが書いてあるだけですが)はこちらからダウンロードできます。ビルドできる状態のコードになっているので、「#import」文のコメントアウトを解除して、「@class」文をコメントアウトしてからビルドしてみてください。エラーになります。(Mac OS X 10.6 + Xcode 3.2.6で確認しました)

サンプルコードのダウンロード

RecursiveImport

▲ページトップへ戻る

MOSADeN ONLINE 第22回の記事をアップしました

投稿者:アールケー開発 この記事は 2011年11月24日 に公開されました。

MOSADeN ONLINE 第22回の記事をアップしました。11月24日の朝9時にMOSA会員向けに公開されます。今回はTwitterフレームワークの続きで、Twitterフレームワークを使ってTwitter APIを呼ぶ方法についての紹介です。システムに設定されたアカウント情報を使って認証して、ユーザーのタイムラインを取得します。

MOSADeN ONLINEはこちらです。

▲ページトップへ戻る

Mac App Store版に乗り換え後の認識エラー?(解決)

投稿者:アールケー開発 この記事は 2011年11月13日 に公開されました。

Transmitをインストールしようと思ったのですが、どうしても、Mac App Storeでは「インストール済み」という認識になってしまい、再インストールができないという症状になっていました。他のマシンでは問題ないし、アプリケーションフォルダからは削除済みなのになぜだろう。。。としばらく悩んでいましたがやっと原因が分かりました。

ホームディレクトリの下の「Library/Application Support/Sparkle/Transmit」の中にアプリが残っていました。こんな場所にインストールした記憶は全くなかったのですが、調べてみると「Sparkle」というのは自動アップデータ機能を提供するためのフレームワークです。Mac App Store版のTransmitに乗り換えるまで、Webから購入したTransmitを使っていたので、TransmitはSparkleを利用しているようなので、そのときに行ったアップデータの一部が残っていたということのようです。このディレクトリに残っていたアプリを削除すると、Mac App Storeの認識も変わり「インストール」ボタンが押せるようになりました。

もし、同じような現象で悩んでいる方がいましたら、「~/Library/Application Support/Sparkle」ディレクトリは要チェックです。また、アプリをMac App Store版に乗り換えようと思ったときにもチェックした方が良いようです。

▲ページトップへ戻る

MOSADeN ONLINE 21回の記事をアップしました

投稿者:アールケー開発 この記事は 2011年11月8日 に公開されました。

MOSADeN ONLINE 第21回の記事をアップしました。本日の15時頃公開されます。今回はiOS 5.0で登場したTwitterフレームワークについてです。Twitterフレームワークを使用すると、iOSアプリから簡単にTwitterへ投稿できます。MOSADeN ONLINEはこちら

▲ページトップへ戻る

MSM2011にご参加の皆様、ありがとうございました

投稿者:アールケー開発 この記事は 2011年11月6日 に公開されました。

MSM (MOSA Software Meeting) 2011にご参加いただいた皆様、ありがとうございました。

セッションで使用したスライドやサンプルコードはセッションの最後にお知らせしたURLからダウンロードできるように昨晩(日付が変わる直前でしたが)アップロードしました。URLについては、別途、事務局からもご案内があるかと思います。ダウンロードしてご覧ください。

▲ページトップへ戻る