Clojureはコンソールからcljで起動出来る。 $ clj Clojure 1.4.0 user=> これが↑↓キーで入力履歴が出なかったりしてイマイチ使いづらい。 Clojureのビルドツールleiningenを使って起動すると↑↓キーで入力履歴が出る様になる。 ちょっとClosureを試してみるだけなのでビルドツールは要らないと思っていたが、 入力履歴が使えないのは不便なのでleiningenを使ってcljを起動してみると 入力履歴が使える様になった。 因みにClojureもleiningenもhomebrewからインストール出来る。 $ lein repl REPL started; server listening on localhost port user=>
2012年6月15日金曜日
Mac: Clojureコンソール(clj)を使いやすくする
2012年6月4日月曜日
ペアノ算術とProlog
ペアノ算術をPrologで表現してみる。 元ネタ 第3巻『数学ガール/ゲーデルの不完全性定理』 Prolog by Example: Peano Arithmetics Swi-Prologを使います。 と…その前にペアノの公理で自然数を定義しよう。
ペアノの公理1
0は自然数である
Prologコード
natural_n(0).
ペアノの公理2
どんな自然数nに対しても後続数s(n)は自然数である
Prologコード
natural_n(s(N)) :- natural_n(N).
ペアノの公理3
どんな自然数nに対してもs(n)は0ではない
Prologコードは無し
ペアノの公理4
どんな自然数m,nに対しても、s(m)=s(n)ならばm=nである
Prologコードは無し
ペアノの公理5
自然数nに対する述語P(n)で、次の2つが成り立つとする。 ・P(0)である。 ・どんな自然数kに対しても、P(k)ならばP(s(k))である。 このときどんな自然数nに対してもP(n)が成り立つ。
Prologコードは無し
ここまでのPrologコードまとめ
natural_n(0). natural_n(s(N)) :- natural_n(N).
実行してみる。
?- natural_n(X). X = 0 ; X = s(0) ; X = s(s(0)) ; X = s(s(s(0))) ; X = s(s(s(s(0)))) ; X = s(s(s(s(s(0))))) . 自然数が出来た! 「これが自然数?」と言いたい人もいるでしょうが、これで良いんです!! s(0)を1 s(s(0))を2 s(s(s(0)))を3 というふうに読み替えて下さい。
さて、ここからがペアノ算術
足し算の公理1
どんな自然数nに対しても、n + 0 = nが成り立つ
Prologコード
sum(N,0,N).
余談ですが自然数に0を含める考え方(0,1,2 ...)と含めない考え方(1,2,3 ...)があり、上の足し算の公理は前者です。 後者の場合の公理はこうなります。足し算の公理1(0は自然数に含めない)
どんな自然数nに対しても、n + 1 = s(n)が成り立つ
"0"という自然数は存在しないので、こうするしかないのでしょう。 この場合は、 s(1)を2 s(s(1))を3 s(s(s(1)))を4 というふうに読み替えて下さい。
Prologコード
sum(N,1,s(N)).
足し算の公理2
Prologコード
sum(N,s(M),s(K)) :- sum(N,M,K).
Prologコードまとめ
sum(N,0,N). %In case of natural numbers contain 0. sum(N,1,s(N)). %In case of natural numbers contain NO 0. sum(N,s(M),s(K)) :- sum(N,M,K). %K=N+M X=3+2を実行してみます。 ?- sum(s(s(s(0))),s(s(0)),X). X = s(s(s(s(s(0))))) ; false. X=5になりました。 おっと、ここで作った自然数は"5"とは言わないんでしたね。 s(s(s(s(s(0)))))ですね、でも長ったらしいので5と言う事にします。 因みに、0は自然数に含めない場合で X=4+3を実行してみます。 ?- sum(s(s(s(1))),s(s(1)),X). X = s(s(s(s(s(s(1)))))) ; false. X=7になりました。 0を自然数に含める場合は、例えば2をs(s(0))と表現すると sum(N,s(M),s(K)) :- sum(N,M,K). を再帰的に評価し、最終的に sum(N,0,N). にマッチして、再帰の階段を戻って結果が出る訳です。 0を自然数に含めない場合は、例えば2をs(1)と表現して、最終的に sum(N,1,s(N)). の方にマッチするのです。
ではここからは掛け算です。
掛け算の公理1
0を自然数に含める場合
Prologコード prod(M,0,0).
0を自然数に含めない場合
Prologコード prod(M,1,M).
掛け算の公理2
Prologコード prod(M,s(N),P) :- prod(M,N,K), sum(K,M,P). % K=M+N
Prologコードまとめ
prod(M,0,0). %In case of natural numbers contain 0. prod(M,1,M). %In case of natural numbers contain NO 0. prod(M,s(N),P) :- prod(M,N,K), sum(K,M,P). % K=M+N 実行 0を自然数に含める場合、X=2*3 ?- prod(s(s(0)),s(s(s(0))),X). X = s(s(s(s(s(s(0)))))) ; false. X=6となりました。 0を自然数に含めない場合、X=2*4 ?- prod(s(1),s(s(s(1))),X). X = s(s(s(s(s(s(s(1))))))) ; false. X=8となりました。
2012年5月3日木曜日
備忘録:ファイルパスを保存しておいて、後でファイルを参照しようとするときに、ファイルが見つからなくなってしまう事がある
よくある間違いらしいので、備忘のため。
元ネタ:「iOSアプリを、AppStore経由でアップデートした場合、フォルダのアドレスが変更されます。アプリケーションフォルダの基本パスが変わる訳です。しかし、データ(Documentフォルダの内容、Libraryフォルダの内容)はアップデートの際はコピーされます。それで、アップデート前に、ファイルパスをフルパスで保存していると、アップデートのあとに、ファイルを参照しようとするときに、ファイルが見つからなくなってしまいます。」
この方法も良さそうだが、 stringByExpandingTildeInPath と stringByAbbreviatingWithTildeInPath で、「~/Desktop」というように記述した相対パスにすれば良いのかも。
元ネタ:「iOSアプリを、AppStore経由でアップデートした場合、フォルダのアドレスが変更されます。アプリケーションフォルダの基本パスが変わる訳です。しかし、データ(Documentフォルダの内容、Libraryフォルダの内容)はアップデートの際はコピーされます。それで、アップデート前に、ファイルパスをフルパスで保存していると、アップデートのあとに、ファイルを参照しようとするときに、ファイルが見つからなくなってしまいます。」
この方法も良さそうだが、 stringByExpandingTildeInPath と stringByAbbreviatingWithTildeInPath で、「~/Desktop」というように記述した相対パスにすれば良いのかも。
2012年4月21日土曜日
Blocks関連のバグがデバッグコンパイルで再現しないケース
TOMOHISAさんのブログ記事
BLOCKSを使った記述で、リリースビルドのみにクラッシュする事例
これは元のコードの問題はスタック領域上のブロックオブジェクトをスコープ外で使ってしまったという割と有りがちなバグだったのですが、 念のため検証してみました。
ところがブロックオブジェクトのデフォルトの生成場所(スタック領域かヒープ領域か)がデバッグコンパイル時にはどうも違うらしくて、 そういう挙動を知らずにデバッグコンパイルで検証してしまった為にハマってしまいました。
今回のはちゃんとリリースコンパイルでもテストしないといけないという教訓です。
ブロックのデフォルトの生成場所はスタック領域なのですが、デバッグだろうがリリースだろうがそれは同じだと思い込んでいました。
特に__block属性付き変数をキャプチャするとブロックの生成場所は最初からヒープになるみたいです。何でかな?…
検証コード
コードはTOMOHISAさんのものですが、
NSLog(〜〜,block) の箇所とコメントは私が検証の為に追加しました。
因みに_tweetはこのコードが属するオブジェクトのメンバ関数です。
ARCです。
デバッグビルドでのログ出力
2012-04-21 13:49:22.109 TOVPopoverSample[10592:707] Pattern 1 <__NSMallocBlock__: 0xfe44ee0>
2012-04-21 13:49:34.734 TOVPopoverSample[10592:707] Pattern 2 <__NSMallocBlock__: 0xfe45b20>
2012-04-21 13:49:39.624 TOVPopoverSample[10592:707] Pattern 3 <__NSMallocBlock__: 0x13dc50>
2012-04-21 13:49:43.224 TOVPopoverSample[10592:707] Pattern 4 <__NSMallocBlock__: 0xfe45110>
見ての通り4パターンともmallocされたブロック、つまりヒープ上に生成されてます。 これではバグの検証は出来ませんね。
別に全部問題ないじゃん、みたいに見えてしまいます。
「ほらスタック領域にブロック生成されるからヒープにコピーしないと」と言う結論を期待したのに「なんで…」と数時間悩んでしまいました。
リリースビルドでのログ出力
2012-04-21 13:10:39.912 TOVPopoverSample[10504:707] Pattern 1 <__NSStackBlock__: 0x2fe54510>
2012-04-21 13:10:54.860 TOVPopoverSample[10504:707] Pattern 2 <__NSMallocBlock__: 0x149250>
2012-04-21 13:11:18.754 TOVPopoverSample[10504:707] Pattern 3 <__NSStackBlock__: 0x2fe544c4>
2012-04-21 13:12:11.226 TOVPopoverSample[10504:707] Pattern 4 <__NSStackBlock__: 0x2fe544ac>
ほぼ予想通りStackBlockとなってます。
このスタックブロックを辞書に登録しておいてスコープ外で使おうとしているのでバグだね、と分かる訳です。
ただパターン2、__block属性の変数をキャプチャしたケースだけはMallocBlockとなってます。
なぜこのケースではヒープに生成されるのか理由は分かりませんが、この為に問題無いかの様に動いたのですね。
結論
Blocksはデバッグビルドでのテストだけで満足してはならない。
リリースビルドでも入念にテストすべきである。
因みに。。。
このケースでは正解は4なのですが、いつもコピーする方が良い訳ではないので注意が必要です。
ヒープにコピーしたらしたで、リークだとか別の心配をしないといけなくなりますから。
4ではブロックをスコープ外で使いたい為にヒープにコピーしています。
ただし!!
ヒープにコピーしたブロックを使い終わったら忘れない様にリリースしないといけません。
今回のはARCなので問題なさそうではありますが。
__blockをキャプチャするとヒープブロックになるのを知ってたのなら2も正解じゃん、
という意見もあるかも知れませんが、そういう裏ワザ的なのはここでは不正解とさせて頂きます(笑)
BLOCKSを使った記述で、リリースビルドのみにクラッシュする事例
これは元のコードの問題はスタック領域上のブロックオブジェクトをスコープ外で使ってしまったという割と有りがちなバグだったのですが、 念のため検証してみました。
ところがブロックオブジェクトのデフォルトの生成場所(スタック領域かヒープ領域か)がデバッグコンパイル時にはどうも違うらしくて、 そういう挙動を知らずにデバッグコンパイルで検証してしまった為にハマってしまいました。
今回のはちゃんとリリースコンパイルでもテストしないといけないという教訓です。
ブロックのデフォルトの生成場所はスタック領域なのですが、デバッグだろうがリリースだろうがそれは同じだと思い込んでいました。
特に__block属性付き変数をキャプチャするとブロックの生成場所は最初からヒープになるみたいです。何でかな?…
検証コード
コードはTOMOHISAさんのものですが、
NSLog(〜〜,block) の箇所とコメントは私が検証の為に追加しました。
因みに_tweetはこのコードが属するオブジェクトのメンバ関数です。
ARCです。
// NSMutableArray *arrRows = [NSMutableArray arrayWithCapacity:0]; { //検証コード1 #warning this code only crash on Release Build.... Don't use this NSMutableDictionary * dicRow = [NSMutableDictionary dictionaryWithCapacity:0]; [dicRow setValue:NSLocalizedString(@"Pattern 1 Crash",nil) forKey:kDicKeyLinkPopCellText]; [arrRows addObject:dicRow]; dispatch_block_t block = ^{ NSString *str = [NSString stringWithFormat:@"%@",[_tweet valueForKey:@"text"]]; [[UIPasteboard generalPasteboard] setString:str]; [[JTCAppNotificationManager sharedManger] startTimerNotificationWithMessage:NSLocalizedString(@"Copy succeeded", @"Copy succeeded") dulation:2.5 iconName:@"w17-check.png"]; }; NSLog(@"Pattern 1 %@",block); [dicRow setValue:block forKey:kDicKeyLinkPopBlock]; } { //検証コード2 NSMutableDictionary * dicRow = [NSMutableDictionary dictionaryWithCapacity:0]; [dicRow setValue:NSLocalizedString(@"Pattern 2 __block",nil) forKey:kDicKeyLinkPopCellText]; [arrRows addObject:dicRow]; #warning this code is not the way recommended how to use __block ... not recommended __block id bt = _tweet; dispatch_block_t block = ^{ NSString *str = [NSString stringWithFormat:@"%@",[bt valueForKey:@"text"]]; [[UIPasteboard generalPasteboard] setString:str]; [[JTCAppNotificationManager sharedManger] startTimerNotificationWithMessage:NSLocalizedString(@"Copy succeeded", @"Copy succeeded") dulation:2.5 iconName:@"w17-check.png"]; }; NSLog(@"Pattern 2 %@",block); [dicRow setValue:block forKey:kDicKeyLinkPopBlock]; } { //検証コード3 NSMutableDictionary * dicRow = [NSMutableDictionary dictionaryWithCapacity:0]; [dicRow setValue:NSLocalizedString(@"Pattern 3 only declare bt crash",nil) forKey:kDicKeyLinkPopCellText]; [arrRows addObject:dicRow]; #warning this code only crash on Release Build.... Don't use this id bt = _tweet; dispatch_block_t block = ^{ NSString *str = [NSString stringWithFormat:@"%@",[bt valueForKey:@"text"]]; [[UIPasteboard generalPasteboard] setString:str]; [[JTCAppNotificationManager sharedManger] startTimerNotificationWithMessage:NSLocalizedString(@"Copy succeeded", @"Copy succeeded") dulation:2.5 iconName:@"w17-check.png"]; }; NSLog(@"Pattern 3 %@",block); [dicRow setValue:block forKey:kDicKeyLinkPopBlock]; } { //検証コード4 NSMutableDictionary * dicRow = [NSMutableDictionary dictionaryWithCapacity:0]; [dicRow setValue:NSLocalizedString(@"USE THIS:Pattern 4 declare bt and copy block",nil) forKey:kDicKeyLinkPopCellText]; [arrRows addObject:dicRow]; id bt = _tweet; dispatch_block_t block = ^{ NSString *str = [NSString stringWithFormat:@"%@",[bt valueForKey:@"text"]]; [[UIPasteboard generalPasteboard] setString:str]; [[JTCAppNotificationManager sharedManger] startTimerNotificationWithMessage:NSLocalizedString(@"Copy succeeded", @"Copy succeeded") dulation:2.5 iconName:@"w17-check.png"]; }; NSLog(@"Pattern 4 %@",block); [dicRow setValue:[block copy] forKey:kDicKeyLinkPopBlock]; } NSMutableArray *sections = [NSMutableArray arrayWithObject:arrRows]; TOVLinkPopoverViewController *controller= [[TOVLinkPopoverViewController alloc] init]; controller.arrayLink = sections;
デバッグビルドでのログ出力
2012-04-21 13:49:22.109 TOVPopoverSample[10592:707] Pattern 1 <__NSMallocBlock__: 0xfe44ee0>
2012-04-21 13:49:34.734 TOVPopoverSample[10592:707] Pattern 2 <__NSMallocBlock__: 0xfe45b20>
2012-04-21 13:49:39.624 TOVPopoverSample[10592:707] Pattern 3 <__NSMallocBlock__: 0x13dc50>
2012-04-21 13:49:43.224 TOVPopoverSample[10592:707] Pattern 4 <__NSMallocBlock__: 0xfe45110>
見ての通り4パターンともmallocされたブロック、つまりヒープ上に生成されてます。 これではバグの検証は出来ませんね。
別に全部問題ないじゃん、みたいに見えてしまいます。
「ほらスタック領域にブロック生成されるからヒープにコピーしないと」と言う結論を期待したのに「なんで…」と数時間悩んでしまいました。
リリースビルドでのログ出力
2012-04-21 13:10:39.912 TOVPopoverSample[10504:707] Pattern 1 <__NSStackBlock__: 0x2fe54510>
2012-04-21 13:10:54.860 TOVPopoverSample[10504:707] Pattern 2 <__NSMallocBlock__: 0x149250>
2012-04-21 13:11:18.754 TOVPopoverSample[10504:707] Pattern 3 <__NSStackBlock__: 0x2fe544c4>
2012-04-21 13:12:11.226 TOVPopoverSample[10504:707] Pattern 4 <__NSStackBlock__: 0x2fe544ac>
ほぼ予想通りStackBlockとなってます。
このスタックブロックを辞書に登録しておいてスコープ外で使おうとしているのでバグだね、と分かる訳です。
ただパターン2、__block属性の変数をキャプチャしたケースだけはMallocBlockとなってます。
なぜこのケースではヒープに生成されるのか理由は分かりませんが、この為に問題無いかの様に動いたのですね。
結論
Blocksはデバッグビルドでのテストだけで満足してはならない。
リリースビルドでも入念にテストすべきである。
因みに。。。
このケースでは正解は4なのですが、いつもコピーする方が良い訳ではないので注意が必要です。
ヒープにコピーしたらしたで、リークだとか別の心配をしないといけなくなりますから。
4ではブロックをスコープ外で使いたい為にヒープにコピーしています。
ただし!!
ヒープにコピーしたブロックを使い終わったら忘れない様にリリースしないといけません。
今回のはARCなので問題なさそうではありますが。
__blockをキャプチャするとヒープブロックになるのを知ってたのなら2も正解じゃん、
という意見もあるかも知れませんが、そういう裏ワザ的なのはここでは不正解とさせて頂きます(笑)
2012年3月24日土曜日
Objective-C++でboostを使う時のリンクエラー
XCodeでObjective-C++でboostを使う時に以下のリンクエラーが出た場合。
Undefined symbols for architecture x86_64:
"boost::system::generic_category()"
boostの.dylibが足りないらしい。
libboost_system-mt.dylib をTARGETSのBuild PhaseのLink Binary With Librariesに追加する。
Undefined symbols for architecture x86_64:
"boost::system::generic_category()"
boostの.dylibが足りないらしい。
libboost_system-mt.dylib をTARGETSのBuild PhaseのLink Binary With Librariesに追加する。
2012年3月23日金曜日
Objective-cでコルーチン?!
「C10K問題を回避するためにコルーチンを使う方法」というブログ記事があって、なるほどと思いObj-cに移植しようとしたのですが、どうもコルーチンクラスをサンプルに特化させていじっている様で汎用性が無さそうであまり使い勝手よくありません。
仕方ないので元記事のソースはあまり参考にせずに移植と言うより作ってみました。
→ここgithub
C10K問題を回避するためには、コネクション数とスレッド数が比例しないような設計をしなければならない事からコルーチンが有効らしいです。
実際に通信するコードを書ければ素晴らしいですが、面倒くさいので省いてコルーチンの動作を確認するだけです。
まぁObj-cのコルーチンはCSCoroutineとかあるみたいですが、setjmp, longjmp使ってるのが気持ち悪かったりします。(食わず嫌い)
yieldではsetjmp, longjmp使わず普通にreturnで抜けているのでローカル変数等は保存されません。
なのでローカル変数の代わりにメンバ関数を使っています。
そういう作りなのでコルーチンからサブコルーチンを呼ぶ時には新たなコルーチンオブジェクトを作成します。
色々と制約あって万能ではありませんが、ちょこっとコルーチンを使いたいぐらいなら丁度よいかも。
使用例
二組のコルーチンを使って擬似マルチスレッドをシングルスレッドで実現しています。
performメソッドがコルーチンの中身ですが、COR_YIELDでmainへ値を戻し、mainで次にnextメソッド呼ぶとCOR_YIELDの直後から続き実行します。
仕方ないので元記事のソースはあまり参考にせずに移植と言うより作ってみました。
→ここgithub
C10K問題を回避するためには、コネクション数とスレッド数が比例しないような設計をしなければならない事からコルーチンが有効らしいです。
実際に通信するコードを書ければ素晴らしいですが、面倒くさいので省いてコルーチンの動作を確認するだけです。
まぁObj-cのコルーチンはCSCoroutineとかあるみたいですが、setjmp, longjmp使ってるのが気持ち悪かったりします。(食わず嫌い)
yieldではsetjmp, longjmp使わず普通にreturnで抜けているのでローカル変数等は保存されません。
なのでローカル変数の代わりにメンバ関数を使っています。
そういう作りなのでコルーチンからサブコルーチンを呼ぶ時には新たなコルーチンオブジェクトを作成します。
色々と制約あって万能ではありませんが、ちょこっとコルーチンを使いたいぐらいなら丁度よいかも。
使用例
二組のコルーチンを使って擬似マルチスレッドをシングルスレッドで実現しています。
performメソッドがコルーチンの中身ですが、COR_YIELDでmainへ値を戻し、mainで次にnextメソッド呼ぶとCOR_YIELDの直後から続き実行します。
// #import "TestCoroutine.h" #import "CoroutineX.h" @interface TestCoroutine (/*PRIVATE*/) - (NSString*)makeChildName; @end @implementation TestCoroutine { int _concurrency_level; NSString *_name; int _cl, _i, _j; NSString *_retStr; } - (id)initWithConcurrencyLevel:(int)lv name:(NSString*)name { self = [super init]; if (self) { _concurrency_level = lv; _name = name; } return self; } - (NSString*)makeChildName { static int c =0; return [NSString stringWithFormat:@"%@c%d", _name, c++]; } - (id)perform { /* You can NOT use any local variables in this method. */ static int forkDepth = 2; COR_BEGIN; for (_cl = 0; _cl < _concurrency_level; ++_cl) { //recursive call COR_FORK([[TestCoroutine alloc] initWithConcurrencyLevel:forkDepth-- name:[self makeChildName]]); } for (_i=0; _i < 3; ++_i) { _retStr = [NSString stringWithFormat:@"%@'s loop1 i=%d",self->_name, _i]; COR_YIELD(_retStr); } for (_i=0; _i < 2; ++_i) { for (_j=0; _j < 3; ++_j) { _retStr = [NSString stringWithFormat:@"%@'s loop2 i=%d, j=%d",self->_name, _i, _j]; COR_YIELD(_retStr); } } //sub coroutine call COR_FORK([[SubCoroutine alloc] init]); COR_END; } @end @interface SubCoroutine (/*PRIVATE*/) @end @implementation SubCoroutine - (id)perform { /* You can NOT use any local variables in this method. */ COR_BEGIN; COR_YIELD([NSNumber numberWithFloat:1.2]); COR_YIELD([NSNumber numberWithFloat:2.3]); COR_END; } @end
// // main.m // #import#import "TestCoroutine.h" int main(int argc, const char * argv[]) { @autoreleasepool { CoroutineState *corState1 = [[CoroutineState alloc] init]; TestCoroutine *coro1 = [[TestCoroutine alloc] initWithConcurrencyLevel:3 name:@"p0"]; [corState1 push:coro1]; CoroutineState *corState2 = [[CoroutineState alloc] init]; TestCoroutine *coro2 = [[TestCoroutine alloc] initWithConcurrencyLevel:2 name:@"p1"]; [corState2 push:coro2]; id retVal1, retVal2; do { retVal1 = [corState1 next]; retVal2 = [corState2 next]; NSLog(@"1:%@, 2:%@", retVal1, retVal2); NSLog(@"1's stack-size=%ld, 2's stack-size=%ld", [corState1 stackSize], [corState2 stackSize]); } while (retVal1 || retVal2); } return 0; }
2012年3月5日月曜日
iOS,ARC:weakポインタを使う場合の注意
うっかり循環強参照(循環参照では無い)にしてしまうとオブジェクトが解放されずリークとなりますので、
弱い参照を使って循環強参照を防ぎます。
ただし、weak修飾子は iOS5以降でしか利用できないので、iOS4ではunsafe_unretainedをweak修飾子の代わりとします。
ところがunsafe_unretainedは挙動が素直と言うかweakほど気が利かないので、iOS5/ARCに慣れてしまうとiOS4/ARCで痛い目を見ると言う話です。
詳細はこの元ネタ参照。
元ネタはObj-cお馴染みのデリゲートパターンでは循環参照になるので、これが循環強参照とならない様にdelegateの属性を弱参照とするのだが、うっかりiOS4/ARCで痛い目を見たという話です。
weak属性が気が利いてるというのは、例えばこの本によると、
コンパイラは、__weak修飾子付きの変数をアクセスする場合、必ずautoreleasepoolに登録してからアクセスする様なコードを生成するらしい。
わざわざこんなコードを生成する理由は元ネタを読むと分かるので説明は省略します。
●対策
元ネタにも対策が書いてあるのですが、問題が発覚してからの対処の様な感じでちょっと分かりにくいと感じるかもしれません。
ここは敢えて別の方法を考えてみましょう。
要はレシーバが弱参照の場合は__weakの時と同じ挙動をする様にすれば良いのです。
こんなマクロ関数(WeakSendMessage)を作ります。
この様にレシーバが弱参照のメッセージングの箇所を
これでiOS4でもiOS5でも同じ挙動になるはず。
と言うか、循環参照するならiOS4は切り捨てろ。
iOS4を切り捨てられないなら循環参照はするな、というのが正しいのかもしれません。
因みにWeakSendMessageの一時ポインタの属性__autoreleasingを__strongに変えても問題無く動作します。
弱参照weak_ptrからは直接参照先にアクセス出来なくて、メンバ関数lock()で強参照を取り出す必要があります。
こんな感じ
弱い参照を使って循環強参照を防ぎます。
ただし、weak修飾子は iOS5以降でしか利用できないので、iOS4ではunsafe_unretainedをweak修飾子の代わりとします。
ところがunsafe_unretainedは挙動が素直と言うかweakほど気が利かないので、iOS5/ARCに慣れてしまうとiOS4/ARCで痛い目を見ると言う話です。
詳細はこの元ネタ参照。
元ネタはObj-cお馴染みのデリゲートパターンでは循環参照になるので、これが循環強参照とならない様にdelegateの属性を弱参照とするのだが、うっかりiOS4/ARCで痛い目を見たという話です。
weak属性が気が利いてるというのは、例えばこの本によると、
コンパイラは、__weak修飾子付きの変数をアクセスする場合、必ずautoreleasepoolに登録してからアクセスする様なコードを生成するらしい。
// id __weak obj1 = obj0; NSLog(@"class=%@", [obj1 class]);を
// id __weak obj1 = obj0; id __autoreleasing tmp = obj1; NSLog(@"class=%@", [tmp class]);と等価なコードに生成するそうです。
わざわざこんなコードを生成する理由は元ネタを読むと分かるので説明は省略します。
●対策
元ネタにも対策が書いてあるのですが、問題が発覚してからの対処の様な感じでちょっと分かりにくいと感じるかもしれません。
ここは敢えて別の方法を考えてみましょう。
要はレシーバが弱参照の場合は__weakの時と同じ挙動をする様にすれば良いのです。
こんなマクロ関数(WeakSendMessage)を作ります。
// //1:弱参照にweakを使う。0:弱参照にunsafe_unretainedを使う #define kUsingWeak 0 #if kUsingWeak #define unretained weak #define __unretained __weak #define WeakSendMessage(_r, _s, _a1) \ [(_r) _s(_a1)]; #else #define unretained unsafe_unretained #define __unretained __unsafe_unretained #define WeakSendMessage(_r, _s, _a1) \ __autoreleasing NSObject *tmp = (_r); \ [tmp performSelector:@selector(_s) withObject:(_a1)]; #endifいったん__autoreleasing属性の一時ポインタに置き換えてます。
この様にレシーバが弱参照のメッセージングの箇所を
// //self.delegateは弱参照 [self.delegate callFromClassB:self];こう変えます。
// //self.delegateは弱参照 WeakSendMessage(self.delegate, callFromClassB:, self);
これでiOS4でもiOS5でも同じ挙動になるはず。
と言うか、循環参照するならiOS4は切り捨てろ。
iOS4を切り捨てられないなら循環参照はするな、というのが正しいのかもしれません。
因みにWeakSendMessageの一時ポインタの属性__autoreleasingを__strongに変えても問題無く動作します。
// #define WeakSendMessage(_r, _s, _a1) \ __strong NSObject *tmp = (_r); \ [tmp performSelector:@selector(_s) withObject:(_a1)];この方法はC++0x(あるいはboost)のスマートポインタweak_ptrと考え方がだいたい似ていると思われます。
弱参照weak_ptrからは直接参照先にアクセス出来なくて、メンバ関数lock()で強参照を取り出す必要があります。
こんな感じ
//弱参照から強参照を取り出している weakPtr.lock()->doIt(); //weakPtr->doIt(); //これはコンパイルエラーweak_ptrは使った事が無いので、確信はありませんが…
2012年3月4日日曜日
iOS:TableViewのセクションヘッダにUITextFieldを追加する
UITableViewControllerに以下を追加する。
*iOS5 ARCです。MRCの場合は少しコードを変える必要があります。
*iOS5 ARCです。MRCの場合は少しコードを変える必要があります。
// static const CGFloat kHeight = 40.0f; - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { UIView *v = [[UIView alloc] init]; UITextField *txtF = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 160.0f, kHeight)]; v.backgroundColor = [UIColor brownColor]; txtF.textColor = [UIColor yellowColor]; [v addSubview:txtF]; return v; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return kHeight; }
登録:
投稿 (Atom)