2013年8月26日月曜日

Objective-Cの勉強(7):オブジェクトの所有権



オブジェクトの所有権


Objective-Cでは、前述の通りクラスはローカルやグローバルな領域に実体を確保できないため、必ずオブジェクトの生成と破棄を必要とする。
アクセスしたメソッドが、それを解放して良いか、解放するならそれはいつかは、わかりにくいこともある。
特にメソッドのリターン値になる場合などはそうである。
誰が解放するかということについて、一応の規約が存在する。


オブジェクトを作成する alloc / allocWithZone:
オブジェクトをコピーする  copy / copyWithZone: / mutableCopy / mutableCopyWithZone: 
オブジェクトを保持する retain
以上を自身で行った場合はreleaseでオブジェクトを解放する。
行ってない場合は「そのオブジェクトの所有権はない」として解放処理は行わない。


別のまとめ方をすると、以下の通りとなる。
ローカルでallocしたそのスコープ内でrelease
クラスメソッド(+で始まるメソッド)で確保された基本的には何もしない(OSが解放する)
または明示的にAutorelease Poolを確保~解放する
プロパティでcopyまたはretaindeallocメソッド内でrelease
allocとcopyは実メモリの確保を伴い、retainはアドレスが示す内容の解放を遅らせるだけ、と意味が異なる。にもかかわらず、同じreleaseで解放指示を行うところが注意である。



allocとcopyは特にわかりにくいことはないと思うが、retainは少しわかりにくい概念かもしれない。
Objective-Cのクラスはアドレスでやりとりされる。
従って、代入したもしくはメッセージに引数として渡したと思って元のオブジェクトを解放してしまうと、代入先が参照できなくなってしまう。これを渡された側で「解放に待ったをかける」方法が「retain」である。
Objective-Cではマルチタスクを前提としているので、呼び出されたメソッドと呼び出したメソッドは並行動作する可能性が高い。そのためこのような指示が必要なのである。
呼び出し側でその後の処理でreleaseをかけても、そこでは解放せず、呼び出された側もreleaseをした時に初めて実解放がかかるようにする。



しかしこれもあくまで解放の遅延だけであって、内容の書き換え禁止ではないことには注意すべきである。元の内容を書き換えると、代入先の内容まで書き換わってしまう。これが顕著になるのが配列オブジェクトを使う場合である。



Objective-CのクラスはCの構造体と見た目似ているが決定的に違う部分があって、それは「実体を代入することが出来ない」である。このことは配列クラスであるNS(Mutable)Arrayの要素にて特に問題になる。
NSArrayの要素はid型であるが、これはすなわち要素として実際に格納されるのはアドレスであることを示している。addObject:メッセージで要素を追加しても、その要素のオブジェクトのアドレスが格納されるだけであって、要素実体のコピーが格納されるわけではい。Cで構造体の配列を作った場合は、その実体がコピーされる。これが最大の違いである。
 
// 構造体の場合
typedef struct {
    int x;
    int y;
} XY;
XY xy[10]; // 構造体XYの実内容を持つワークが10個分用意される。
~
    xy[0]=xy[1]; // xy[1]の内容全てがxy[0]の領域にコピーされる


// クラスの場合
@interface XY : NSObject
{
    int x;
    int y;
}
~
{
    XY *xy0=[[XY alloc]init];
    NSMutableArray *ary=[[NSMurableArray alloc]init];
    //
    [ary addObject:xy0]; // ここではxyのアドレスだけが格納され、xyの実体は格納されない
    XY *xy1=[ary objectAtIndex:0];

(構造体とクラスのプログラムは同じ意味ではない。)
ここで、構造体の場合はxy[1]の内容をこの後いじってもxy[0]の内容は変化しない。
実内容がコピーされているからである。
ところが、クラスの場合、xy0の内容をいじるとxy1から読み出す内容も同じになる。
配列に格納されているのはあくまで実体アドレスであって、内容ではないからだ。
このことを理解しておかないと、「配列に要素を保存したはずなのに内容が変わってる!」という現象に悩まされる。クラスではこれはバグではなく仕様なのである。
仮にretainを指定しても、それはあくまでアドレスの示す領域の解放を遅らせるだけであって、内容の書き換えを禁止するわけではない(その方法はない)ので要注意である。


MacのCocoaではメモリの有効活用のためにワークを移動させてまとめたり不要になった領域をメモリ上から消すガベージコレクション機能を持つらしい。古くはBASICで文字列を扱うと発生していたのが「ガベージコレクション」だし、X68のSX-Window上でもワークの移動はOSの大事な機能だったが、それとほぼ同じである(こんな事書いても解る人の方が少ないだろうが)。
確かにそれがあれば解放を気にしなくて済むようになるからプログラムは楽に書けるようになるかもしれない。
が、本来メモリ管理は明確かつ厳密に行われるべきであって、ガベージコレクションなどに頼るべきではないと思う。
アセンブラやCの時代からプログラムを組んでいると、ガベージコレクションどころか、クラスインスタンス以外のAutorelease Poolだって甘やかしだと思うのだが。

2013年8月17日土曜日

Objective-Cの勉強(6):インスタンスの確保/初期化/解放とAutorelease Pool



インスタンスの確保/初期化/解放とAutorelease Pool

クラスのメンバー変数やインスタンスメソッドは、クラスを宣言するだけでは使えない。
実体が存在しないからである。利用時には必ず実体の確保が必要となる。
これは、基本的にはCの構造体と変わらない。


構造体の場合、その確保はグローバルまたはローカルの変数として宣言することで行う。
typedef struct {
    int a;
    int b;
} SINT2; // 構造体の宣言

SINT2 x; // 実体の確保
Objective-Cでも宣言に関しては(記述方法はともかく))基本的に同じであるが、実体の確保はかなり異なる。
クラスを示す変数はアドレスで宣言し、実体確保もメソッドで行うである。
クラスの実体はローカルでもグローバルでも直接確保は出来ない。
なので、

// CINT2がクラスの場合
CINT2 x; // エラー
 
はエラーとなる。宣言時はクラス位置を示すアドレスを宣言、実体確保は別途allocメソッドで行わなければならない。

CINT2 *x;
x=[INT2 alloc];
~
[x release];

C風に書くなら、
x=malloc(sizeof(CINT2));
~
free(x);
となるだろう。

なぜこんな面倒なことをするのかであるが、Objective-Cのクラスは、実行アドレスの確定を初め動的な部分が多いので、ある特定のメモリ領域に一括して格納し、管理するためではないかと思われる。Cのヒープ領域よりもっと高度に管理されたメモリ領域を使うということだ。
そのため、位置不定のローカルや、逆に位置が完全に固定されるグローバルな確保をさせないのだと思う。


クラスの利用を宣言するとき、多くの場合2つの確保し方/され方がある。
alloc/copyなどで明確に領域の確保を指定する場合と、クラスインスタンスを使って確保する場合である。

たとえば、NSStringでは、
(1)NSString *str1=[[NSString alloc]initWithString:@"文字列"];
(2)NSString *str2=[ NSString     stringWithString:@"文字列"];
の2つは、どちらも領域を確保して、その実体として@"文字列"をコピーする。
もっと正確に言えば、str1/str2には確保された領域の先頭アドレスが入り、そのポインタが指し示す領域に"文字列"が格納される(ここではどのように文字列が格納されているかは言及しない)。


ではこの2つは同じなのかというと、「メソッド内で使うだけなら同じ」であるが「メソッドを抜けた後の動作が全く違う」。

(1)がmalloc相当による確保、(2)はAutorelease Poolという自動開放領域への確保となる。
(1)はメソッドを抜けてもその領域は確保されたまま=有効であるが、
(2)はメソッドを抜けると、あるタイミングで自動解放されてしまい内容が不定となる。
メソッドだけでなく、関数で使う場合も同じ。


(1)は必ず対応するreleaseが必要となる。
それは、同一関数内である必要はない、と言うか、無理な場合も多い。その場合は呼び出し側でreleaseすることを忘れてはいけない。ただし同一関数内でreleaseしないと、Analyzeを掛けたとき警告が出る。基本的にallocで確保したものをそのまま関数のリターン値に使うことは避けた方が良い、という考えなのであろう。


ちなみに、自分で翻訳しておいてなんだが、アップルのメモリ管理の規約に従えば、メソッドや関数内でallocしたままリターンし、呼び出し側でreleaseするのは御法度とされている。クラスメソッドを使って返すか、allocする場合はautoreleaseしておけと書いてある。

今度からそうしよう(^_^;)


なおreleaseは厳密にはfree()とは異なる。詳しくは次のオブジェクトの所有権に譲るが、即時解放ではなく解放要求である。

というのも、Objective-Cではオブジェクトは複数から保持要求されることがあり、その場合、全てのrelease要求が揃った時点で初めてメモリが解放されるからである。


Cocoa Touch上では、NSArray等のように要素を自動的にretainするようになっているクラスあり、
その場合、プログラムの記述上では要素に代入したら即時relaseするような、
Cで考えたら「おかしいんじゃねぇ?」と思えるような書き方も当然のように必要だったりするが、
それもreleaseが「即時解放」ではなく「解放要求」だからこそ成立する仕組みである。


(2)のようなクラスメソッドによる確保は、いちいちallocする必要もないし、終了時にreleaseも必要ない(してはいけない)。
なので、ローカルで使うには便利であるが、逆にローカルから外へは持ち出す=リターン値として返す場合は、受け側でcopyするか、retainして明示的に解放を遅らせる必要がある。


それをしなかったらどうなるか。それは、str2の記憶しているポインタアドレスそのものは変化しないのに、その指し示す領域の内容が変化するということになり、デバッグ時に非常にやっかいな現象を引き起こす。Cでローカルワークに格納したものをリターン値にして、呼び出し側で受けようとしたら内容が化けてた、というのと同じである。


(1)(2)の違いを正しく把握することは、Cocoa Touch上でプログラムを組む上で、きわめて重要である。にもかかわらず、アップルのドキュメントにおいてもそれに関する記述が見つけられなかったし、インターネットで調べても書いてあるサイトが1つも見つけられなかったという始末である。
ひょっとして、誰も正しく理解してなかった?それとも理解している人は隠していたか。



Autorelease Pool(自動解放プール)とは、登録されたオブジェクトを、自動解放プールを解放したときに一括して解放する仕組みである。自動開放プールの宣言からその開放までの間に登録されたオブジェクトに対し、自動的に release メッセージを送ってくれる。


NSAutoreleasePool

メソッド名動作
-(void)drain全強制解放する(ガベージコレクション付き)
-(void)release解放する(ガベージコレクションなし)

+(void)addObject:(id)object

-(void)addObject:(id)object
オブジェクトを追加する
-(id)autorelease解放要求+1
-(id)retain保存要求+1

// プール作成
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// たくさんのオブジェクトを登録
id *anObject = [[NSObject alloc] init];
[pool addObject:anObject];

    ~ 
 
//プール解放時に、登録したオブジェクトにもreleaseが送信される
 [pool drain]; // プールの解放 / iOSではreleaseでも同じ
 
このように、自動解放プールに登録したオブジェクトは、自動解放プールを解放したときと同時に解放される。
iOSではガベージコレクションを行わないので、drainとreleaseは同じである(Xcodeが自動的に作るmain.mでもreleaseを使っている)。



私は、iOSアプリを作り始めてしばらくは「クラスメソッドによる確保はローカルスタックに作られる」と理解していた。実際動作だけから見ると、その理解は「ほぼ」間違いではない。
が、もっと詳細に動作を追跡する必要が出て、それをやっている内に「Autorelease Pool領域内に確保されている」と理解した次第である。

なぜか。

Instrumentという解析ツールの中にメモリの確保状況を見る機能がある。
これで追跡していると、クラスを抜けた後にもかかわらず、クラスメソッドで確保したワークが解放されていないことが分かった。
ローカルスタックなら、リターン時に絶対に解放されるはずである(内容が破壊されるかどうかは別にして)。
試しに呼び出し前後でAutorelease poolを確保~解放したら,そのワークも解放された。
ここで「Autorelease pool内に確保される」と理解した次第である。


Autorelease poolは、iOSアプリ起動時にmain()内で自動的に確保される。
だから、ユーザープログラムの中でそれを宣言しないでもクラスインスタンスによる確保が使えるわけだが、これはメインスレッド用なので、ユーザーが別スレッドを動かす場合は、その中で明示的にAutorelease poolを確保~解放する必要がある。
これをしないと、メイン用のAutorelease PoolがあふれてiOSが異常動作を起こすこともある。
経験から言えば、アプリが落ちるのではなく、画面表示がされなくなることが多い。
非常にわかりにくいバグになるので要注意。


スレッド内でクラスインスタンスによる確保をしていないと思っても、一応AutoreleasePoolは確保しておいた方が良い。
なぜなら、アップル純正や、ちまたに流れているクラスライブラリの用意しているメソッドや関数の中には、autoreleaseオブジェクトを返してくるものがあるからである。
確認している範囲では、
  • becomeFirstResponder
  • UIGraphicsGetImageFromCurrentImageContext()
  • UTF8String
等がある。特にbecomeFirstResponderやUTF8Stringは見逃しがちである。
逆に、クラスメソッドでもシステム内のアドレスを返すだけのものは例外的に自動開放プールを使わない。
まあ、なんにせよ転ばぬ先の杖。



また、for等ループの中も同様で、ループを抜けないとAutorelease Poolが解放されないため、
ループ内でクラスインスタンスによる確保を行うとあふれてしまうことがある。
ここでも、ループ内で別途Autorelease Poolの宣言が必要となる。
 
NSMutableArray *array = [NSMutableArray array];
 
for ( int i = 0; i < 10000; i++ ) {
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    [array addObject:[NSNumber numberWithInt:i]];
    [p drain];
}
for ( id n in array ) NSLog( @"%@", n );
 
for 内の[NSNumber numberWithInt:i] は、クラスメソッドによる確保であり、自動開放プールなしでループを回すと、NSNumberインスタンスが一時的に 10000個作成されることになる。
このようなところでは局所的に自動開放プールを作成・解放しておく。


allocした結果をpropertyで宣言したインスタンスに代入する場合、たとえば
 
@interface MyClass : NSObject
{
    NSString *string;
}
proterty (nonatomic,retain) NSString *string;
@end

の場合は、確保は同じだが、解放は
 
self.string=nil; // [string release]; string=nil;相当

でもいいらしい。プロパティーとして作用させる必要があるので、「self.」は外してはいけない。
でも、allocしたのにreleaseがないというのはやはり美しくない。それはCでmallocしたのにfreeがないのと同様で、メモリの確保と解放は、見た目上も対で存在すべきなのだ。

世の中にはreleaseを書かないことを美しいように言っているホームページもあるようだが、メモリの確保と解放を意識しないことがメモリ漏れバグの温床であるから、むしろ「releaseは意識的に書く」方が美しく、理にかなっていると思う。
まあ、個人が「ちゃんと」理解して使っている分にはどちらでもいいのだが。


Cocoa Touchで提供されているクラスは、allocに対しては単純にreleaseを発行すれば解放される。
しかし、自分でクラスを設計するとき、その中で別の領域を確保するかもしれない。
このような場合は、単純にreleaseすると中の領域が開放されないまま残ってしまう。
このようなクラスの場合、中にdeallocメソッドを作って、独自の解放処理を記述しておく。
deallocはクラスをreleaseするときに内部的に呼び出されるメソッドである。
C++でいうところの「デストラクタ」に相当する、のだと思う。
 
@interface MyClass : NSObject
{
    NSString *string;
}
@end

@implementation MyClass

-(NSString *)initString
{
    string=[[NSString alloc]initWithString:@"初期化"];
}

-(void)dealloc
{
    [string release];
}
@end

~

MyClass *my;
    my=[[MyClass alloc]init];
    ~
    [my release]; // ここでdeallocが呼び出される
 
なお、deallocをユーザーが呼び出してはいけない。実行エラーとなる。
それはシステムがオブジェクトを解放するときに自動的に呼び出すものだからである。
ユーザーが呼び出すのはあくまでreleaseのみである。


Google Objective-Cスタイルガイド日本語訳 では「確保する変数はautoreleaseしてから代入せよ」と書いてあるけど、これは「すべての確保系をautoreleaseで行うときのみ」にのみ使える方法であって、上記のようにallocを使っている場合にはしてはいけない。確実に落ちる。
私個人的な感想としては、Googleのスタイルガイドには従いづらい部分が多いので、基本的には無視している。クラスメンバー名の付け方とか、TABとか。


オブジェクトは多くの場合、初期化を必要とするC++ではコンストラクタと呼んでオブジェクト生成時に自動的に呼び出しがかかるが、Objective-Cでは初期化をイニシャライザまたはコンビニエンス コンストラクタと言い、明示的に呼び出す必要がある。


イニシャライザ(initializer)/コンビニエンス コンストラクタ(convenience constructor)はどちらも初期化を行うメソッドであり、よく似た機能を持つが、前者がインスタンスメソッド用、後者はクラスメソッド用で、動作も微妙に違う。


実はクラスの「内容=メンバー変数」はインスタンス化されるときに0で初期化されることが決まっている。Cはauto変数は内容不定だったのとは違う。これは、実はObjective-Cのクラスはスタック上に作られるauto変数ではなく、メモリ上に直に確保されるグローバル変数だからである。Cもグルーバル変数は0で初期化された。Objective-Cで特別に変更された仕様ではない。
(ただし、通常はイニシャライザで必要な初期値を与えておくべきである)。


しかし、インスタンスのアドレス自体はnil(=0)ではない(そのアドレスがselfである)。
プログラム上、時にインスタンスのアドレスをnilで初期化して呼び出すことを要求しているクラスがあるので注意が必要である。



initはNSObject(NSObject)が用意しているインスタンスの初期化用の標準的にイニシャライザー(メソッド)である。多くのクラスはこれをオーバーライドして独自の初期化処理を追加している。
initは多くの場合、初期化された自身のオブジェクトのアドレス=selfを返すが、オーバーライドの実装によっては他の値を返したり、nilでエラーを返す場合もある。
従って、そのリターン値を確認することは重要である。


@interface Point : NSObject
{
    int x;
    int y;
}
- (id)init;     // 引数なしだから変数名のように見えるけど、関数
- (id)ObjFree;  // 〃
- (int)getX;    // 〃
- (int)getY;    // 〃
@end

@implementation Point
- (id)init  // initは引数を持たせることが出来ない
{
    [super init]; // 親クラスのinitを呼び出しておく;子クラスを作る時は必須
    self = [super init];
    if (self != nil) {
        x = y = 0;
        printf("init method\n");
    } // self==nilの時は何らかの原因で初期化に失敗している
    return(self);
}
- (id)ObjFree
{
    printf("ObjFree method\n");
    return [super ObjFree]; // 親クラスのObjFreeを呼び出しておく;子クラスを作る時は必須
}
- (int)getX
{
    return(x);
}
- (int)getY
{
    return(y);
}
@end

int main()
{
    id pt = [[Point alloc]init]; // 確保して初期化
    //      [Point new];    と書き換えることも出来る。new=alloc+init
    printf("x=%d,y=%d\n", [pt getX] , [pt getY]); // なんか変数を並べているようで違和感あるなぁ
    [pt ObjFree];
    return(0);
}
initはオーバーライド時にも引数を持たせることが出来ない。無理に持たせようと定義してもコンパイラがエラーを出す。これはnewというクラスメソッドが=alloc+initと定義されているので、その互換性を維持するためだろう。


そういうこともあって、Point.init()は引数を取れないので=0という固定値で初期化しているが、
引数付きとして任意の値で初期化する場合は別途そういうイニシャライザを定義する。
それを「指定イニシャライザ」といい、慣習的に名称はinitWith~とする。


initと同じく、子クラスのイニシャライザでは、superで親クラスの初期化も行う必要がある。
 
@interface Point : NSObject
{
    ~
}
- (id)initWithPoint:(int)x arg2:(int)y;
~

@implementation Point
- (id)init // initは引数を持たせることが出来ない
{
    [super init];   // 親クラスのinitを呼び出しておく;子クラスを作る時は必須
    return [self initWithPoint:0 arg2:0];
}
- (id)initWithPoint:(int)x arg2:(int)y
{
    self->x = x;
    self->y = y;
    return(self);
}
int main()
{
    id pt1 = [Point new]; // alloc+init=new
    id pt2 = [[Point alloc] initWithPoint:400 arg2:300]; // こちらはnewにはできない
    //
    printf("pt1.x=%d,pt1.y=%d\n" , [pt1 getX] , [pt1 getY]);
    printf("pt2.x=%d,pt2.y=%d\n" , [pt2 getX] , [pt2 getY]);
    [pt1 ObjFree]
    [pt2 ObjFree]
    return(0);

実行結果(未確認)
pt1.x=0,pt1.y=0
pt2.x=400,pt2.y=300
ObjFree method
ObjFree method
「メモリ管理」も参照のこと。



2013年8月8日木曜日

Objective-Cの勉強(5);super



super



クラスを継承した子クラスでは、親クラスを同じ名前のメソッドを作り、処理を置き換えることが出来る。これをオーバーライドという。
オーバーライドした時でも、親のメソッドを呼び出す必要がある場合は「super」を使う。
(親クラスは英語で「SuperClass」と書く。子クラスは「SubClass」。)

@interface SuperClass : NSObject
// 変数がないので{}は省略
- (void)method;
@end

@interface SubClass : SuperClass
// 変数がないので{}は省略
-(void)method; // 同一名関数を作りオーバーライドする
@end

@implementation SuperClass
- (void)method
{
    printf("SuperClass.method\n");
}
@end

@implementation SubClass
-(void)method
{
    printf("SubClass.method\n");
    [super method]; // [SuperClass method]を呼び出す
}
@end

void CallMethod(id obj)
{
    [obj method];
}

int main()
{
    CallMethod([SuperClass alloc]); // [SuperClass method]が実行される
    CallMethod([  SubClass alloc]); // [SubClass method]が実行される→その中で[SuperClass method]も呼び出される
    return 0;
}

実行結果(未確認)
 SuperClass.method
 SubClass.method
 SuperClass.method

こうした時、CallMethod()は引数によって呼び出し関数を変更することになる。
Cではswitch caseか関数のアドレスを持つ配列を使った呼び出しUByte (*fnc)()[]を使ったが、
その辺りの記述が大幅に簡潔になる。
superはselfとは違いアドレスを示すものではなく予約語なので、super->という使い方は出来ない。

2013年8月1日木曜日

Objective-Cの勉強(4);メソッド実体実装


メソッド実体実装



メソッドの実体実装は、@implementationで行う。

@implementation クラス名
+(リターン値)関数名:(引数型)引数    // クラスメソッド
{
    return(リターン値); // 当然(void)なら省略
}
-(リターン型)関数名:(引数型)引数    // インスタンスメソッド
{
    return(リターン値);
}
命名規則はCと同じだが、習慣的にメソッド名は小文字から始めるらしい。
(Cとしての通常変数名とメソッド名は同じになっても良い。でも分けた方がわかりやすいのは当然。)


クラス宣言と実体記述は、ファイルを分離するのが「原則」である。
ファイル名は同じで、拡張子はヘッダーは.hでCと同じだけど、実体は.mと異なる。
実はXcodeでは.cと.mでコンパイル時の挙動がかなり変わる。
自動的にimport/includeするファイルが異なり、また.c内では、ごく一部を除きObjective-Cによる拡張部分は使えない。
<UIKit.h>のimportも許されないし、@"~"もエラーになる。と言うことは、それを使うNSLog()などの関数も使えないと言うことを意味する(代わりにprintf()を使う)。
先に書いたとおり、BOOLも.c内では使えない。
例外は、私の知る限りでは#importくらいである。これはプリプロセッサ命令だからだと思される。
逆に、.m内ではCの全てが使える。従って、基本的には.mで統一しておいた方が面倒がなくて良い。


Objective-Cではインスタンスもしくはクラスにメッセージを送ることでメソッドを実行させる。
この「メッセージを送る」式はCに対して拡張された部分なので、識別できるように[]で括る。これをメッセージ式という。
あるメソッド中で同じクラス内の別のメソッドを呼び出す場合は[self 関数]と記述する
Objective-Cにおいて、メソッドの呼び出しは基本「オブジェクトへのメッセージ送信」の形を取るからである。
自分自身クラス中である場合にはselfが必要となる(メソッド名(引数)という形で呼び出すことは出来ない)。
「基本的に」Cで記述された部分とObjective-Cで記述された部分は切り離されているからである。
その垣根を越える方法はあるが、難しいので省略。

//                                _____引数型
-(int) CheckDropPoint:(int)x arg2:(int)y
//                    ~~|~~  ~|~~
//                   引数型  第2引数ラベル
{
    ~
    return(1);
}
- (int) Cascades
{
    while ([self CheckDropPoint:x arg2:y]==0) { // ←ここ要注意
        r=[super getScrn:x+1 arg2:y];           // ←親クラスのメソッドに対してはメッセージで送る
        ~
    }
    return(0);
}

クラスインスタンスを保存しておく必要がない場合は、その確保宣言そのものを省略して、
[[Class alloc] method]
と記述することも出来る。


プログラムの実行がmain()からなのはCと同じ。
iOSアプリケーションの場合はもう1段上のクラスからの呼び出し処理が入るのだけど、
その辺りは「アプリケーション実装の勉強」で。

int main()
{
    id obj = [Class alloc];
    [obj method];
    return (0); // iOS上ではexit()は使えない
}
メソッドは、クラスを@implementationで実装すれば使えるようになる。
処理実体がメモリ上に存在するということである。
しかし、@implementationしてもメンバー変数は確保されない。
クラスインスタンスの実確保は別途行う必要があるということである。
メンバー変数にアクセスするメソッド=インスタンスメソッドはクラスの実体が確保されるまで実行できないが、クラスの構造だけに依存するメソッド=クラスメソッドはこの時点で実行できる。
この「実体の存在」の認識はObjective-Cでは非常に重要となる。



式の記述上は似ているメンバー変数とグローバル変数には、実は根本的な違いがある。
「メンバー変数はアドレスが固定でない」ということである。


グローバル変数は、コンパイラがコンパイル時にそのアドレスを確定する。
(正確にはリンカーやOSがプログラムをメモリ上に読み込み際に確定される)。
だから、プログラム内どこからアクセスしても同じアドレスにアクセスする。
しかし、クラスメンバはインスタンスによってアドレスが変わる。
だから、記述上は同じに見えても実行時のアクセスという意味では全く異なるわけである。


Cで構造体の制御関数を作るときは、必ず、引数に構造体(多くの場合そのアドレス)を渡すことが必須となる。
制御関数内でのメンバーへのアクセスは、その引数のアドレスからの相対アドレスによる。
ところがObjective-Cのクラス定義では@implementationにクラスのアドレスを渡す記述はない。ないが、内部的には、クラス内のメソッドが実行されるときには、自動的にその時使うクラスインスタンスのアドレスが渡される。
逆に言えば、クラスインスタンスのアドレス確定=領域確保は絶対必須であるということである。


クラス実装はあくまでひな形であって、クラスそのものの領域確定宣言そのものではない。このあたり、慣れるまではクラス実装を書いただけで、インスタンスがあるような錯覚を起こすことがあるので要注意である。


ここからは少し、高度なお話。
iOSは実はかなり高度にマルチタスクを行う。少なくとも、メソッド単位ではマルチタスクが基本になっているので、「このメソッドを呼び出して、返ってきてからこの処理が走って・・・」と考えてはいけない。メソッドを呼び出したら、帰りを待たずに呼び出し側は次の処理を実行してしまう、こともある。どこが待ってどこが待たないかは実のところはっきり理解してないのだが、たぶん「リターン値を持たないメソッドは並行動作する」で間違いないと思う。
少なくともUIKitは呼び出したら即並行動作するので、「表示されたはずだから」と想定してその後の処理を書いてはいけない。
表示が完了したらデリゲートが呼び出されるので、そこで後の処理を書く必要がある。
あまりに高度な並行動作なので、頭の中でなかなか追い切れなかったりする。
自分でマルチスレッドなどを書いたらさらに複雑になるわけで、μITRONのようなイベントドリブン型マルチタスクに慣れているなら、考え方を根本的に変える必要があるかもしれない。(私がそうだった)。
絶対に処理終了を待つ必要がある場合は、別途フラグなどで判定して待つ必要がある。
しかし、ループで単純に待ったら他のスレッドが走らなくなるので、いろいろと対策を入れる必要がある。

拙作X-BASIC for iOSはマルチタスクでないプログラム動作を実現するために、このあたりがかなり複雑になっている。実はBASICのコア自体がバックグラウンドで動いてたりするのだが、その辺の話は、そうさなぁ、1万本くらい売れたら書くと言うことで(^_^;)。