以下はTitanium Mobile Module Developer Guide for iOSの全訳です。
事前の許可なく勝手に日本語に翻訳しました。画像はAppceleratorのサイトへのリンクとなっています。
オリジナルのドキュメントがいささか古く、現行のTitanium StudioではなくTitanium Developerを使って説明されているため、最近Titaniumを始められた方は面食らうかもしれません。適宜補って読む必要がありますが、モジュール開発は、基本的にコマンドラインによる開発になるので、十分訳しておく意味があると思われます。
また、元々のドキュメントの品質が、エディトリアルを経ていない開発者が書いたストレートなものになっているので、若干ブロークンだったり、文意を読み取れない箇所が存在します。訳注等で補いましたが、あまりにもうるさくなりすぎる箇所は、断らずにもとの文を変更して訳している箇所もあります。
また、Xcodeのメニューなど原文同様英語版のままにしてあります。適宜置き換えてください。
モジュール開発しようという人たちの助けになれば幸いです。
また、私の間違い、勘違いなど不備があると思います。お気づきの点がございましたら、ご遠慮なく、コメントしていただけると助かります。
---------------------------
概要
Titanium は、Titanium+Plusモジュールと呼ばれる一連の追加的な拡張を通じて、組み込み済みの機能を拡張する能力を提供します。このガイドではそれらを簡単に「モジュール」と呼びます。このドキュメントはTitaniumモジュール開発者ガイドシリーズの一部であり、iOSモジュール開発のステップ0からステップ3までをカバーしています。
- ステップ0は、実際の開発環境をカバーし、iOSベースのモジュール開発の環境の設定について説明します。
- ステップ1は、Objective-Cを使って最初のモジュールを作成します。
- ステップ2では、モジールの基盤となるアーキテクチャについて学びます。
- ステップ3では、作成したモジュールの利用方法と第三者に作配布する方法を学びます。
iOS モジュールの事前条件
iOSベースのモジュールを開発するためには、次の事前条件を満たす必要があります:- Intel-based Macintosh running OSX 10.5 以上
- XCode 3.2 以上
- iOS 4.0 SDK 以上
- Titanium 1.4 Mobile SDK 以上ではiPhone と iPad 用の機能拡張をサポートします. 以下の(ステップ0の)環境の事前条件に加えて、Objective-C 開発についての全般的な知識を持っている必要があります。
ステップ0:モジュール環境の設定
OSX上のモジュール環境を設定するために、パスに関して補助的なスクリプトを設置したり、ファイルへの環境エイリアスの設定が必要となるでしょう。このドキュメントでは、以下のエイリアスを作ることを前提にします。Titanium 1.4.0 SDK (以上の現行バージョン)をどこにインストールしたか確認する必要があります。それは、/Library/Application\ Support/Titanium にインストールされているかも知れないし、~/Library/Application\ Support/Titanium にインストールされているかもしれません。以下の説明では、 /Library 以下にインストールされたものと仮定します。もし~/Library 以下にインストールされているなら、下の3の該当の箇所を補正する必要があります。 |
- Consoleアプリケーションを開きます。 Console アプリケーションは /Applications/Utilities/Console 以下にあります。
- $HOME ディレクトリ以下にある.bash_profile という名前のファイルを編集します。
- このファイルのなかに、ファイルの末尾に以下の行を追加します:
alias titanium=
'/Library/Application\ Support/Titanium/mobilesdk/osx/1.4.0/titanium.py'
- ファイルを保存します。
- コンソールで以下のコマンドをタイプします:
source \~/.bash_profile
- コンソールで以下のコマンドをタイプします:
titanium
Appcelerator Titanium
Copyright (c) 2010 by Appcelerator, Inc.
commands:create - create a project
run - run an existing project
help - get help
- 次のステップのためにコンソールは開いたままにしておきます。
ステップ1: 最初のモジュール作成
最初のモジュールをビルドする前に、環境設定が正確であるか、またテストアプリケーションでモジュールが使用できるか確認するために、テストモジュールを作成した方がいいでしょう。モジュールを作成するためには、コンソールで以下のコマンドを実行する必要です:
> titanium create --platform=iphone --type=module --dir=~/tmp/ --name=test --id=com.test |
Appcelerator Titanium XCode templates installed Created iphone module project |
> cd ~/tmp/test > ls -la |
total 80 drwxr-xr-x 16 jhaynie staff 544 Jul 30 15:08 . drwxr-xr-x@ 254 jhaynie staff 8636 Jul 30 15:08 .. -rw-r--r-- 1 jhaynie staff 20 Jul 30 15:08 .gitignore drwxr-xr-x 7 jhaynie staff 238 Jul 30 15:08 Classes -rw-r--r-- 1 jhaynie staff 62 Jul 30 15:08 ComTest_Prefix.pch -rw-r--r-- 1 jhaynie staff 78 Jul 30 15:08 LICENSE -rw-r--r-- 1 jhaynie staff 4877 Jul 30 15:08 README drwxr-xr-x 3 jhaynie staff 102 Jul 30 15:08 assets -rwxr-xr-x 1 jhaynie staff 5490 Jul 30 15:08 build.py drwxr-xr-x 3 jhaynie staff 102 Jul 30 15:08 documentation drwxr-xr-x 3 jhaynie staff 102 Jul 30 15:08 example drwxr-xr-x 7 jhaynie staff 238 Jul 30 15:08 hooks -rw-r--r-- 1 jhaynie staff 373 Jul 30 15:08 manifest -rw-r--r-- 1 jhaynie staff 777 Jul 30 15:08 module.xcconfig drwxr-xr-x 3 jhaynie staff 102 Jul 30 15:08 test.xcodeproj -rw-r--r-- 1 jhaynie staff 478 Jul 30 15:08 titanium.xcconfig |
ファイル/ディレクトリ | 説明/目的 |
.gitignore | 特別な .gitignore ファイルはリビジョン管理システムgitで使用され、システムに特定の一時ファイルや一時ディレクトリを度外視するように指示する。 |
このファイルはモジュールとともに配布されない。 | |
gitを使用しないなら、無視してかまいません。 | |
Classes | Objective-C ヘッダや実装クラスがおかれるディレクトリ |
これらのファイルはモジュールとともに配布されない。XCodeコンパイラによって使用される。 | |
ComTest_Prefix.pch | プリコンパイルヘッダ。これらのファイルはモジュールとともに配布されない。XCodeコンパイラによって使用される。 |
LICENSE | モジュールのライセンステキストの内容として配布するためのファイル。 |
このファイルはエンドユーザにモジュールとともに配布される。 | |
README | モジュールプロジェクトの簡単な説明をするファイル。 |
このファイルはモジュールとともに配布されない。 | |
assets | 画像ファイルなどのモジュールアセットをおくディレクトリ。 |
build.py | このファイルは、利用や配布のためにモジュールをコンパイルしたりパッケージしたりするスクリプトです |
documentation | このディレクトリはエンドユーザ向けのモジュールのドキュメントを格納する場所です。index.mdファイルは、モジュールドキュメンテーションを書くときのMarkdownフォーッマットされたテンプレートファイルです。これはTitanium+Plus Market-placeで配布するモジュールでのみ必須となります。モジュールを配布ない場合には、無視しても大丈夫です。 |
example | このディレクトリは、モジュールのサンプルを入れておく場所です。生成されたapp.jsファイルは、テストウインドウでモジュールをロードするサンプルを含んでいます。このファイルは、モジュールのクイックテストに使われるばかりでなく、どのようにモジュールを使用するかエンドユーザに伝えます。このディレクトリはモジュールとともに配布されます。 |
hooks | このディレクトリは現在は無視されます。将来の利用のために予約されています。 |
manifest | この特別なファイルはモジュールのメタデータを記述して、Titaniumコンパイラが利用します。 |
このファイルは必須で、モジュールとともに配布されます。 | |
module.xcconfig | この特別なファイルは、モジュールを参照するエンドユーザのTitaniumアプリケーション内にモジュールをコンパイルするときXcodeによって使用されます。 |
このファイルはXcode設定ファイルで、モジュールに固有のコンパイラやリンカの特別な記述子をセットアップします。このファイルはモジュールとともに配布されます。 | |
test.xcodeproj | このディレクトリはXcodeプロジェクトを含みます。このディレクトリはモジュールとともに配布されません。 |
titanium.xcconfig | この特別なファイルはモジュールをビルドする際にコンパイル時にXcodeによって使用される。このファイルはモジュールに固有のコンパイラやリンカの特別な記述子をセットアップするXcode設定ファイルです。 |
このファイルは配布されません。モジュール開発時に使用されるのみです。 | |
XcodeでTestModuleを開く
このステップでは、Xcodeで新規に作成したTestModuleを開くことにします。Xcodeで test.xcodeproj を開きます。プロジェクトを開くには、コンソールから次のようにタイプします:
> open test.xcodeproj |
メインモジュールファイルは ComTestModule.h (ヘッダ) と ComTestModule.m (実装)で定義されています。
モジュールをパッケージするとき、システムで生成された ComTestModuleAssets.h (ヘッダ) と ComTestModuleAssets.m (実装) を無視すべきです。これらのファイルを変更しても、モジュールをビルドするたびにシステムによって上書きされます。
この時点で、ビルドメニューのビルドコマンドでXcode内でモジュールをビルドできるはずです。
ビルドが成功すると、モジュールをパケージしたり、実際のTitaniumアプリケーションでテストしたりする準備ができています。
モジュールのパッケージング
モジュールをパッケージするためには、コンソールから build.py スクリプトを実行します。> ./build.py |
> ** BUILD SUCCEEDED ** |
4ページの注意と同様、Titanium SDKが/Libraryではなく~/Library にインストールされているかもしれません。 また、titanium.xcconfig ファイルは正しいディレクトリを指していなければなりません。さもないとビルドは失敗するでしょう (訳注:元々この文書がPDFファイルで配布されていた当時の名残で「4ページ」といっている) |
MacにプレインストールされているPythonインタープリタ用のデフォルトのPythonPath上にはmarkdown2モジュールが存在しません。インストールされていないとモジュールドキュメントの生成時にbuild.pyが失敗します。まだインストールしていなければ、以下の手順でmarkdown2をインストールしてください:
|
モジュールのテスト
モジュールをテストするには、単にモジュールテストハーネスを実行するだけです。モジュールテストハーネスは一時的プロジェクトを生成して、exampleディレクトリの(既に生成されている) app.jsファイルをコピーして、モジュールをロードします。カレントディレクトリ(~/tmp/test)からコンソールで以下を実行することで、テストを実施できます:> titanium run |
成功すれば、iPhoneシミュレータと白いスクリーンが現れます。(訳注:現行のバージョンでは、確認用のテキストが表示されるようになっています)
コンソール出力の最後の方に以下のものが現れます
[INFO] [object ComTestModule] loaded [DEBUG] loading: / var /folders/4n/4nhl7xlPFsaSrlAGHhPwg++++TI/-Tmp-/mNdElWkti/test/Resources/com.test.js, resource: var /folders/4n/4nhl7xlPFsaSrlAGHhPwg++++TI/-Tmp-/mNdElWkti/test/Resources/com_test_js [INFO] module is => [object ComTestModule] [DEBUG] application booted in 82.601011 ms |
[INFO] [object ComTestModule] loaded [INFO] module is => [object ComTestModule] |
-(void)startup { // this method is called when the module is first loaded // you *must* call the superclass [ super startup]; NSLog(@ "[INFO] %@ loaded" ,self); } |
var test = require( 'com.test' ); Ti.API.info( "module is => " +test); |
テストアプリケーションでモジュールをテストする
さて、テストアプリケーションでモジュールをテストしなければなりません。実際のアプリケーションでモジュールをテストする前に、モジュールをインストールしなければなりません。前のステップで既にモジュールをパッケージしていますから、モジュールのビルドステップは省略できます。しかし、モジュールのコードを変更したときには、ビルドとインストールのステップを繰り返さなければならないことを覚えておいてください。
実際のアプリケーションでモジュールを使用するにはモジュールをインストールしなければなりません。コンソールで以下のコマンドを実行します:
> cp com.test-iphone-0.1.zip /Library/Application\ Support/Titanium/ |
モジュールは以下のディレクトリに展開されます:
/Library/Application Support/Titanium/modules/iphone/com.test/0.1 |
4ページの注意と同様、Titanium SDKが/Libraryの代わりに~/Libraryにインストールされていれば、上記のパスを修正することが必要です。 |
アプリケーションのプロジェクトが作成されたあと、テストアプリケーションのフォルダの中を探して、テキストエディタでtiapp.xmlという名前のファイルを編集してください。(訳注:現在ではTitanium Studio内で編集できます)
このファイルの最後の方に以下の行を追加する必要があります:
< modules > < module version = "0.1" >com.test</ module > </ modules > |
最終的な XML file は以下のようになります:
|
guidの値は、グローバルに一意なIDで、その都度生成されるので、上の例とは異なっているでしょう。 |
var test = require( 'com.test' ); Ti.API.info( "module is => " +test); |
Titanium Developerコンソールはモジュールがロードされたことを確認できる重要なログステートメントを出力するはずです:
Detected third-party module: com.test/0.1 |
また、app.jsに追加したコードに基づいて以下のものが現れるはずです:
[INFO] [object ComTestModule] loaded [INFO] module is => [object ComTestModule] |
ステップ2:モジュールアーキテクチャの基礎
Titaniumプラットフォームはモジュールアーキテクチャに基礎をおいています。それに基づいて、このドキュメントで述べているサードパーティのモジュールを作ることが可能な一方で、Titanium自身も、組み込みAPIのために内部的に同一のモジュールSDKを使用しています。モジュールアーキテクチャは以下の主要なインターフェースコンポーネントを含みます:
Proxy | JacaScriptコードとネイティブコードとの間のつながりを表現する基底クラス. |
ViewProxy | Viewをレンダーする方法を知っている特殊なProxy. |
View | TitaniumがレンダーするUIコンポーネントのビジュアル表現. |
Module | 特殊なAPIあるいは名前空間を記述する特殊なタイプのProxy. |
おのおののViewに対して1つのViewProxyが必要です。
ViewProxy は、(Proxy自身の中でViewが解放される必要がある場合でも保持される)モデルデータを表し、ViewがサポートするAPIやイベントを公開する責任があります。
JavaScriptとネイティブコードとの間で非ビジュアルなデータをやり取りする必要があるときにProxyが必要です。Proxyはメソッドやプロパティのディスパッチやイベントの起動をどう行うかを知るものです。
Proxy
Proxyを作成するには, インターフェース宣言でTiProxyを継承しなければなりません。importステートメントの中に"TiProxy.h"をインポートしてください。例えば、インターフェースは次のようになるでしょう:#import "TiProxy.h" @interface FooProxy : TiProxy { } @end |
#import "FooProxy.h" @implementation FooProxy @end |
Proxy メソッド
Proxiy は、標準的なObjective-C構文を使ってメソッドとプロパティを公開します。メソッドを公開するには、Proxyは以下のような正当なシグニチャのうち1つを持っていなければなりません:-(id)methodName:(id)args { } |
-(void)methodName:(id)args { } |
どちらのシグニチャの場合も、1つの引数を持ち、その型はNSArrayです。しかし、一般にidという宣言を使うことを推奨します。以下で述べるように入ってくる値を別の値にタイプキャストするのを容易にするでしょう。(訳注:「推奨する」といっていますが、実際にはほかの選択肢はありません。ほかのシグニチャを使った場合Titanumはメソッドと認識しません。)
もし関数が引数を持たない場合には、入ってくるargsの値を単に無視してかまいません。(訳注:「関数」といっていますが、メソッドのことです。モジュールの作成過程で、既存の関数からメソッドを作成することを想定してこういう表現になっていると思われます)
Titaniumは、NSArrayから入ってくる値から特定の型へタイプキャストしたり、値を抽出する便利なマクロを提供します。(訳注:原文はarrayとなっていますが、文意から明らかにNSArrayのことです)
ENSURE_SINGLE_ITEM(args,type) |
- 第一引数は(メソッドの)引数です (訳注:parameterとargumentの使い分けが混乱/間違っているような気がします。どちらも「引数」と訳しておきます。適当に括弧書きで「メソッドの」などを追加して区別します。)
- 第2引数は値がとるべき型の名前です
- 最初の(メソッドの)引数を抽出する (つまり[args objectAtIndex:0])
- 第2引数で渡された型へ1.の戻り値をキャストする
ENSURE_UI_THREAD_0 |
このマクロは戻り値を持たない場合にだけ使用できます。
ENSURE_UI_THREAD_1(arg) |
Titaiumは特定の値を変換したり検証したりするための便利なライブラリを提供します。このライブラリはTiUtilsの中にあり、次のようにしてインポートすることができます:
#import "TiUtils.h" |
CGFloat f = [TiUtils floatValue:arg]; NSInteger f = [TiUtils intValue:arg]; NSString *value = [TiUtils stringValue:arg]; NSString *value = [TiUtils stringValue:@ "key" properties:dict def:@ "default" ]; TiColor *bgcolor = [TiUtils colorValue:arg]; |
Proxy プロパティ
ProxyでJavaScriptプロパティを公開するには、単にObjective-Cのプロパティを使用します。@property(nonatomic,readwrite,assign) NSString* propertyName; |
-(void)setPropertyName:(id)value { } -(id)propertyName { return @ "foo" ; } |
getterメソッドでは、プロパティメソッドはNSObject, nilあるいはTiProxy(あるいはサブクラス)のいずれかとして値を返さなければなりません。
戻り値になるオブジェクト
以下のObjective-Cの型が変換なしに戻り値として使用できます:NSString | NSDictionary | NSArray |
NSNumber | NSDate | NSNull |
戻り値になるプリミティブな値
メソッドやプロパティからプリミチィブな値を返すためには、プリミティブな値を適切にラップされたプリミティブな値をもつNSNumberを返すべきです。Titaniumはこれを簡単にするためのマクロ群を提供します:Macro | Description |
NUMINT | Equivalent to [NSNumber numberWithInt:value] |
NUMBOOL | Equivalent to [NSNumber numberWithInt:value] |
NUMLONG | Equivalent to [NSNumber numberWithLong:value] |
NUMLONGLONG | Equivalent to [NSNumber numberWithLongLong:value] |
NUMDOUBLE | Equivalent to [NSNumber numberWithDouble:value] |
NUMFLOAT | Equivalent to [NSNumber numberWithFloat:value] |
戻り値としての複合的な値
複合的な値を返すには2つのアプローチがあります:- 第一のアプローチは、NSDictionaryに値をセットしてNSDictionaryを返すことです。NSDictionaryが返されるとTitaniumはこれをJavaScriptのオブジェクトに変換します。マップされたkey/valueは、JavaScriptのproperty/valueオブジェクトになります。
- 第二のアプローチは特別なProxyを作成することです。Proxyは関数とプロパティを持ったJavaScriptオブジェクトとして公開されるものとして返されます。返されたProxyに対する呼び出しは返されたProxyインスタンスに対する呼び出しになります。Proxyインスタンスを返す際、メソッドの中で作成したならautoreleaseにしておかなくてはなりません。
戻り値としてのファイル
ファイルシステムのファイルへの参照を返すには、TiFile プロキシのインスタンスを返すべきです。これはネイティブファイルとそのメッソッドとプロパティの公開を自動的に受け持ちます。ファイルを返すには、次の例のようにすることができます:return [[[TiFile alloc] initWithPath:path] autorelease]; |
戻り値としてのBlob
(NSDataのような)blobデータへの参照を返すには、TiBlobのインスタンスを返すべきです。これは、いくつかのネイティブなblob操作の公開を自動的に受け持ちます。blobを返すには、次の例のようにできます:return [[[TiBlob alloc] initWithData:nsdata mimetype:@ "application/octet-stream" ] autorelease]; return [[[TiBlob alloc] initWithImage:image] autorelease]; return [[[TiBlob alloc] initWithFile:file] autorelease]; |
戻り値としてのCGRect
CGRectを返すために, Titanium はTiRectという名前のプロキシを提供します。次の例のようにできます:TiRect *result = [[[TiRect alloc] init] autorelease]; [result setRect:rect]; return result; |
戻り値としてのCGPoint
CGPointを返すために、Titanium はTiRectという名前のプロキシを提供します。次の例のようにできます:return [[[TiPoint alloc] initWithPoint:point] autorelease]; |
戻り値としてのNSRange
NSRangeを返すために、Titanium はTiRangeという名前のプロキシを提供します。次の例のようにできます:return [[[TiRange alloc] initWithRange:range] autorelease]; |
戻り値としてのUIColor
UIColorを返すために、Titanium はTiColorという名前のプロキシを提供します。次の例のようにできます:return [[[TiColor alloc] initWithColor:color name:@ "#fff" ] autorelease]; |
Proxyの値の設定
JavaScript側でプロキシを作成する場合、 通常は、オプションでkey/value ペアのディクショナリを渡します。このパターンを使って、Titaniumは、プロキシのプログラミングを容易にする組み込みの機能を提供します。簡単な例を使ってみましょう:
var my_module = require( 'com.test' ); var foo = my_module.createFoo({ "bar" : "123" }); |
ComTestFooProxy.h ComTestFooProxy.m |
プロキシにおいて、barプロパティを扱うためにsetterメソッドを定義したいくなるかもしれません。(initでの)構築において、Titaniumは自動的にコンストラクタに渡されるすべてのプロパティに対するsetterを呼び出す。
#import "ComTestFooProxy.h"#import "TiUtils.h" @implementation ComTestFooProxy -(void)setBar:(id)value { NSString *str = [TiUtils stringValue:value]; } @end |
また、プロキシは、そのプロパティを扱ったり保持したりする仕方に関して特殊になります。プロキシは常に、'dynprops'と呼ばれる特別な内部のNSDictionaryに渡された値を保存します。これは、プロパティごとにgetter/setterを定義することなしにプロキシの値を扱うために次の方法を使うことができることを意味します:
id value = [self valueForUndefinedKey:@ "bar" ]; |
id value = [self valueForKey:@ "bar" ]; |
-(id)bar { return @ "456" ; } |
foo.bar = 789; |
- 定義済みのsetterがあれば、それが呼ばれる。
- setterの定義がなければ、プロパティと値はdynpropsに内部的に保存されるだろう。
-(void)setBar:(id)value { [self replaceValue:value forKey:@ "bar" notification:NO]; } |
イベント処理
プロキシは自動的にイベント発行とイベントリスナの管理を処理します。内部的にJavaScriptからプロキシのインスタンスに対してaddEventListener や removeEventListenerが呼ばれると、プロキシは自動的にイベントリスナを管理するコードを処理します。追加や削除時に通知が必要な場合には、メソッドをオーバーライドできる:
-(void)_listenerAdded:(NSString*)type count:(int)count { } -(void)_listenerRemoved:(NSString*)type count:(int)count { } |
_listenerRemoved メソッドはイベント名(type)と(削除されたリスナを除いた)同じタイプの残ったリスナの総数を引数として呼び出されるだろう。これは、実際にイベントを受信しているリスナがない場合にシステムリソースをクリーンナップするのに有用でしょう。イベントリスナにイベントを送るために、便利なメソッドを使うことができます:
-(void)fireEvent:(NSString*)type withObject:(id)obj; |
例:
-(void)mymethod{NSDictionary *event = [NSDictionary dictionaryWithObjectAndKeys:@ "foo" ,@ "name" ,nil]; [self fireEvent:@ "foo" withObject:event];} |
foo.addEventListener( 'foo' , function (e){ alert( "name is " +e.name); }); |
- source — イベントを発行する送信元オブジェクト(プロキシ)
- type — イベントタイプ
(void)fireMyEvent { if ([self _hasListeners:@ "foo" ]) { // fire event } } |
メモリ管理
Proxyは、Objective-Cクラスと同じように働き、すべてのメモリ管理規則が考慮されなければなりません。メソッドから新規のプロキシのインスタンスを返すとき、インスタンスをautoreleaseにしなければなりません。Titaniumは、結果となるJavaScriptの変数が参照への参照へマップするプロキシへの参照をretainします。しかしJavaScript変数はもはや参照可能でない場合、それは解放され、プロキシはdeallocメッセージを受け取ります。Titaniumでは、オブジェクトのretain/releaseには特に注意を払わなければならないのは、Objective-Cベースのプログラムと同じです。不適切なretain/releaseはクラッシュを引き起こしたり、望まない結果を生じさせます。
View Proxy
ViewProxyはProxyの特殊化でありViewのために使用されます。Viewとはスクリーン上に描画するUIとインタラクトするオブジェクトです。View Proxy はデータ (model)を保持し、viewに対してプロパティ変更やメソッドをディスパッチするcontrollerのように働きます。Viewは、UIKitとインタラクトしたりUIKitイベントを扱ったりするための内部的なロジックを扱います。ViewはView Proxyへのモデル委譲です。そして、参照されている限りは、プロパティ変更を受信します。
View Proxyは、常にViewへの正当な参照を保有しているとは限りません。Viewは、必要なときに要求に応じてだけ生成されます。Viewが生成されView Proxyによって保有された場合、View Proxyへのすべてのプロパティ変更イベントは、Viewへと先渡しされます。
Viewのプロパティメソッドは特殊な必須の規約を使って名付けられます。
-(void)setFoo_:(id)value { } |
プロパティ名の後置辞はアンダースコアで終わらなければなりません。Viewプロパティメソッドの場合、Viewへの呼び出しは常にUIスレッド上にあるでしょう。 |
ところで、Viewは、View自身がViewに処理させたいメソッドを持たねばなりませんし、ディスパッチしなければなりません(訳注:itがなんなの不明。Viewとして訳す。この辺りの記述は意味不明。全体の話のはがれでは、プロパティ以外のメソッドに話が移ります)。次の例のコードでこのことが正常に処理できます:
(void)show:(id)args { [[self view] performSelectorOnMainThread:@selector(show:) withObject:args waitUntilDone:NO]; } |
USE_VIEW_FOR_UI_METHOD(methodName) |
USE_VIEW_FOR_UI_METHOD(show) |
View
View の実装はTiUIView クラスを拡張しなければなりません。TiUIView クラスはUIViewを拡張して、Titanium固有の機能を提供します。新しいViewを定義します。例えば:
#import "TiUIView.h"@interface ComTestFooView : TiUIView { } @end |
#import "ComTestFooView.h" @implementation ComTestFooView @end |
ViewとView Proxyの作成
Viewを作成するには、Xcodeでメニュー File->New File... を選択して、現れた次のファイル選択ダイアログを選びます:左側のウインドウペインにあるiPhone OS 配下にAppcelerator itemが現れるはずです。右側のウインドウペインのおのおののアイコンが、Appcelerator固有のテンプレートから素早く生成できる様々なファイルタイプを提供しています。"TiUIView" を選んで "Next" ボタンをクリックします。
次のダイアログで、ファイル名 "ComTestView.m" を入力し、プロジェクトディレクトリと "Classes" サブディレクトリを選択します。"Choose..." ボタンを使ってファイル選択できます。
この時点で、Xcodeプロジェクトに2つのファイルができているはずです。
おそらく、独自の事情で、デフォルトの著作権常用文を変更したいでしょう(訳注:生成されたソースコードの先頭のAppceleratorのコピーライト表示を変更してもかまわないといいたいと思われます) 。この時点でコードに有用なこをとさせる準備は整っています。
この例ではcolorプロパティがセットされた子サブビューとして矩形がアタッチされただけのViewを作成します。ComTestView.hで次のコードを使います:
#import "TiUIView.h" @interface ComTestView : TiUIView { UIView *square; } @end |
#import "ComTestView.h" #import "TiUtils.h" @implementation ComTestView -(void)dealloc { RELEASE_TO_NIL(square); [ super dealloc]; } -(UIView*)square { if (square==nil) { square = [[UIView alloc] initWithFrame:[self frame]]; [self addSubview:square]; } return square; } -(void)frameSizeChanged:(CGRect)frame bounds:(CGRect)bounds { if (square!=nil) { [TiUtils setView:square positionRect:bounds]; } } -(void)setColor_:(id)color { UIColor *c = [[TiUtils colorValue:color] _color]; UIView *s = [self square]; s.backgroundColor = c; } @end |
これは簡単な例なので、今はテンプレートのコードをあるがままにしておきます。
新しいViewをテストするためには、JavaScript でアプリケーションロジックを書く必要があります。簡単なViewをテストするので、titaniumコマンドラインツールを使って、素早くテストすることができます。以下のJavaScriptコードをプロジェクトのexampleディレクトリにあるapp.jsファイルに追加します:
var my_module = require( 'com.test' ); var foo = my_module.createView({ "color" : "red" , "width" :20, "height" :20 }); window.add(foo); |
これを実行するためには、コンソールでモジュールプロジェクトディレクトリから次のコマンドをタイプするだけです:
> titanium run |
テストコートについての注:
- TitaniumのViewにおいて、通常推奨されるのは、(この例での"square"にあたる)クラスメンバ変数として、メインのUIViewへの参照を保持しておき、Viewをロードし、必要に応じて自身(TiUIView)にViewがアタッチすることです。この例ではプロパティがセットされたときにだけViewにアタッチされています。
- "frameSizeChanged:bounds:"という特別のメソッドを実装しなければなりません。このメソッドはTitaniumの中でframe/bounds/centerが変更されるたびに呼ばれます。Titaniumは特別なレイアウトエンジンを持っており、このメソッドを使ってViewのframeやboundsに変更を通知すべきです。"setBounds:", "setFrame:" や "setCenter:"をオーバーライドしてはいけません。これらのメソッドをオーバーライド場合には、TiUtilesヘルパークラスに対して"setView:positionRect:"という特別なメソッドを呼ばなければなりません。このメソッドはViewの新しいboundsの正しい境界内に子ビューを正しくレイアウトするでしょう。
- 生成のためのViewの名前パターン(モジュールに対するメソッド"createView")を使っているので、モジュール内でこのメソッドを定義する必要はありません。Titaniumは自動的に名前規約を使ってこれを実行します。しかし、そうせずに、もし"createFooView"という名前のメソッドを作成したいなら、モジュール内でこのメソッドを定義して、Proxyを直接生成する必要があります。
特殊なコンパイラ記述子の追加
(訳注:以下の記述で、モジュールのプロジェクトとテストアプリケーションのプロジェクトが出てきますが、記述がいささか混線しています。注意深く読む必要があります)モジュールが特別なFrameworkやその他のシステムライブラリや特殊なコンパイラ記述子を必要としているなら、モジュールのxcconfig ファイルを使ってそれらを定義できます。Titaniumは自動的にmodule.xcconfig というファイルとtitanium.xcconfigとを生成します。titanium.xcconfig はパッケージするためにコンパイル時に使用されます。一方、module.xcconfig は、アプリケーションビルド時に、モジュールが参照されていれば、アプリケーションのコンパイラによって使用されます。これは、この過程(訳注:テストアプリケーションの作成過程)でもコンパイラ記述子をコントロールできるようにします。
このことを示すために、簡単な例を見てましょう。まず、Frameworkを追加してみましょう。
まず、XcodeでFrameworkを追加する必要があるでしょう。この例では、GameKitを追加します。プロジェクトフォルダ内でFrameworksを選択し、右クリックして、"Add -> Existing Frameworks" を選びます。
これで次のダイアログが現れるはずです:
"GameKit.framework"を選択して"Add" ボタンをクリックします。Frameworkフォルダ内に新しいFrameworkの参照が現れているはずです:
今の状態では、プロジェクトをコンパイルすると、エラーとなります。
プロジェクトにFrameworkを追加したので、コンパイル時にアプリケーションコンパイラがこのFrameworkをインポートできるようにセットアップする必要があります。これをするには、module.xcconfig を編集して、最後の方に以下の行を追加します:
OTHER_LDFLAGS=$(inherited) -framework GameKit |
アプリケーションコードから始めましょう。Titaniumではダイアログからの値の選択をサポートするAPIが設計されているので、通常推奨されるのは、"success", "error" と "cancel"というキーに値としてコールバック関数をオブジェクトに渡すことです。
コードでは以下のようになります:
my_module.start( 'Jeff' ); my_module.showChooser({ success: function (e) { alert( "peer choosen: " +e.peer); }, cancel: function (e){ alert( "cancelled" ); } }); |
今度は、ネイティブコードへの追加を見てみましょう。 ComTestModule.m と ComTestModule.h ファイルで上記のことが行われます。既に定義されたメッソッドの後で"@end"ステートメントの前に 以下のコードをペースとしてください:
#pragma mark Internal -(void)dealloc { if (session!=nil) { [session disconnectFromAllPeers]; RELEASE_TO_NIL(session); } RELEASE_TO_NIL(peer); if (controller!=nil) { [controller performSelectorOnMainThread:@selector(dismiss) withObject:nil waitUntilDone:NO]; RELEASE_TO_NIL(controller); } [ super dealloc]; } #pragma Public APIs -(void)start:(id)name { ENSURE_SINGLE_ARG(name,NSString); NSString *sessionid = [TiUtils createUUID]; [[GKSession alloc] initWithSessionID:sessionid displayName:name sessionMode:GKSessionModePeer]; } -(void)stop:(id)args { if (session!=nil) { [session disconnectFromAllPeers]; RELEASE_TO_NIL(session); } } -(void)showChooser:(id)args { ENSURE_UI_THREAD_1_ARG(args); ENSURE_SINGLE_ARG(args,NSDictionary); id success = [args objectForKey:@ "success" ]; id cancel = [args objectForKey:@ "cancel" ]; RELEASE_TO_NIL(successCallback); RELEASE_TO_NIL(cancelCallback); successCallback = [success retain]; cancelCallback = [cancel retain]; controller = [[GKPeerPickerController alloc] init]; controller.delegate = self; [controller show]; } #pragma mark Delegates /* Notifies delegate that a connection type was chosen by the user. */ - (void)peerPickerController:(GKPeerPickerController *)picker didSelectConnection-Type:(GKPeerPickerConnectionType)type { } /* Notifies delegate that the connection type is requesting a GKSession */ - (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnection-Type:(GKPeerPickerConnectionType)type { return session; } /* Notifies delegate that the peer was connected to a GKSession. */ - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString*)peerID toSession:(GKSession *)session_ { RELEASE_TO_NIL(peer); peer = [session_ retain]; if (successCallback) { NSDictionary *event = [NSDictionary dictionaryWithObjectsAnd- Keys:peerID,@ "peer" ,nil]; [self _fireEventToListener:@ "success" withObject:event listener:successCallback thisObject:nil]; } } /* Notifies delegate that the user cancelled the picker. */ - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker { if (cancelCallback!=nil) { [self _fireEventToListener:@ "cancel" withObject:nil listener:cancelCallback this -Object:nil]; } } } |
次のメソッドstartはGKSessionを開始してメンバ変数sessionに参照を保持します。startメソッドはマクロ ENSURE_SINGLE_ARG を使って、引数を変換し、それをほどいて、第一引数として変数 "name"に入れます。
次のメソッド stop は多くの接続したピアからセッションを切断して、参照を解放します。
次のメソッド showChooser はUIダイアログへの責務を負っています。モジュールメソッドは非UIスレッドで実行されていますが、すべてのUIKitメソッドはメインUIスレッド上で実行されなければなりません。この場合、マクロ ENSURE_UI_THREAD_1_ARG を使って、UIスレッドでメソッドが実行されるようにします。第2のマクロ ENSURE_SINGLE_ARG は、第一引数としてNSDictionary に変換して渡します。
コールバックを抜き出し、既に潜在的に保持しているメモリを解放し、クラスのメンバ変数として引数を保持します。それらを、後でネイティブコードから関数が呼び出したいときに使います。GameKitの残りのコードは、コントローラを生成したり、デレゲートとして登録したり、チューザーを表示したりしています。
残りのメソッドは、自前で実装した追加的なGameKitチューザーデレゲートメソッドです。"peerPicker-Controller"メソッドはピアが選択されたとき呼ばれます。この場合"success"コールバックにいくつかのデータとともにイベントを発行します。JavaScriptイベントに渡されたデータは、 key/valueペアのNSDictionary として渡されます。キーのおのおのはコールバック関数に渡されるイベントオブジェクトのプロパティになります。Proxy メソッド_fireEventToListener:withObject:listener:thisObject: を呼んで、コールバックメソッドを呼び出し、それにデータを渡すことができます。追加的なイベントデータが何もないなら、"withObject" に対してnilを渡すこともできます。
さて、練習を完成させるために、ComTestModule.h のインターフェースコードを追加しましょう:
#import "TiModule.h"#import <GameKit/GameKit.h> @interface ComTestModule : TiModule <GKPeerPickerControllerDelegate> { GKSession *session; GKSession *peer; GKPeerPickerController *controller; KrollCallback *successCallback; KrollCallback *cancelCallback; } @end |
さて、"Cancel" ボタンを押して、 cancel コールバックを呼び出してみましょう。次の結果になるはずです:
cancelが現れたら、万事良好です。動いています!
モジュールアセットの構築
モジュールとともにモジュールアセットを配布するには、プロジェクトの "assets"ディレクトリにそれらを配置しなければなりません。このフォルダ内にあるどのアセットも(後で見るようにJavaScriptファイル以外は)配布され、アプリケーションバンドルの"module/<moduleid>"というパターンのフォルダにコピーされます。Objective-Cコードからは、この相対パスを使ってアセットをロードできます。例えばfoo.png"という名前のモジュール画像がある場合、以下の例のようにロードすることができるでしょう:NSString *path = [NSString stringWithFormat:@ "modules/%@/foo.png" ,[self moduleId]];NSURL *url = [TiUtils toURL:path proxy:self];UIImage *image = [TiUtils image:url proxy:self]; |
JavaScript ネイティブモジュールの構築
(訳注:"JavaScript native module"という用語は、誤解を与える表現なので、単に"JavaScript module"と呼ぶべきではないか?)
Titaniumモジュールのすばらしい点の一つが完全にネイティブでも完全にJavaScript でもありうることです。今まで、もっぱら完全にネティブなモジュールについて議論してきました。
しばしばネイティブモジュールを作成したいが、JavaScriptで書いて実装しコンパイルしたモジュールとして配布したいことがあります。Titaniumでは、モジュールのコードを"assets"ディレクトリ内の"<module_id>.js"と名付けたファイル内でモジュールコードを書くことができます。 サンプルプロジェクトの名前に従えば、そのファイル名は"com.test.js"となるでしょう。モジュールファイルは、モジュールの宣言とイクスポートにCommonJS フォーマットを使用しなければなりません。一つのプロパティと一つの関数を定義したとても簡単な例から始めましょう:
exports.prop1 = 123; exports.func = function (v) { return "hello : " + v; }; |
さて、モジュールを利用するために、テストアプリケーションのコードを修正します。既にあるこのファイルにある(訳注:どれだかわからない)コード変更を取り去って、次のものと置き換えましょう:
var my_module = require( 'com.test' ); var result = my_module.func( "prop1 is " + my_module.prop1 ); alert(result); |
CommonJS は、(モジュールが読み込まれる以前に事前定義された)特別な"exports"オブジェクトに対して定義することで、1つ以上のプロパティ/関数をクリーンにイクスポートできるようにします。"exports"オブジェクトの外側で定義されたメソッドやプロパティはどんものでも、モジュールのスコープの中では考慮されますが、モジュールの外側からは見えなくなるでしょう。
ステップ3:モジュール配布のためのパケージング
Titanium モジュールは、開発者自身の配布メカニズムの内部でも、Titanium+Plus marketplaceのような外部でも 、配布のためのビルドが容易になるように生成されます。モジュールの記述
Titanium モジュールメタデータは "manifest"と名付けられた特別のファイルに記述されます。このファイルは単純なkey/value プロパティフォーマットになっています。テストモジュールの例をあげます:# # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # version: 0.1 description: My module author: Your Name license: Specify your license copyright: Copyright (c) 2010 by Your Company # these should not be edited name: test moduleid: com.test guid: 5bfdfd9b-12fe-42a4-bf2f-6ce7841fb4ae platform: iphone minsdk: 1.4.0 |
エントリ | 説明/目的 |
version | モジュールのバージョン。メジャーな変更がある場合にその都度この値を変更して配布すべきです。versionはドット記法で書かれます (X.Y.Z)。空白を含んではいけません。数字以外の文字は使用できません。 |
description | モジュールの人間に読める記述。モジュール名ととも表示するための、短く表示可能なものであるべきです。 |
author | モジュールをともに表示するための人間に読める作者名。単に "Jeff Haynie"のような個人名にしてもよいし、"Appcelerator"のような組織の名称にしてもよい。 |
license | 人間に読めるライセンスの名称。"Apache Public License" や "Commercial"のように短く記述すべきです。 |
copyright | 人間に読めるモジュールの著作権文字列。例えば、"Copyright (c) 2010 by Appcelerator, Inc." |
name | プロジェクトを作成したときに生成された変更不可のモジュールの名前。この値を変更すべきでありません。 |
moduleid | プロジェクトを作成したときに生成された変更不可のモジュールのID。この値を変更すべきでありません。注意:一意なIDを生成すべきです。推奨されるのは、一意性を担保するためのパターンとして、反転DNS企業名にモジュール名を加えたものを使うことです。Titanium Marketplaceは、配布の際に、一意なモジュールIDだけを許可します。この値を編集しなければならないなら、モジュールの実装ファイルの中の値も編集しなければなりません。 |
guid | プロジェクトを作成したときに生成された変更不可の一意なモジュールID。この値を変更すべきでありません。 |
platform | プロジェクトを作成したときに生成された変更不可のモジュールのプラットフォームターゲット。この値を変更すべきでありません。 |
minsdk | モジュール作成時に使用された最小のTitanium SDKのバージョンを表す生成された値。現状では使用されないが将来のために予約。 |
モジュールの手動配布
手動でモジュールを配布するには、 "build.py" を使ってモジュールを単純にビルドしてzipファイルを直接配布すればいいでしょう。モジュールは /Library/Application\ Support/Titanium ディレクトリにコピーされ、(プロジェクト内で参照され、プロジェクトがビルドされると)インストールされます。4ページの注意と同様、Titanium SDKが<t1>/Library</t1>の代わりに<t0>~/Library</t0>にインストールされていれば、/Library/Application\ Support/Titanium ではなくて ~/Library/Application\ Support/Titanium にモジュールをコピーする必要があります。 |
Titanium+Plus Marketplace 経由の配布
(訳注:この項は、「構想」が書かれている。現在(2011/9)準備中のAppcelerator Marketplaceが公開される予定である。しかし配布の様式など、変更されていると思われる)
Titanium+Plus Marketplaceを通じてモジュールを配布するには、まず普通にパッケージしておく必要があります。ローカル環境でのモジュールのテストがすんでいて、配布の準備ができていれば、配布のためにマーケットプレースに登録できます。配布可能になるためにはいくつかの事前の必須項目があります:- 正当な Titanium デベロッパアカウントが必要です
- manifest の値を完全に埋めておかなければなりません
- プロジェクトのLICENSE ファイルに正当なライセンス文書をおかなければなりません
- プロジェクトのドキュメンテーションディレクトリのindex.md ファイルに正当なドキュメンテーションをおかなければなりません
- アップロードの際に(フリーの可能ですが)価格のような追加のメタデータを指定しなければなりません
- モジュールに課金する場合には、Appceleratorが支払い可能な支払い方法の設定を確立しておく必要があります
- Titanium+Plus Marketplace のサービス規約に同意しなければなりません
初めてモジュールを登録する場合は、上記の基本的な必須項目についてレビューが行われます。しかし、一旦承認されれば、更なる承認なしに直ちに登録されるでしょう。
文書の終わり
AppceleratorのサイトのURLがかわってしまったので、画像のリンクが切れています。そのうち直します。
返信削除