パズル情報どうやって保存してるのかなーと、前回のPuzzleView.mソースを解析しようとして- export:の
でカウンターパンチ食らった人もいるかもしれんすね。
iOS 4.0から導入された、ブロック構文です。
時代はブロック構文。
Appleの開発者が「これからバンバン、ブロック構文使うから皆勉強しておくように!」と言ったかどうかは知らないが、とにかくブロック構文であらざれば、Cocoa touchにあらず、ちゅ~くらいに新しいメソッドには多用されてます。
ま、実際便利だしね。
詳しくは日本語ドキュメントサイトのBlocksプログラミングトピックスを読みましょう。
日本語ドキュメントサイト
なにをやりたいかというと、PuzzleTileLayerを画面上の配置順に並べたいわけです。
左上から右上、そこから折り返して次の段の左という順序。
この順でタイルが持つ画像の番号(number)を、下の画面なら8、5、1、0、4…という値を書き込む必要があるんですな。
なんだけど、パズルを始めるとPuzzleTileLayerの画面上の位置は変わるので、self.layer.sublayersの順は何の役にもたたんわけです。
そのため、PuzzleTileLayerには、画面上の位置をlocationNumberに持たせる(う~ん、今考えるとframeから計算できるね、locationNumber…)ようにしています。
で、このlocationNumberで並べ替えると
となって、あとはこの順にPuzzleTileLayerのnumberを書き出す…
出せてない。
すんまそん、
は
の間違いです。
これじゃ、いつでもパズルが正解になってしまいますな。
まあ、これは直すとして、とにかくこの並べ替えに使ったのが、先の-sortedArrayUsingComparator:メソッド。
この-sortedArrayUsingComparator:は、昔からある-sortedArrayUsingFunction:context:のブロック構文版なんですが、-sortedArrayUsingFunction:context:ってなんすか?だと話にならんので、まずはNSArrayの配列要素並べ替え講座から。
まず、NSArrayはNSObjectクラスを継承したクラスのインスタンスなら、なんでも配列要素として登録できるんで、並べ替えろと言われても、どういう基準で並べ替えたらいいかわからないんですよ。
要素がNSStringだったり、NSNumberだったり、NSDictionaryだったり、ひどいときは、そのすべてが入り交じってたりするわけです。今回ならPuzzleTileLayer。
で、どんな要素でNSArrayを構成してるかを理解してるのはプログラマだけって事になるわけです。PuzzleTileLayerのlocationNumberを使って並べ替えるなんて夢にも思わんわけです。
なので、NSArrayは並べ替えろと命令された場合、
を命令した側に問い合わせる必要があるんですな。
NSArrayは自分が管理している全インスタンスに対して、どっちが前でどっちが後ろかを呼び出し側に問い合わせ、その結果でインスタンスを並びかえるわけです。
この
を教える方法として、関数ポインタを使う方法や、ブロック構文を使う方法があるわけなんだけど、じゃあ、関数ポインタてのは何かというと
そもそもC言語の関数というのは、ソースの段階では人間がわかるように名前を使うけど、実際の動作時にはメモリ上の番地がやり取りされています。
とか書かれているのは
という状態なわけです。
ここで、別のYYY番地の関数が以下のように定義されていたら
このYYY番地の関数を以下のように呼びだせば
YYY番地でXXX番地の関数が実行されるって事になります。で、これをCソースレベルで記述すると以下のようになるわけです。usefuncがYYY番地の関数ね。
関数の番地func_pを受け取るってための記述部分が、ひどくまわりくどいんですが、
で、表したいのはint型の引数を2つとって、int型を返す関数のポインタをfunc_pと名付けますよ、なわけです。
てわけです。メソッドの場合の表現だと、あの独特の表記法に従って
という書き方になります。
実際、-sortedArrayUsingFunction:context:の定義を調べると
となっていて、解釈すると、引数はid型を2つと、void*型を受け取り、NSIntegerを返す関数を比較関数として指定してやるって事になるわけです。
この引数は-sortedArrayUsingFunction:context:のリファレンスで書かれているように、最初の2つがaとb、2つの比較対象のインスタンスで、最後がプログラマが自由に使える情報ってことになります。リファレンスでの例は
てなってますな。
intValueメッセージを送れるのでインスタンスはNSStringかNSNumberなんでしょう。で、v1 より v2が大きいならNSOrderedAscending、小さいならNSOrderedDescending、同じならNSOrderedSameを返してます。
これで、NSArrayの方は、渡したa、b、どっちのインスタンスが大きいかがわかり並べ替えができるわけです。
今回なら
という関数を用意してわたしてやればいいって事になります。
もし比較時に自分自身の情報が必要なら
とやってsortByLocationNumber側で
という風にして使うわけです。
で、ここがブロック構文との違いで、上のやり方だと関数なんでselfが使えなくて、しょうがなくvoid* context経由で渡す事になるわけです。
これがブロック構文を使う-sortedArrayUsingComparator:だと、渡すのはブロック(調べたヘッダーで以下のように定義されてました)
*(アスタリスク)じゃなく^(ハット)になってるけど、基本は関数ポインタの定義と同じです。この場合「引数にid型の変数を2つもらい、NSComparisonResult型の結果を返す」になります。
となり、関数じゃないので名前をつけずに引数の宣言(こいつをしないと引数にアクセスできないからね)だけして、そのまま処理を書いちゃうわけです。
特筆すべきなのは、ブロック構文は、ブロック構文が記述された関数やメソッド(今回だと-export:メソッド)で使われている変数をそのまま使えるってことです。
ただし値が同じなのであって、記憶領域は別に確保されると思われ。もっとも__blockを使うと共有もできるみたいだけど…
selfとかいきなり使えるわけです。
って感じで、selfを使えるし、その上で作ってるdataやdestrefをいきなり使ってもコンパイルエラーにならない。
だからといって、正しく動作するかどうかは別の話、特に非同期で使う場合はなー、と思われ~♪
そういうわけで、void* contextなんて引数が必要なくなるわけです。
ま、これがブロック構文のメリットですが、今回の場合だと特にメリットはないですね。
ブロック構文に慣れていこうという事で、ひとつ。
以上でブロック構文の話はおしまい。
で、問題の書き出し情報の話に戻りますが、最初に
を付ける事で、分割数が変わっても対応できるようにしてます。
PUZZLEってのは、いわゆるマジックワード。
TIFFは公開されているフォーマットなので、誰でも変更が可能なんで、別のプログラムによって変更される場合があるわけで、最初の文字がPUZZLEでなければ、書かれている情報はあてにならないと判断するわけです。
とか、バージョンなんかもいれてもいいかもね。
-import:メソッドでは、この情報文字列を以下のメソッドを使って「,」で分解してNSStringの配列にしてます。
最初の文字列がPUZZLEでなければ読み込みを中止し、そうでなければ、次を横の分割数、その次を縦の分割数として-intValueでint型に変換して利用します。あとは-setImage: withHCount: vCount:メソッドでやったようにPuzzleTileLayerを配置です。
ただし、こっちではPuzzleTileLayerのnumberを、さっきのlocationsから取り出して設定するわけですな。
ああ~、ここも修正しないと…
というかPuzzleTileLayerの- makeContent:メソッドでlocationNumber使ってるけど、これnumberの間違いね。
とにかく修正版を前回のリンクに貼りなおしておきます。
こっちにも貼っておく↓
------------
サンプルプロジェクト:puzzle-08.zip
NSArray* array = [self.layer.sublayers sortedArrayUsingComparator:^(id obj1, id obj2) {
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}];
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}];
でカウンターパンチ食らった人もいるかもしれんすね。
iOS 4.0から導入された、ブロック構文です。
時代はブロック構文。
Appleの開発者が「これからバンバン、ブロック構文使うから皆勉強しておくように!」と言ったかどうかは知らないが、とにかくブロック構文であらざれば、Cocoa touchにあらず、ちゅ~くらいに新しいメソッドには多用されてます。
ま、実際便利だしね。
詳しくは日本語ドキュメントサイトのBlocksプログラミングトピックスを読みましょう。
日本語ドキュメントサイト
なにをやりたいかというと、PuzzleTileLayerを画面上の配置順に並べたいわけです。
左上から右上、そこから折り返して次の段の左という順序。
この順でタイルが持つ画像の番号(number)を、下の画面なら8、5、1、0、4…という値を書き込む必要があるんですな。
なんだけど、パズルを始めるとPuzzleTileLayerの画面上の位置は変わるので、self.layer.sublayersの順は何の役にもたたんわけです。
そのため、PuzzleTileLayerには、画面上の位置をlocationNumberに持たせる(う~ん、今考えるとframeから計算できるね、locationNumber…)ようにしています。
で、このlocationNumberで並べ替えると
となって、あとはこの順にPuzzleTileLayerのnumberを書き出す…
出せてない。
すんまそん、
for (PuzzleTileLayer* layer in array) {
description = [description stringByAppendingFormat:@",%03d", layer.locationNumber];
}
description = [description stringByAppendingFormat:@",%03d", layer.locationNumber];
}
は
for (PuzzleTileLayer* layer in array) {
description = [description stringByAppendingFormat:@",%03d", layer.number];
}
description = [description stringByAppendingFormat:@",%03d", layer.number];
}
の間違いです。
これじゃ、いつでもパズルが正解になってしまいますな。
まあ、これは直すとして、とにかくこの並べ替えに使ったのが、先の-sortedArrayUsingComparator:メソッド。
この-sortedArrayUsingComparator:は、昔からある-sortedArrayUsingFunction:context:のブロック構文版なんですが、-sortedArrayUsingFunction:context:ってなんすか?だと話にならんので、まずはNSArrayの配列要素並べ替え講座から。
まず、NSArrayはNSObjectクラスを継承したクラスのインスタンスなら、なんでも配列要素として登録できるんで、並べ替えろと言われても、どういう基準で並べ替えたらいいかわからないんですよ。
要素がNSStringだったり、NSNumberだったり、NSDictionaryだったり、ひどいときは、そのすべてが入り交じってたりするわけです。今回ならPuzzleTileLayer。
で、どんな要素でNSArrayを構成してるかを理解してるのはプログラマだけって事になるわけです。PuzzleTileLayerのlocationNumberを使って並べ替えるなんて夢にも思わんわけです。
なので、NSArrayは並べ替えろと命令された場合、
自分の管理してるAとBインスタンスは、どっちが前で、どっちが後ろなのか
を命令した側に問い合わせる必要があるんですな。
NSArrayは自分が管理している全インスタンスに対して、どっちが前でどっちが後ろかを呼び出し側に問い合わせ、その結果でインスタンスを並びかえるわけです。
この
AとB、どっちが前で、どっちが後ろなのか
を教える方法として、関数ポインタを使う方法や、ブロック構文を使う方法があるわけなんだけど、じゃあ、関数ポインタてのは何かというと
そもそもC言語の関数というのは、ソースの段階では人間がわかるように名前を使うけど、実際の動作時にはメモリ上の番地がやり取りされています。
int sub(int a, int b)
{
return a > b;
}
・
・
int x = sub(1, 2);
・
{
return a > b;
}
・
・
int x = sub(1, 2);
・
とか書かれているのは
XXX番地: ← sub関数の番地
int a, int bを受け取って以下の{}部分の処理を実行
{
return a > b;
}
・
・
int x = XXX番地の関数を引数、1、 2で呼び出し。
・
int a, int bを受け取って以下の{}部分の処理を実行
{
return a > b;
}
・
・
int x = XXX番地の関数を引数、1、 2で呼び出し。
・
という状態なわけです。
ここで、別のYYY番地の関数が以下のように定義されていたら
YYY番地
関数の番地func_pとint a, int bを受け取って以下の{}部分の処理を実行
{
return (func_p番地を引数、a, bで呼び出し。)
}
関数の番地func_pとint a, int bを受け取って以下の{}部分の処理を実行
{
return (func_p番地を引数、a, bで呼び出し。)
}
このYYY番地の関数を以下のように呼びだせば
・
・
int x = YYY番地を引数、XXX番地、1、 2で呼び出し。
・
・
int x = YYY番地を引数、XXX番地、1、 2で呼び出し。
・
YYY番地でXXX番地の関数が実行されるって事になります。で、これをCソースレベルで記述すると以下のようになるわけです。usefuncがYYY番地の関数ね。
int sub(int a, int b)
{
return a > b;
}
int usefunc(int (*func_p)(int, int), int a, int b)
{
return func_p(a, b);
}
・
・
int x = usefunc(sub, 1, 2);
・
{
return a > b;
}
int usefunc(int (*func_p)(int, int), int a, int b)
{
return func_p(a, b);
}
・
・
int x = usefunc(sub, 1, 2);
・
関数の番地func_pを受け取るってための記述部分が、ひどくまわりくどいんですが、
int (*func_p)(int, int)
で、表したいのはint型の引数を2つとって、int型を返す関数のポインタをfunc_pと名付けますよ、なわけです。
(*func_p):func_pという名前の関数のポインタ変数
てわけです。メソッドの場合の表現だと、あの独特の表記法に従って
(int (*)(int, int))func_p
という書き方になります。
実際、-sortedArrayUsingFunction:context:の定義を調べると
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;
となっていて、解釈すると、引数はid型を2つと、void*型を受け取り、NSIntegerを返す関数を比較関数として指定してやるって事になるわけです。
この引数は-sortedArrayUsingFunction:context:のリファレンスで書かれているように、最初の2つがaとb、2つの比較対象のインスタンスで、最後がプログラマが自由に使える情報ってことになります。リファレンスでの例は
NSInteger intSort(id num1, id num2, void *context)
{
int v1 = [num1 intValue];
int v2 = [num2 intValue];
if (v1 < v2)
return NSOrderedAscending;
else if (v1 > v2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
{
int v1 = [num1 intValue];
int v2 = [num2 intValue];
if (v1 < v2)
return NSOrderedAscending;
else if (v1 > v2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
てなってますな。
intValueメッセージを送れるのでインスタンスはNSStringかNSNumberなんでしょう。で、v1 より v2が大きいならNSOrderedAscending、小さいならNSOrderedDescending、同じならNSOrderedSameを返してます。
これで、NSArrayの方は、渡したa、b、どっちのインスタンスが大きいかがわかり並べ替えができるわけです。
今回なら
NSInteger sortByLocationNumber(id obj1, id obj2, void* context)
{
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
{
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
という関数を用意してわたしてやればいいって事になります。
もし比較時に自分自身の情報が必要なら
[self.layer.sublayers sortedArrayUsingFunction:sortByLocationNumber context:self];
とやってsortByLocationNumber側で
NSInteger sortByLocationNumber(id obj1, id obj2, void* context)
{
PuzzleView* puzzleView = context;
・
・
}
{
PuzzleView* puzzleView = context;
・
・
}
という風にして使うわけです。
で、ここがブロック構文との違いで、上のやり方だと関数なんでselfが使えなくて、しょうがなくvoid* context経由で渡す事になるわけです。
これがブロック構文を使う-sortedArrayUsingComparator:だと、渡すのはブロック(調べたヘッダーで以下のように定義されてました)
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2);
*(アスタリスク)じゃなく^(ハット)になってるけど、基本は関数ポインタの定義と同じです。この場合「引数にid型の変数を2つもらい、NSComparisonResult型の結果を返す」になります。
となり、関数じゃないので名前をつけずに引数の宣言(こいつをしないと引数にアクセスできないからね)だけして、そのまま処理を書いちゃうわけです。
^(id obj1, id obj2)
{
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
{
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
特筆すべきなのは、ブロック構文は、ブロック構文が記述された関数やメソッド(今回だと-export:メソッド)で使われている変数をそのまま使えるってことです。
ただし値が同じなのであって、記憶領域は別に確保されると思われ。もっとも__blockを使うと共有もできるみたいだけど…
selfとかいきなり使えるわけです。
^(id obj1, id obj2)
{
[self …];
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
{
[self …];
PuzzleTileLayer* l1 = obj1;
PuzzleTileLayer* l2 = obj2;
if (l1.locationNumber < l2.locationNumber)
return NSOrderedAscending;
if (l1.locationNumber > l2.locationNumber)
return NSOrderedDescending;
return NSOrderedSame;
}
って感じで、selfを使えるし、その上で作ってるdataやdestrefをいきなり使ってもコンパイルエラーにならない。
だからといって、正しく動作するかどうかは別の話、特に非同期で使う場合はなー、と思われ~♪
そういうわけで、void* contextなんて引数が必要なくなるわけです。
ま、これがブロック構文のメリットですが、今回の場合だと特にメリットはないですね。
ブロック構文に慣れていこうという事で、ひとつ。
以上でブロック構文の話はおしまい。
で、問題の書き出し情報の話に戻りますが、最初に
PUZZLE,横分割数,縦分割数
を付ける事で、分割数が変わっても対応できるようにしてます。
PUZZLEってのは、いわゆるマジックワード。
TIFFは公開されているフォーマットなので、誰でも変更が可能なんで、別のプログラムによって変更される場合があるわけで、最初の文字がPUZZLEでなければ、書かれている情報はあてにならないと判断するわけです。
PUZZLE,1.0.0,横分割数,縦分割数
とか、バージョンなんかもいれてもいいかもね。
-import:メソッドでは、この情報文字列を以下のメソッドを使って「,」で分解してNSStringの配列にしてます。
NSArray* locations = [commentStr componentsSeparatedByString:@","];
最初の文字列がPUZZLEでなければ読み込みを中止し、そうでなければ、次を横の分割数、その次を縦の分割数として-intValueでint型に変換して利用します。あとは-setImage: withHCount: vCount:メソッドでやったようにPuzzleTileLayerを配置です。
ただし、こっちではPuzzleTileLayerのnumberを、さっきのlocationsから取り出して設定するわけですな。
ああ~、ここも修正しないと…
というかPuzzleTileLayerの- makeContent:メソッドでlocationNumber使ってるけど、これnumberの間違いね。
とにかく修正版を前回のリンクに貼りなおしておきます。
こっちにも貼っておく↓
------------
サンプルプロジェクト:puzzle-08.zip