2013年7月23日火曜日

Objective-Cの勉強(3);クラスの宣言



クラスの宣言



クラスはObjective-Cの肝であり、Cで言う所の「構造体」を大幅に機能/思想拡張した物である。
クラスは通常何らかの親クラスからの継承で作られる。


表面上、何も継承することがない場合でも、クラスの基本的機能を内包させるため、NSObject(一部NSProxy)を親にする。
親クラスを書かない場合は、それらのクラスが持つべき全てのメソッドを自前で実装する必要があるので、よほど腕に自信がない限り行うべきではない。
メーカー提供のクラスは必ずNSObject(相当)を親にしている。



クラスの宣言には@interfaceコンパイラディレクティブを使う。
クラス名は習慣的に大文字から始めるらしい。

@interface クラス名:親クラス名
{
    変数型 メンバー変数宣言;
}
メソッド宣言;
@end
「例」
@interface AClass : NSObject
{
    id var;
}
-(void)setVar: (id)v;
@end

メンバー変数を持たないクラスの場合、{~}の部分は省略できる。
変数の型はCと同じ基本の型(int等)、もしくはid型を含む後述のクラス型となる。



クラス内で実装されるメソッドにとってメンバー変数は、プログラムの記述上はグローバル変数とほぼ同じ扱いができる。
これは楽そうに思えるが実は、「ローカル変数を使うつもりだったが実は未宣言で、同名メンバーがクラス内にある場合、コンパイルエラーにならない」ので要注意である。このあたりは命名規則を作って避けるようにしなければならない。



NSObjectから継承した場合、メンバーにはid self;というものも自動的に定義される。
self->メンバー名でメンバーに確実にアクセスできる。
メソッドの引数がメンバー名と重複する場合に有効であるが、これはCと同じで引数がグローバル変数を隠蔽してしまう「バグの元」なので、別の名前にするのが本筋であろう。慣れるまでは。

2013年7月12日金曜日

Objective-Cの勉強(2);メソッドの宣言



メソッドの宣言



メソッドは以下の書式で宣言する。
関数のプロトタイプ宣言に近い。Objective-Cでは関数名から引数のラベル名までを含め全体を「メッセージ」と呼ぶ。
 
 -(リターン型)メソッド名:(引数型)引数;                       // 1引数の場合
 -(リターン型)メソッド名:(引数型)引数1 label2:(引数型)引数2; // 2引数の場合
 -(リターン型)メソッド名:(引数型)引数,...;                   // 可変引数の場合

リターン型は必ず()で括る。なぜかというと、Objective-Cの基本型はintではなくid型というものになっているからである。要するに、この()はid型からの型キャストを示しているのである。事実id型なら()も要らないし、省略も出来る。しない方が良いけど。

id型はクラスに対し、動的に(=実行時に確定される)型や内容を変更することが出来る万能型。int等基本型の代わりにはできないけど。
idの本性は、オブジェクトのアドレスを示すポインターである。typedefで*を含めて名称定義してあるようなので、*は省略する。


引数の区切りは「:」であり「,」ではない。「,」は可変引数を示すからであるが、可変引数はあまり使われない。Apple提供のクラスでも、それほど多くは出てこない。
Objective-Cでは、同名メソッドを引数別に定義出来るため、必要性が少ないのだろう。
可変引数の取り出し型は、Cと同じくva_listとva_start()を使う。


2引数以上の場合、引数名にラベルを付けるのが「習慣」だそうな。ラベルはその直後の変数の物。すなわち、Objective-Cではメソッド名は第1引数のラベル、その後各引数にラベルを付けるということだ。ラベルなのでラベル名と:に間にスペースを入れてはいけない。
Objective-CはCからの拡張実装であるから、すでにそれが持っているラベル認識機能をうまく使った実装であると言えよう。
(実はラベルは省略可能だが「推奨されていない」。)


ラベル名を変えると、同じメソッド名でも異なるメッセージとして扱われる。
これはオーバーライドとはちょっと違い、メッセージの受信オブジェクトの変更になる。


メソッドはメンバー変数名と同じであってもかまわない。通常メンバー変数は外部から直アクセスさせることはないかららしい。
ただ、初心者は分けた方が迷わなくて良いかもしれない。



ラベル名はCの予約語名とは同じでも大丈夫だが、#define定数名と同じではいけない。
プリプロセッサの方が先にソースを通すためである。


1つのメソッドで複数の引数が同じラベル名を持っていても良いようである。
NSObjectには-(id)performSelector: withObject: withObject:というメソッドが存在したりする。
が、あんまり使うべきじゃないのでは、と思ったりする。



先頭の「-」はインスタンスへのメソッドであることを示す。
他に「+」というのもあって、これはクラスに対するメソッドであることを示す。
インスタンスメソッドは実体を持ったオブジェクトに対して実行される関数であり、クラスメソッドはクラスに実体を持たせる時に実行させる関数、もしくはクラスの特性をして持っておくべくメソッドと理解すればいい。


インスタンスメソッドはselfおよびメンバー変数にアクセスできるが、クラスメソッドはそれがアクセスできない。当然コンパイルするとエラーが出る。
クラスメソッドはクラスを実体化=インスタンス化するときなど、インスタンスがない状態でも呼び出されるからである。



init=初期化されていない/またはalloc=確保されていないクラス内のメソッドを呼び出すとどうなるか。
実は何も起こらない。エラーも何も発生せずに素通りしてしまう。Objective-Cでは初期化してしていないクラスはnilであり、nilへの呼び出しは全て無視するようになっているからである。
もし実装したはずのメソッドが実行されないときは、そのインスタンスが正しく初期化されているか確認すべきである。



2013年7月6日土曜日

Objective-Cの勉強(1);基本

しばらくの間、Objective-Cを勉強した時にまとめた文章をアップします。
全て独学で覚えたので、世間一般の認識とは違う部分があると思います。
私はアセンブラ→C言語→Objective-Cあがりで、特にCで考えることに慣れているため、それとの対比を多く行っています。


基本


まずは用語と基本を覚える。Objective-Cおよびオブジェクト指向言語特有の名称が多いが、覚えやすくするためにCの名称に置き換えることもする。その対応表の作成は後日に行うかもしれない。
要するにここはC言語は習得しているが、C++は勉強したけどあきらめた人(=用語ぐらいは少しは解る)向けで、「普通」とは異なるので注意(自分以外の誰が読んでんねん、という突っ込みは無視^_^;)。


まずは、絶対覚えなければならない基本用語。
オブジェクト メモリ上に配置されるワークエリアの一般名称
クラス オブジェクトのひな形(構造定義書)
Cの構造体に近く、その中に制御関数;メソッドも含む
メソッド クラスの専用制御関数
インスタンス クラスを実際にメモリ上に割り付けた「オブジェクト」実体
インスタンス化  クラスを定義しただけでは構造体宣言と同じく実体は存在しないので、
それを元に変数を宣言、実体をメモリ上に確保すること
継承 あるクラスの機能を受け継いでで新規のクラスを定義すること
親クラス(スーパークラス)の全ての機能を受け継いだ上で、新機能または置き換え分のみ実装すればいい
オーバーライド 既存メソッドを同名メソッドで再実装すること。
基本的には、子クラスで親クラスに存在するメソッドを同名メソッドで再実装する。
レシーバー メソッドを実行するインスタンスのこと。
Cの構造体だって関数をメンバーに出来るので(関数だって所詮はアドレスに過ぎないから)、よく言われる「構造体とクラスはメソッドの有無が違い」というのは実は間違い。


Cの構造体のメンバー関数は「呼び出す」というし、アセンブラレベルでもCallするが、Objective-Cのメソッドは「メッセージを送る」という。
実はObjective-Cの基本にして重要な思想がここに現れている。Cのメンバー関数は(基本的に)構造体に固定されているが、メソッドは受信するオブジェクトを選択することが出来る。

異なるクラスに同じメソッド(名)を実装した場合、それは「同じメッセージを受信できるクラス群」として扱われる。そして、あるメッセージを送信した場合、それを実際に受信するクラスによって動作を切り替えることが出来る。

Cでなら、同じメンバー名関数を持つ構造体群があったときに、場合によって構造体側を切り替える場合にはswitch caseか関数の実行アドレスを持つ配列に入れて切り替えるしかないが、Objective-Cでは受信するクラス名を変数に出来るため、このあたりが実に簡潔に記述できる。
この両者の「基本概念の向きの違い」を覚えておかなければならない。それが「オブジェクト指向」である(この場合「思考」の方が合ってそうだけど)。



Objective-CはCの完全な拡張であるので、一部に制約が付いたC++とは異なり、Cで使えた書式は全て使える。
変数の型も同じだが、typedefにより、見かけ上いくつか追加がある。
id オブジェクトを示す汎用変数(正確にはインスタンスのアドレス=ポインタ)
Class  クラスオブジェクト(クラスの定義内容そのものを持つオブジェクト)へのポインタ
SEL メソッドのセレクタ値
IMP メソッドの実行アドレス(ポインタ)
BOOL YES/NOのどちらかの値を持つ型


また、以下の定数もobj.hをimportすることで使える(標準定数)。
定数名用途・意味定義
nil CにおけるNULLをidに拡張したものと思えば正解。終端やエラーを示すのに多く用いられる(id)NULL
YES/true BOOLの肯定(BOOL)1
NO/false BOOLの否定(BOOL)0
NSNotFound 要素を探したが見つからないとき開発環境によって異なる
もちろんCの標準定数(NULLなど)も使える。
BOOLはCでも定義して使っている人が多いけど、私は使わない主義であった。
関数の改良時によく、それ以外のリターン値を返すようにしてたからである。でも今は「区別しといた方がいい」派になっている。「年々歳々人同じからず」である。



余談。実はCocoa Touch上には真偽値を示す型が複数ある。BOOL/Boolean/boolである。NS/UIクラスで使われているのはBOOL、しかし、plistという物で使われているのはBooleanであったりする。
Boolean/boolではtrue/falseが値として使われるが、YES=true/NO=falseである。
boolがISO Cの標準真偽値型のようなので、正確にはifで判定されるのはbool型だと言えるが、混在してもコンパイラのチェック上でも動作でも特に問題は起こさない。まあ、NS/UIクラスと同じくBOOLで統一しておくのが無難かも知れない。BOOLはObjective-Cのソース、すなわち.m内でのみ使える。BooleanはCのソース、すなわち.Cでも使える。C内ではBOOLはエラーになる。Booleanはどちらでも使えると思う。
実際の値は同じだが、相互に使える使えないがあるので、注意が必要である。




Cの関数も全くの変更なしに呼び出すことが出来る(C++はextern "C"でヘッダーの書き換えが必要)が、一部機能は強化されている。
(古い)CとObjective-Cの細かい違いとしては、
  • ヘッダーは「基本的に」#includeではなく#importで読み込む
  • プロトタイプ宣言が必須(前方参照不可)Xcode4.3では同一ファイル内では自動的に前方参照が解決されるようになった。
  • 一部の例外を除き、変数宣言をスコープの先頭で行う必要がない
と言うのがある。

#importはCのヘッダーで問題になっていた2重読み込みを自動的に排除してくれる。
ただし、#defineで定数の定義内容を書き換える(切り替える)タイプのヘッダーを#includeする場合は、#importに変更するとバグになる可能性がある。
Cのヘッダーは#includeで、Objective-Cのヘッダーは#importで読み込むのがいいかもしれない。


メソッドは、基本的に宣言と実体実装を別ファイルにするため、宣言ファイル(ヘッダー)を#importしておけばいいが、通常のC関数も、それがプロトタイプ宣言されているヘッダーを読み込んでおく必要がある。


(本当はクラスの独立性を高めるためらしいが、)ヘッダーを#importするのが面倒な場合で、クラス名であることを宣言するだけで良いなら、
  @class クラス名1,クラス名2...;
というコンパイラディレクティブを使って宣言しておける。Cにおけるexternに近い。
「コンパイラディレクティブ」とは、Objective-Cで拡張されたプリプロセッサ命令である。プリプロセッサ命令であるため、コンパイル時には実内容に展開される=静的に決定される。


古いCの場合、ローカルに新規の変数を使う場合は必ず新しいスコープの先頭(宣言文以外が出てくるまで)に宣言しなければならなかったが、Objective-Cではこれがかなり緩くなる。本当は最近のCの規格ではそういうものらしい。
ただし、一部例外がある。

Cの場合         Objective-Cの場合
{               {
int x;              int x;
    x=1;            x=1;
    {               int y; // 必要時に宣言すればよい
    int y;          y=x+1;
    y=x+1;
    }
}               }

Cの場合         Objective-Cの場合
switch (x) {      左に同じ
  case 1:         switch内だけはスコープを新規に作る必要がある
     {              (厳密にはcase文の直後に宣言するときのみ、新規スコープの宣言が必要。)
     int y;       "Switch case is in protected scope"エラーが発生することもあるが、発生しないことのほうが多い。
     y=x+1;       
     }
}
という具合である。ただし、正確には上の例は変数yのスコープが異なる。Cの場合の記述は{}の中だけで有効だが、Objective-Cの記述では宣言以降全てである。従って、厳密にスコープを規定する場合は、Objective-CでもCと同じにする。単に「使うときに宣言したい」という意味の時のみ書き換えることが出来る。


各種名称の命名規則は基本的にCと同じであるが、「習慣的」には推奨がある。
クラス 大文字から始める
メソッド 小文字から始める。_(アンダーバー)から始まる名称は禁止(アップル予約)
カテゴリ 大文字から始める
プロトコル 大文字から始める
ラベル   小文字から始める
型名 クラス名に準じる(だから見分けが付かなくてややこしい)
変数 小文字から始める
定数 Cの場合大文字だけど、Objective-Cはクラス名に準じる(だから見分けが付かなくてややこしい)

ただし、Google推奨記法ではkで始めると書いてある。アップル標準もそういうのが多い。


また、「同じ名前を付けたときにどうなんねん」については、以下の規則がある。
  • メンバー変数名とメソッドの引数が同名の場合は、メンバー変数名が隠蔽される(要注意)。この場合、self->で回避できる(メンバーアドレスへのアクセスであり、プロパティ式ではない)
  • メソッド名および引数ラベル名は予約語名と同じでもかまわない(int:とか)
  • メソッド名および引数ラベル名は#define定数名と同じではいけない。プリプロセッサの方が先にソースを通すため
  • クラス同じ名前のグローバル変数はあってはいけない(コンパイルエラー)
  • 異なるクラス間では同じメソッド名を使うことが出来る
  • 継承したクラスが親と同じメソッド名を持つ場合は、親のメソッドの代わりに実行される=オーバーライド
    親のメソッドを実行する場合は、superを使う
  • 継承したクラスに親と同じメンバー変数名を持ってはいけない(コンパイルエラー)
  • 異なるクラス間では同じメンバー変数名があってもかまわない
  • メンバー変数とメソッド名が同じでもかまわない。というか積極的にそうしてメンバー変数への直アクセスをさせないようにする=プロパティ
  • インスタンスメソッドには、クラスメソッドと同じ名前を付けてもいい(対象によって自動的に切り替わる)
  • プロトコルには、クラス、カテゴリ、その他のものと同じ名前を付けてもいい
  • 1つのクラスのカテゴリには、他のクラスのカテゴリと同じ名前を付けてもいい
この他はCと同じ。詳細はその都度。
Objective-Cでは同じ名前でも、機能さえ異なればちゃんと識別される場合が多いということだ。
同一機能であれば同じ名前にしておくとわかりやすいかもしれないが、だからといって全然異なる機能に同じ名前を付けるとバグの元なので避けるべきである。


こまどり君 V1.5;超簡易説明書

iOSアプリ「こまどり君」の使い方が解らん、という方がいらっしゃるので、
ここに超簡易説明書を置いておきます。
V1.5からはこちらが内蔵になり、詳細取説はサポートページに置かれます。


・・・








・・・


不具合報告及び要望は、必ずこちらにコメントでお寄せください。

「動かない」と書き殴るだけでは何も解決しません。
手元の開発環境では動くことを確認してから審査に出し、しかも審査も通っているのですから、動かないのは設定または環境に依存するからです。

不具合報告は必ず
・機種(モデル名、メモリ容量、WiFiか3Gか)
・iOSバージョン
・設定
・具体的な不具合状況
を添えて送ってください。

それがないものは全て無視します。

AppStoreのカスタマレビューを読まないのもそれ故です。
あれは開発者にとってはほとんど無意味、というよりむしろ読むだけ不快になる代物です。
有意義な20件に1件くらいでしょうか。

ごくまれにそれらを添えて報告をくださる方がいらっしゃります。
非常に感謝しております。残念ながら、手元で現象が再現できないこともありますが、出来るだけの対応と何かしらの回避策は提案できるはずです。

個人が趣味で作っている以上、サポートに使える時間は限られています。有償であってもそれは変わりません(非常に安いシェアウエア)。ゆえに、より有効な情報と励ましが必要なわけです。
そのあたりをご理解の上、ご協力をお願いします。