1. iOS and OS X
ここからは、iOS や OS X の RubyMotion プロジェクトの動力となっている RubyMotion ランタイムの一つを扱います。
1.1. Object Model
RubyMotion のオブジェクトモデルは Objective-C をベースとし、iOS と OS X SDK の基本的な言語ランタイムに基づいています。Objective-C は C 言語にオブジェクト指向を取り入れ、Ruby と同じく、Smalltalk から強く影響を受けています。
Smalltalk という祖先を共有することで、Ruby と Objective-C のオブジェクトモデルは概念が似ています。たとえば、両方の言語にオープンクラス、単一継承、単一の動的なメッセージ送信があります。
RubyMotion では Ruby のオブジェクトモデルを Objective-C runtime や iOS SDK の API を使用し実装しています。 Objective-C runtime は Objective-C 言語と iOS SDK API に間接的に力を与えているライブラリです。
RubyMotion では、Ruby クラスのメソッドやオブジェクトは、Objective-C クラスのメソッドやオブジェクトでもあります。逆のこともまた真実と言えます。Objective-C クラスのメソッドやオブジェクトは Ruby ネイティブのように利用できます。
同じオブジェクトモデル構成を共有することで、Objective-C と RubyMotion の API はパフォーマンスの犠牲なしで相互に乗り入れできます。
1.1.1. Objective-C Messages
RubyMotion では Objective-C のメッセージ定義やメッセージ送信ができます。
セレクタとも呼ばれる Objective-C のメッセージは、複数の引数をとる場合において一般的な Ruby とは異ります。
Objective-C のメッセージには、Ruby のメッセージと異なり、キーワードがあります。各引数は関連づけられているキーワードを持ち、最終的に Objective-Cのメッセージは、すべてのキーワードの組み合わせとなります。
文字列を描画する Objective-C コード片を取り上げてみましょう。
[string drawAtPoint:point withFont:font];
このコードでは string と point 、 font が変数です。メッセージのキーワードは drawAtPoint: と withFont: です。完全なメッセージはこれらのキーワードの組み合わせの drawAtPoint:withFont: で、point と font オブジェクトが引数として string オブジェクトにメッセージが送信されます。
Objective-C のメッセージは RubyMotion から同じような構文で送信できます。
string.drawAtPoint(point, withFont:font)
ここで drawAtPoint:withFont: が送信するメッセージと思い描いてください。より詳しく説明するために、send メソッドを用いて同じメッセージを送信してみましょう。
string.send(:'drawAtPoint:withFont:', point, font)
Objective-C のメッセージは RubyMotion でも定義できます。プロキシとして動作するクラスに、描画するためのコードを構築するところをイメージしてください。 次のコード片は、DrawingProxy クラスに drawAtPoint:withFont: メッセージを定義します。
class DrawingProxy def drawAtPoint(point, withFont:font) @str.drawAtPoint(point, withFont:font) end end
Objective-C のセレクタを定義するために用いられる構文は RubyMotion に追加され、これは Ruby の標準ではありません。 |
1.1.2. Objective-C Interfaces
Objective-C は iOS SDK を記述するメイン言語として使われ、Objective-C インタフェースの読み方や Ruby から使う方法について学ぶ必要があります。
Objective-C インタフェースは Apple の iOS API リファレンスや、iOS framework のヘッダファイル (.h) で見ることができます。
Objective-C インタフェースは、クラスメソッドを宣言する場合は +、インスタンスメソッドを宣言する場合は - の文字から始まります。
たとえば、以下は Foo クラスに foo というインスタンスメソッドを宣言するものです。
@class Foo - (id)foo; @end
次は、同じクラスに foo クラスメソッドを宣言するものです。
@class Foo + (id)foo; @end
(id) は型情報です。型については、あとのセクションで説明します。 |
前のセクションでの説明のように、Objective-C のメソッドの引数はキーワードを持つことができます。すべてのキーワードをあわせたものが、メッセージ名やセレクターを形成します。
以下は、sharedInstanceWithObject:andObject: クラスメソッドを Test クラスに宣言するインタフェースです。
@class Test + (id)sharedInstanceWithObject:(id)obj1 andObject:(id)obj2; @end
このメソッドは Ruby から次のように呼び出すことができます。
# obj1 and obj2 are variables for the arguments. instance = Test.sharedInstanceWithObject(obj1, andObject:obj2)
1.1.3. Selector Shortcuts
RubyMotion ランタイムは特定の Objective-C セレクタに対して、便利なショートカットを用意しています。
Selector | Shortcut |
---|---|
setFoo: |
foo= |
isFoo |
foo? |
objectForKey: |
[] |
setObject:forKey: |
[]= |
例として、UIView の setHidden: と isHidden メソッドをショートカットを用いて呼び出してみましょう。ショートカットを用いるとそれぞれ hidden= と hidden? となります。
view.hidden = true unless view.hidden?
1.1.4. Super
Objective-C では、super は現在のクラスのスーパークラスへメソッドを送信し、メソッドを実行します。以下の例では、super は MediaController へ song という引数と一緒に play メッセージを送信します。
@interface VideoController : MediaController @end @implementation VideoController - (void)play:(NSString *)song { [super play:song]; // Customize here... } @end
RubyMotion では、super は現在のクラスのスーパークラスへメソッドを送信し、 super を使用しているメソッドと同じ引数 で、同じ名前のメソッドを実行 します。
class VideoController < MediaController def play(song) super # Customize here... end end
Objective-C では、スーパークラスのメソッドを呼び出し現在のメソッドへバイパスすることができます。これは特に init メソッドを定義するときに便利です。
@interface CustomView : UIView @property (copy) NSString *text; @end @implementation CustomView - (id) initWithFrame:(CGRect)frame { [super initWithFrame:frame]; self.text = @""; return self; } - (id) initWithText:(NSString*)text { UIFont *font = [UIFont systemFontOfSize:12]; CGRect size = [text sizeWithFont:font]; // skip local initializer [super initWithFrame:{{0, 0}, size}]; self.text = text; return self; }
スーパークラスの initWithFrame を呼び出すわざとらしい例です。定義したメソッドを呼ばずに、スーパークラスのメソッドを呼び出す必要があるケースがあるでしょう。
これは Ruby では簡単に実現できません (see How do I call a super class method on stack overflow)。Ruby では、alias で別名のメソッドを用意し、それを代わりに呼び出す必要があります。
class CustomView < UIView alias :'super_initWithFrame:' :'initWithFrame:' # if your method takes multiple arguments, you can alias that as well # alias :'renameWithArg1:arg2' :'methodWithArg1:arg2' def initWithFrame(frame) super.tap do @text = '' end end def initWithText(text) font = UIFont.systemFontOfSize(12) size = text.sizeWithFont(font) super_initWithFrame([[0, 0], size]).tap do @text = text end end end
1.2. Builtin Classes
いくつかの RubyMotion の組み込みクラスは、iOS と OS X のベースレイヤーである Foundation framework をベースとしています。
Foundation framework は Objective-C のフレームワークですが、RubyMotion が Objective-C runtime をベースとしてあるおかげで、組み込みクラスの定義で自然に Foundation framework が利用されています。
Foundation クラスは一般的に NS というプレフィックスから始まります。これはフレームワークが構築された当初のシステムである NeXTSTEP を表します。
Foundation ではプリミティブなオブジェクトクラスと同様に NSObject がルートクラスとしてあります。RubyMotion では、Object は NSObject の別名で、NSObject はすべての Ruby クラスのルートクラスとなっています。また、いくつかの組み込みクラスは Foundation から継承しています。
ここで、いくつかの組み込みクラスと、新規に Hello というクラスを作成した場合で、それぞれのクラスの祖先がどのようになるか示します。
Ruby の組み込みクラスを Foundation 上に実装した結果、インスタンスがより多くのメッセージに応答するようになります。 たとえば、NSString クラスは uppercaseString メソッドを定義します。String クラスは NSString のサブクラスなので、Ruby で作成した文字列もそのメソッドに応答します。
'hello'.uppercaseString # => 'HELLO'
組み込みクラスの Ruby インタフェースは、それぞれ対応する Foundation 上に実装されます。例として、Array の each メソッドは NSArray 上に実装されています。これにより、オブジェクトが Ruby で作られたのか Objective-C API で作られたのかに関係なく、常に同じインタフェースで応答することができます。each はいつでもすべての Array オブジェクトで動きます。
def iterate(ary) ary.each { |x| p x } end iterate [42] iterate NSArray.arrayWithObject(42)
しかし、この設計の主な目的は Objective-C と Ruby との間でデータ変換が不要なため、パフォーマンスの犠牲なしにプリミティブな型の変換を行えることにあります。典型的なアプリケーションで変換される型の多くは組み込み型なので、このことは重要です。Ruby で作成した String オブジェクトは、NSString を期待する Objective-C メソッドの引数にできるメモリアドレスを持っています。
1.2.1. Mutability
Foundation framework は mutable と immutable の両方をセットとして持っています。mutable オブジェクトは変更することができます。一方、immutable オブジェクトは変更できません。
RubyMotion では、immutable は凍結されたオブジェクトのように振る舞います (freeze メッセージを受信したオブジェクトのような振る舞いです)。
例として、NSString の内容を変更することは禁止され、変更しようとすると例外が発生します。しかし、mutable である NSMutableString インスタンスの内容は変更できます。
NSString.new.strip! # raises RuntimeError: can't modify frozen/immutable string NSMutableString.new.strip! # works
RubyMotion で作られる文字列は NSMutableString を継承するので、文字列を変更することができます。Array や Hash も同様です。
しかし、iOS SDK API はごく一般的に immutable なオブジェクトを返すので注意しなければなりません。この場合、dup や mutableCopy メッセージを送信することで、mutable なオブジェクトを取得できます。そして内容を変更できるようになります。
1.3. Interfacing with C
RubyMotion を使用する際には、あなたが C プログラマである必要はありませんが、このセクションで説明されるいくつかの基本概念が必要となります。 Objective-C は C 言語のスーパーセットです。そのため、Objective-C メソッドは C 言語の型を受け付けたり、返したりします。
また、iOS SDK で使用されるメインのプログラミング言語は Objective-C ですが、いくつか C API でのみ使用できるものがあります。
RubyMotion は Ruby で C API を利用する対応するためのインタフェースがあります。
1.3.1. Basic Types
C 言語や間接的に Objective-C は変数に型があります。iOS SDK API では、型がついた変数を受け付けたり返したりするのが一般的です。
以下に例として NSFileHandle のメソッドを示します。このメソッドは、C 言語の整数型 int を受け付けて返します。
- (id)initWithFileDescriptor:(int)fd; - (int)fileDescriptor;
基本的な C 言語の型付きの変数は Ruby から直接生成することができませんが、それと等しい Ruby の型によって自動的に変換されます。
以下のコード片は、上記 NSFileHandle の二つのメソッドを使用します。はじめに、 initWithFileDescriptor: メソッドが Fixnum のオブジェクトを引数として呼び出されます。つぎに、 fileDescriptor が呼び出され、 Fixnum のオブジェクトを Ruby に返します。
handle = NSFileHandle.alloc.initWithFileDescriptor(2) handle.fileDescriptor # => 2
この表は、すべての基本的な C 言語の型が、どのように Ruby の型と変換されるかを説明します。
C Type | From Ruby to C | From C to Ruby |
---|---|---|
bool |
オブジェクトが false または nil の場合、 false 。 それ以外の場合、 true 。 Note: Fixnum の 0 は C の型に変換されると true と扱われるので注意。 |
true または false 。 |
BOOL |
||
char |
オブジェクトが Fixnum または Bignum の場合、その値が返されます。オブジェクトが true または false の場合、それぞれ 1 または 0 が返されます。 オブジェクトが to_i メッセージに応答できる場合、to_i メッセージを送信しその結果が返されます。 |
Fixnum または Bignum オブジェクト。 |
short |
||
int |
||
long |
||
long_long |
||
float |
オブジェクトが Float の場合, その値が返されます。オブジェクトが true または false の場合、それぞれ 1.0 または 0.0 が返されます。オブジェクトが to_f メッセージに応答できる場合、to_f メッセージを送信しその結果が返されます。 |
Float オブジェクト。 |
double |
C 言語の void 型を返す API を使用するとき、RubyMotion は nil を返します。
1.3.2. Structures
C 言語の構造体は RubyMotion ではクラスとしてマッピングされています。Ruby で生成した構造体のインスタンスは、C 言語の構造体を期待する API で使用できます。同様に、C 言語の構造体を返す API は、適切な構造体クラスのインスタンスを返します。
構造体クラスは C 言語の構造体の各フィールドをラップしたアクセサメソッドを持っています。
例として、以下に CGPoint 構造体のインスタンスを生成するコードを示します。インスタンスの x と y フィールドを設定し、drawAtPoint:withFont: メソッドへ渡すコードとなっています。
pt = CGPoint.new pt.x = 100 pt.y = 200 'Hello'.drawAtPoint(pt, withFont: font)
フィールドの値はコンストラクタに直接渡すこともできます。
pt = CGPoint.new(100, 200) 'Hello'.drawAtPoint(pt, withFont: font)
RubyMotion は Array オブジェクトも受け付けます。この場合、構造体の各フィールドと一致する値を Array に含む必要があります。
'Hello'.drawAtPoint([100, 200], withFont: font)
1.3.3. Enumerations and Constants
C 言語の列挙子や定数は Object クラスの定数としてマッピングされます。
たとえば、NSNotFound と CGRectNull (それぞれ Foundation フレームワークで列挙子と定数が定義されています) は直接使用することができます。
if ary.indexOfObject(obj) == NSNotFound # ... end # ... view = UIView.alloc.initWithFrame(CGRectNull)
iOS と OS X SDK でいくつか英小文字から始まるの列挙子や定数が定義されています。たとえば、kCLLocationAccuracyBest (小文字の k で始まる)。Ruby では定数は英大文字から始まらなければいけません。RubyMotion では KCLLocationAccuracyBest (大文字の K で始まる) と定数は英大文字から始まるように変換されます。 |
locationManager.desiredAccuracy = kCLLocationAccuracyBest # NameError: undefined local variable or method locationManager.desiredAccuracy = KCLLocationAccuracyBest # works
1.3.4. Functions
C 言語の関数は、Object クラスのメソッドとして使用できます。フレームワークのヘッダファイルで実装されているインライン関数もサポートしてます。
例として、CGPointMake 関数は Ruby で CGRect 構造体を生成するのに使用できます。
pt = CGPointMake(100, 200) 'Hello'.drawAtPoint(pt, withFont: font)
iOS と OS X SDK の多くの関数は、英大文字から始まります。引数を取らない関数の場合には、定数として評価されないようにするため、明示的に括弧を使用することが重要です。 |
NSHomeDirectory # NameError: uninitialized constant NSHomeDirectory NSHomeDirectory() # works
1.3.5. Pointers
ポインターは C 言語において非常に基本的なデータタイプです。概念的に、ポインターはオブジェクトを指し示すメモリアドレスです。
iOS と OS X SDKでは、ポインターは引数や戻り値のオブジェクトを参照する際に一般的に使われています。例として、以下の NSData のメソッドで error 引数は、エラーが発生した際にポインターが設定されることが期待されます。
- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)mask error:(NSError **)errorPtr
RubyMotion は、ポインターを扱うために Pointer クラスを導入しています。 ポインターを作成する際、ポインターの型を new コンストラクタに与えなる必要があります。ポインターのメモリアドレスを間接参照するために [] を、新たな値を割り当てるために []= を使うことができます。
上記の NSData メソッドは Ruby で次のように使用することができます。
# Create a new pointer to the object type. error_ptr = Pointer.new(:object) unless data.writeToFile(path, options: mask, error: error_ptr) # De-reference the pointer. error = error_ptr[0] # Now we can use the `error' object. $stderr.puts "Error when writing data: #{error}" end
上記のケースでは、オブジェクトへのポインタを必要とします。 Pointer.new はさまざまな C 言語の基本的な型はもちろん構造体へのポインタを作成することもできます。
Pointer.new は Objective-C runtime types で示される String かショートカットの Symbol でなければなりません。ショートカットを使うことをおすすめします。
以下の表は、RubyMotion で作ることができるポインタをまとめたものです。
C Type Pointer | Runtime Type String | Shortcut Symbol |
---|---|---|
id * |
"@" |
:object |
BOOL * |
"B" |
:bool / :boolean |
char * |
"c" |
:char |
unsigned char * |
"C" |
:uchar |
short * |
"s" |
:short |
unsigned short * |
"S" |
:ushort |
int * |
"i" |
:int |
unsigned int * |
"I" |
:uint |
long * |
"l" |
:long |
unsigned long * |
"L" |
:ulong |
long long * |
"q" |
:long_long |
unsigned long long * |
"Q" |
:ulong_long |
float * |
"f" |
:float |
double * |
"d" |
:double |
RubyMotion は構造体のポインタ生成もサポートします。type メッセージを構造体のオブジェクトに送信することで、構造体の型を取得できます。これを Pointer.new へ渡すと、構造体のポインタが生成できます。たとえば、次のコード片は CGRect 構造体のポインタを生成するものです。
rect_ptr = Pointer.new(CGRect.type)
C 言語の文字列へのポインタは、RubyMotion によって String と自動的に相互変換されます。
1.3.6. Function Pointers and Blocks
RubyMotion では、C の関数ポインタや C Blocks を受け付ける C や Objective-C API は、引数に Proc オブジェクトを渡すことで呼び出すことができます。
iOS と OS X SDKにおいて、関数ポインタはレアな存在ですが、C Blocks は一般的に使われています。 C Blocks は Apple によって定義された C 言語の拡張機能です。C Blocks の定義にキャレット (^) 文字が使われます。
例として、addObserverForName:object:queue:usingBlock: メソッドの NSNotificationCenter に注目してみましょう。それは最後の引数として C Blocks を受け付けます。
- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *))block;
その Blocks は void を返し、一つの引数 NSNotification のオブジェクトを受け付けます。
このメソッドは、Ruby から次のように呼び出すことができます。
notification_center.addObserverForName(name, object:object, queue:queue, usingBlock:lambda do |notification| # Handle notification here... end)
NSArray の enumerateObjectsWithOptions:usingBlock: メソッドはより複雑な C Blocks のケースを差し出し、3 つの引数を 受け付けます (列挙のオブジェクト、配列のインデックス番号と true を設定することで列挙を停止できる boolean 変数へのポインタ)。
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
10 回目の列挙で停止する場合には、以下のようになります。
ary.enumerateObjectsWithOptions(options, usingBlock:lambda do |obj, idx, stop_ptr| stop_ptr[0] = true if idx == 9 end)
(この例は RubyMotion の学習を目的としたもので、このメソッドを使う必要はありません。なぜなら、NSArray は Ruby の Array とに結びついていて、Ruby の each を利用する方が手軽です。)
Proc オブジェクトは、C の関数ポインタや、Blocks が引数と同じ数のブロックパラメータを用意しなければなりません。そうでなければ、実行時に例外が発生します。 |
C 言語の関数ポインタを引数とするケースでは、Proc オブジェクトがメモリ管理により、処理の途中で回収されないようにする必要があります。もし非同期でメソッドが関数ポインタを呼び出す場合には、Proc オブジェクトの参照を保持しておかなければなりません。メモリ管理については次のセクションで説明します。 |
1.4. Memory Management
RubyMotion は自動的なメモリ管理を提供します。みなさんは、使わなくなったオブジェクトを解放したりする必要がありません。
iOS デバイスは Mac などと比べるとメモリは制限されているので、みなさんは巨大なオブジェクトを生成しないよう慎重になる必要があります。
RubyMotion は参照カウントと呼ばれるガベージコレクションの形式を実装しています。オブジェクトは初期値として参照カウント 0 を持ちます。オブジェクトが生成されると参照カウントが加算され、破棄されると減算されます。参照カウントが 0 になると、ガベージコレクタによってオブジェクトのメモリ領域が解放されます。
RubyMotion のメモリ管理しシステムは開発プロセスを単純化するように設計されています。伝統的な Objective-C プログラミングとは異なり、オブジェクトの参照は自動的にシステムによって生成・破棄されます。これは Objective-C の ARC の設計と非常に類似していますが、実装は異なっています。
オブジェクトの回収はシステムにより引き起こされますが、いつ回収されるかは決定的ではありません。RubyMotion は既存の autorelease pool 構造をベースとしています。この autorelease pool は iOS が event run loop などで生成・破棄します。 |
1.4.1. Weak References
2 つ以上のオブジェクトが相互参照するときに生じる、オブジェクトの循環は現在ランタイムによって処理されません。そのため、オブジェクトのメモリリークを避けるために弱参照を作成することが重要です。
RubyMotion では、Ruby の標準ライブラリ weak_ref と同じような WeakRef クラスを実装されています。WeakRef.new(obj) を使用して弱参照を作成でき、渡されたオブジェクトは保持されません。WeakRef オブジェクトに送られた全てのメッセージは、メソッド呼び出しを行う dispatcher の層で WeakRef.new(obj) で渡されたオブジェクトへ転送されます。
弱参照を使用して delegate パターンを実装した例です。
class MyView def initialize(delegate) @delegate = WeakRef.new(delegate) end def do_something # ... @delegate.did_something end end class MyController def initialize @view = MyView.new(self) end def did_something # ... end end
弱参照を使用しない場合には、MyView と MyController はメモリリークを起こします。
将来、RubyMotion のランタイムは循環参照を解決できることでしょう。その際には WeakRef は使用しても機能しないものに置き換えられることでしょう。 |
1.4.2. Objective-C and CoreFoundation Objects
Objective-C や CoreFoundation-style API によって生成されるオブジェクトは、RubyMotion によって自動的に管理されます。retain や release 、autorelease メッセージをオブジェクトに送信したり、CFRetain や CFRelease 関数を使う必要はありません。
つぎに二つの NSDate オブジェクトを異なるコンストラクタを使って allocate するものです。典型的な Objective-C では、返された二つのオブジェクトは異なるメモリ管理が必要となります (data1 は release などでプログラマが解放する必要があります) 。RubyMotion では、両方のオブジェクトはガベージコレクションの対象となります。
date1 = NSDate.alloc.init date2 = NSDate.date
オブジェクトを回収されないようにするには、インスタンス変数に設定するなどのように参照を生成する必要があります。インスタンス変数のレシーバが生きている限り、オブジェクトが確実に保持されるようになります。
@date = date1 # date1 will not be collected until the instance variable receiver is collected
オブジェクトの参照を生成する方法は他にもいくつかあります。定数の定義、クラス変数を使用、オブジェクトのコンテナに追加する (たとえば、Array や Hash など) などがあります。RubyMotion で生成されたオブジェクトで Objective-C API に渡されるものも、必要に応じて参照が生成されます。
1.4.3. Immediate Types
RubyMotion では、Fixnum と Float 型は即値で、これらはオブジェクト用のメモリ領域を使用しません。RubyMotion ランタイムでは、tagged pointers と呼ばれるテクニックを使用してこれを実装しています。
メモリ使用無しでこれらの型が使えることは、アプリのかなりの部分が描画制御(算術アルゴリズムを大量に使用します) に費やされるので重要です。
iOS では 32bit アーキテクチャが使用されているため、即値は 30bit のポインタとして変換されます(型情報として 2bit 使用します)。OS X では、最近の 64bit アーキテクチャの Mac 上で実行する際には即値は 62bit のポインタとして変換されます。 |
1.5. Concurrency
iOS デバイスにマルチコアプロセッサが登場したので、コードを平行して実行させる能力は重要になりました。RubyMotion はこの目的に沿って設計されています。
RubyMotion は、スレッドの実行状態をラップした virtual machine の概念を持ちます。コード片は virtual machine を介して実行されます。
virtual machine はロックしないので、複数の virtual machine が同時に並行に動作できます。
CRuby の実装とは異なり、スレッドの平行実行を禁止する Global Interpreter Lock (GIL) がないため、RubyMotion では race conditions が起こり得ます。共有リソースへ、同時にアクセスする際に注意が必要です。 |
RubyMotion では平行動作するコードを記述する際に、次の異なる選択肢を利用できます。
1.5.1. Threads and Mutexes
Thread クラスは POSIX スレッドを生成します。生成されたスレッドは他のスレッドと平行して動作します。Mutex クラスは POSIX ミューテックスをラップしたもので、共有リソースへの同時アクセスを防止するのに使うことができます。
1.5.2. Grand Central Dispatch
RubyMotion は Grand Central Dispatch (GCD) ライブラリを Dispatch モジュールとしてラップしています。concurrent queue や serial queue のもとで同期・非同期 Blocks を実行することができます。
普通のスレッドに比べて理解することが難しくなりますが、GCD は平行動作するエレガントなコードを提供してくれます。GCD はスレッドのプールを保持し、ミューテックスの使用しないで済むように API が構成されています。
2. Android
ここからは、Android の RubyMotion プロジェクトの動力となっている RubyMotion ランタイムの一つを扱います。
現在、Android サポートはパブリックベータです。While it is fully functional, we are still working on making it ready for prime time. |
2.1. Object Model
RubyMotion のオブジェクトモデルは、Google のモバイルプラットフォームである Android の標準的なプログラミング言語の Java に基づいています。
より具体的に、RubyMotion は Android ランタイムの Java Native Interface (JNI) 上に Ruby 言語を再実装しています。
Java は Objective-C と比べると Ruby との類似点は少ないのですが、統合するのに十分な類似性があります。
RubyMotion では、すべてのオブジェクトは Java のオブジェクトであり、すべての Java のクラスやメソッドは Ruby で利用できます。また、いくつかの Ruby のクラスやメソッドは Java で利用できます。この統一されたアプローチは、無駄なパフォーマンスコストなしで、ネイティブの Java API を扱えるようになります。
2.1.1. Classes
すべての Java クラスは Ruby から直接扱えますが、Java とは異なる構文を用いてアクセスします。
Java にはパッケージという概念があり、コンポーネントのクラス集合をグループ化し、直接アクセスできたりインクルードされたものをアクセスできます。
たとえば、java.lang.Object は java.lang パッケージに定義された Object クラスへのパスを表します。Java クラスはしばしばパッケージのフルパスで記述されたりします。
RubyMotion では、それぞれのパッケージが英大文字から始まる Ruby の定数チェーンとして定義されているかのように Java パッケージをアクセスします。
java.lang.Object というパスは、Ruby では Java::Lang::Object でアクセスできます。
obj = Java::Lang::Object.new
final 修飾子が付いていない Java クラスは、サブクラスにすることができます。たとえば、final が付いていないパブリックな android.app.Activity クラスは、Ruby で Android::App::Activity として利用でき、次のようにサブクラスにすることができます。
class MyActivity < Android::App::Activity # ... end
執筆時点では、Ruby にパッケージをインクルードできないので、フルパスを使用する必要があります。 |
2.1.2. Primitive Java Types
Objective-C のように、プリミティブな Java に組み込まれているデータ型が Java にあります。Java のメソッドはこれらの型を使用し、どのように Ruby コードと連結するかを示す重要なものです。
Java Type | From Ruby to Java | From Java to Ruby |
---|---|---|
boolean |
オブジェクトが false+、+nil のときは false+。それ以外は +true+。Note: +0 は true になります。 |
true と false のどちらか。 |
byte |
オブジェクトが Fixnum か Bignum の場合、その値が返されます。オブジェクトが true か false の場合、それぞれ 1 と 0 が返されます。もしオブジェクトが to_i メソッドに応答できれば、そのメソッドの実行結果を返します。 |
Fixnum か Bignum オブジェクトのどちらか。 |
char |
||
short |
||
int |
||
long |
||
float |
オブジェクトが Float の場合、その値が返されます。オブジェクトが true か false の場合、それぞれ 1.0 と 0.0 が返されます。もしオブジェクトが to_f メソッドに応答できれば、そのメソッドの実行結果を返します。 |
Float オブジェクト。 |
double |
2.1.3. Builtin Ruby Classes
RubyMotion の組み込みクラスのいくつかは、Android SDK のコアクラスである Java Class Library をベースにしています。
すべての Java クラスはルートクラスに java.lang.Object と呼ばれるクラスを持っています。
RubyMotion のランタイムを統合するアプローチのために、すべての Ruby クラスも java.lang.Object をベースにしています。Object という定数も同様にそのクラスを指し示します。
以下の表に、Ruby 組み込みクラスと新しく Hello というクラスを作成した場合のベースクラスを記載します。
Ruby Class | Base |
---|---|
Hello |
java.lang.Object |
Fixnum |
java.lang.Integer |
Float |
java.lang.Float |
Proc |
java.lang.Runnable (interface) |
String |
java.lang.CharSequence (interface) |
Array |
java.util.ArrayList |
Hash |
java.util.HashMap |
Thread |
java.lang.Thread |
Regexp |
java.util.regex.Pattern |
MatchData |
java.util.regex.Matcher |
この設計の主な目的は、Java と Ruby との間でデータ変換が不要なため、パフォーマンスの犠牲なしにプリミティブな型の変換を行えることにあります。典型的なアプリケーションで変換される型の多くは組み込み型なので、このことは重要です。
Ruby で作成した String オブジェクトは java.lang.CharSequence インタフェースを実装したオブジェクトです。たとえば、これは android.widget.TextView クラスの setText という Java メソッドが引数として期待するオブジェクトです。
textView.text = "foo"
2.1.4. Methods
Java メソッドは Ruby にネイティブに展開され、Ruby メソッドで定義されているかのようにアクセスできます。
たとえば、java.lang.Object クラスは toString() メソッドを実装しています。
String toString()
そのメソッドは、Ruby ですべてのオブジェクトに対して呼び出すことができます。
42.toString #=> "42" true.toString #=> "true" textView.toString #=> "android.widget.TextView{3607..."
Ruby で Java クラスをサブクラスにすると、Java のメソッドをオーバーライドできます。
例として、android.app.Activity クラスには onCreate() メソッドがあり、これをサブクラス化によってオーバーライドしてみましょう。
void onCreate (Bundle savedInstanceState)
Ruby で自然に行うことができます。
class MyActivity < Android::App::Activity def onCreate(saved_instance) super # ... end end
Java で定義されたスーパークラスの実装を呼び出すのに、Ruby 言語の super を使用しています。
2.1.5. Method Shortcuts
RubyMotion ランタイムは、便利な Java メソッドのショートカットを提供します。
Method | Shortcut |
---|---|
setFoo |
foo= |
getFoo |
foo |
たとえば、android.widget.TextView の getText と setText メソッドは、text と text= というショートカットで呼び出すことができます。
view.text # instead of view.getText view.text = "foo" # instead of view.setText("foo")
2.1.6. Interfaces
Java interfaces は、RubyMotion で Android アプリをプログラミングする際に知っておく重要な概念です。
Java では、インタフェースは基本的にメソッド宣言のリストです。インタフェースのすべてのメソッドを定義すると、クラスはインタフェースを実装したと言えます。
Java インタフェースは Objective-C のプロトコルと同様のものです。 |
RubyMotion では、クラスに必要なメソッドを単に定義することによって実装できます。インタフェース名を指定する必要はなく、コンパイラが自動的に補います。
例として、android.view.View の setOnClickListener メソッドは、android.view.View.OnClickListener インタフェース (onClick というたった一つのメソッドが宣言されています) を実装したオブジェクトを必要とします。
abstract void onClick(View v)
Ruby でインタフェースを実装するのは、ただクラスにメソッドを実装を記述だけです。
class MyButtonListener def onClick(view) # ... end end # ... button.onClickListener = MyButtonListener.new
2.1.7. Methods Overloading
Java には メソッドオーバーロード という概念がありますが、Ruby には存在しません。
Java では、引数の型が異なる同じ名前のメソッドを複数定義することができます。あとでコンパイラが呼び出されたメソッドがどれなのか特定します。
たとえば android.widget.TextView の Java クラスは、以下の setText メソッドを取り扱います。
void setText(int resid) void setText(char[] text, int start, int len) void setText(int resid, TextView.BufferType type) void setText(CharSequence text) void setText(CharSequence text, TextView.BufferType type)
ご覧の通り、これらのメソッドは引数が異なりますが、同じ名前 (そして同じ戻り値の型) です。
Ruby は動的型付けなので、オーバーロードされたメソッドは、コンパイル時ではなくランタイム時に解決します。
Fixnum を渡したときには、setText(int resid) が呼び出されます。文字列のように、CharSequence インタフェースを実装したオブジェクトを渡したときには、setText(CharSequence text) が呼び出されるでしょう。
textView.text = 42 #=> calls setText(int) textView.text = 'foo' #=> calls setText(CharSequence)
メソッドディスパッチの際に、ランタイムがオーバーロードメソッドを把握できないケースでは、適切なメッセージで NoMethodError 例外が発生します。
2.1.8. Fields
Java フィールドはクラスに宣言された変数です。Java フィールドには 2 種類あります。
-
Instance フィールド; Ruby のインスタンス変数と同様のものです。
-
Static フィールド; Ruby のクラス変数と同様のものです。
Android SDK では、定数はたいてい Static な Java フィールドで、Ruby の定数かのように取得できます。
例として、android/widget/LinearLayout には以下のような定数がいくつかあります。
public static final int HORIZONTAL public static final int VERTICAL ...
これらの定数は Ruby で自然に扱うことができます。
layout = Android::Widget::LinearLayout.new(self) layout.orientation = Android::Widget::LinearLayout::VERTICAL
Instance な Java フィールドは attr_accessor で定義されたかのように Ruby で扱えます。
2.1.9. Annotations
Java アノテーションは特別なデータで、メソッドなどに用いられます。
Android の WebKit フレームワークの例です。セキュリティ上の理由から、JavaScript に展開されるメソッドは適切なアノテーションを付加されたものが必要となります。
RubyMotion では、annotation という特殊なメソッドをメソッド定義の直前に使用して行うことができます。コンパイル時にアノテーションが取り扱われます。
class DemoJavaScriptInterface __annotation__('@android.webkit.JavascriptInterface') def methodFromJavaScript end end
執筆時点では、RubyMotion はメソッドだけがアノテーションを付加できます。 |
2.2. Memory Management
RubyMotion はメモリの割り当てや不要となったメモリの処理を Dalvik や ART のガベージコレクターに頼っています。
Ruby 開発者の観点から、メモリ管理についてまったく考える必要はありません。
内部的には、RubyMotion はローカル変数やグローバル変数にローカル参照を使用しています。ローカル参照の破棄はコンパイル時に決定されます。
みなさんが考慮すべきかもしれない重要なことが二点あります。
-
ガベージコレクターはマルチスレッドです。回収処理が他のスレッドで行われるかもしれません。java.lang.Object の finalize をオーバライドする場合には、必ずリエントラントな実装にします。
-
ガベージコレクターは循環参照を検出し処理することができます。iOS と OS X の RubyMotion と異なり、WeakRef オブジェクトを作成する必要はありません。
2.3. Concurrency
RubyMotion は並行性を念頭において設計されています。
スレッドの実行状態をラップした仮想マシンオブジェクトの概念を RubyMotion にあります。仮想マシンを通じてコード片が実行されます。
仮想マシンはロックがなく、並行して同時に実行される複数の仮想マシンがあり得ます。
主流の Ruby 実装とは異なり、スレッドの平行実行を禁止する Global Interpreter Lock (GIL) がないため、RubyMotion では race conditions が起こり得ます。共有リソースへ、同時にアクセスする際に注意が必要です。 |
並行処理に Thread クラスを利用できます。このクラスは java.lang.Thread を直接サブクラス化しています。