MOSADeN ONLINE 第24回の記事を入稿しました

2011年12月25日

MOSADeN ONLINE 第24回の記事を入稿しました。今回は、iOSでキャプチャしている映像の中で変化があったときに、それを知るための方法についてです。27日にMOSA会員限定で公開されます。iOS 5.0ではこんなに簡単に検出できるのかと私自身は驚愕しました。

http://www.mosa.gr.jp/mosaden/

▲ページトップへ戻る

アドレスブックの保存先を明確にしたい

2011年12月17日

アドレスブックをiCloudと統合したときに、iCloudに統合されたデータのバックアップもかねて、iCloudに保存されない、ローカルのMac内に保存されたデータを明確にしたいと思い、その方法を探してみました。

以前であれば、アドレスブックのグループ一覧が表示される領域に、「このMac内」と「iCloud」というように分かれていたはずなのですが、新しいMacをセットアップしてみるとそれが見当たらず、iCloudと統合後はすべてiCloudに保存されるようになっていました。

「すべての連絡先」のみ表示されている

「すべての連絡先」のみ表示されている

探してみると、次のようにすれば表示されることがわかりました。

  1. アドレスブックを起動して、「アドレスブック」メニューから「環境設定」を選択する
  2. 「一般」タブをクリックして表示する
  3. 「デフォルトアカウント」を「この Mac 内」に変更する
「このMac内」と「iCloud」が分かれて表示されている

「このMac内」と「iCloud」が分かれて表示されている

▲ページトップへ戻る

MOSADeN Online 第23回の記事をアップしました

2011年12月15日

お知らせです。MOSADeN Onlineの第23回の記事をアップしました。16時にMOSA会員限定で公開になります。今回は、iOSデバイスから外部ディスプレイを使用する方法についてです。iOS 5ではミラーリングだけではなく、アプリから独自のビューコントローラを外部ディスプレイに表示することもでき、Apple TVを使えばAir Play越しもできます。

MOSADen Online はこちら

▲ページトップへ戻る

The Problem if you don’t know the forward class declaration of the Objective-C

2011年12月1日

I had received the question about the forward class declaration of the Objective-C and this is the answer.

If you want to declare the forward class declaration, you can write following:

@class MyObject;

The member variable of the Objective-C class needs the class name is declared before the variable is appeared. In this case, you can write the “#import” in the header file and read the declarations. For example, this code:

#import "Cocoa/Cocoa.h"

#import "MyObject.h"

@interface MyObjectB : NSObject {

MyObject    *_myObject;

}

@end

This code could be error when you write the following code in the “MyObject.h” file.

#import "Cocoa/Cocoa.h"

#import "MyObjectB.h"

@interface MyObject : NSObject {

MyObjectB   *_object;

}

@end

Following errors will be occurred when you complie abobe code.

Complie MyObject.m

Expected specifier-qualifer-list before "MyObject"

Compile MyObjectB.m

Expected specifier-qualifier-list before "MyObjectB"

The meaning of these errors are:

  • The declaration of the “MyObject” is not appeared in compling “MyObject.m” file (The “MyObject.m” file imports the “MyObject.h” file).
  • The declaration of the “MyObjectB” is not appeared in compling “MyObject.b” file (The “MyObjectB.m” file imports the “MyObjectB.h” file)

Why the complier says the declration is not appeared? The declration is imported with “#import”.

You can know the reason by thinking how the preprocessor works. The preprocessor does following.

“MyObject.h” file

  1. Imports “Cocoa/Cocoa.h” file.
  2. Imports “MyObjectB.h” file (The “MyObject” class is not declared at this point)
  3. Try to imports the “MyObject.h” file at the line “#import “MyObject.h”", but the file is imported only one time when you use the “#import” so the line “#import “MyObject.h”" will be ignored.
  4. The line “MyObject *_objct;” is appered but the class declration of the “MyObject” class is not appered so the compiler will stop.

“MyObjectB.h” file

  1. Imports “Cocoa/Cocoa.h” file.
  2. Imports “MyObject.h” file (The “MyObjectB” class is not declared at this point)
  3. Try to imports the “MyObjectB.h” file at the line “#import “MyObjectB.h”", but the file is imported only one time when you use the “#import” so the line “#import “MyObjectB.h”" will be ignored.
  4. The line “MyObjectB *_objct;” is appered but the class declration of the “MyObjectB” class is not appered so the compiler will stop.

To avoid this problem, you can use the “@class”. Above code can be written following.

“MyObject.h” file

#import "Cocoa/Cocoa.h"

//#import "MyObjectB.h"

@class MyObjectB;

@interface MyObject : NSObject {

MyObjectB   *_object;

}

@end

“MyObjectB.h” file

#import "Cocoa/Cocoa.h"

//#import "MyObject.h"

@class MyObject;

@interface MyObjectB : NSObject {

MyObject    *_myObject;

}

@end

There is the foward protocol declration:

@protocol ProtocolName;

Either you write the protocol declation before or after the class declation, the compiler reports error. You can avoid this problem by using the foward protocol declaration.

The sample code is available.

RecursiveImport

▲ページトップへ戻る

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については、別途、事務局からもご案内があるかと思います。ダウンロードしてご覧ください。

▲ページトップへ戻る

NSTrackingAreaクラス

2011年10月27日

NSViewでカーソルのトラッキングを行うとき、NSViewクラスのaddTrackingRect:owner:userData:assumeInside:メソッドを使っていましたが、Mac OS X 10.5のときにNSTrackingAreaクラスが追加されて、もうちょっとオブジェクト指向的になっていたことに気がつきました。

機能的にはaddTrackingRect:owner:userData:assumeInside:と違いはないように思いますが、updateTrackingAreasという気になるメソッドがあります。

ドキュメントによるとNSViewのサブクラスを作ったときにオーバーライドして、トラッキングエリアの更新を行えるという位置づけのメソッドです。

NSViewのトラッキングエリアの指定は、ビューのサイズが変わったときや移動したときなどに自動的には追従してくれないので、そのことを意識したメソッドのようです。

動作環境が10.5以降のときはこっちを使った方がスマートですね。

▲ページトップへ戻る