2012年1月31日火曜日

別スレッドを使うときの注意

performSelectorInBackground:等で別スレッドを造り処理を並行動作させるとき、
「スレッドではautorelease poolを別途作らなければならない。」
と、仕様書に書いてある。

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];


[pool release];



別スレッドでは常にそれを作るように書いておけばいいが、
処理時間を少しでも短くしたいなら、autoreleaseを使わないのなら、その確保も省略したい。

ユーザーが直接autoreleaseを使わないならそれでもいい、と思いがちだが、
実は暗黙に使われる場合があるので要注意である。
 そのあたりは仕様書をよく読まないといけない。

たとえば、 UIGraphicsGetImageFromCurrentImageContext()は仕様書(UIKit Function Reference)にて「An autolereased object」と書いてある。
だから、これを別スレッドないで使うなら、autorelease poolの生成は必須となる。
これを忘れると、実行時にメモリリークが出る。

・・・

iOS4.xではこの通り。
ところが、iOS5ではそれが出ない。autorelease poolを自動生成してくれるようになったのか、単にエラーが出てないだけなのかわからない。いずれにしても、ちゃんと書いておくのが吉である。

2012年1月26日木曜日

タッチイベントの発生仕方

UIView上、正確にはUIResponderでは、画面をタッチするとtouchesBegan/Moved/Ended/Cancelledイベントが発生する=メソッドが呼び出されるが、どういう状態のときにどう送られてくるかは資料がなかった。

とりあえず解ったのは以下の通り。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

(1)基本はBegan(押したとき)->Moved(移動中)->Ended(指を離した)の順で呼び出される
ただし、移動が少ないときは Movedは来ない場合もある。
また、複数タッチしたときは、その都度Beganが発生する。

(2)同点タッチではタッチイベントは発生し続けない
逆に言えば、イベントは座標もしくは押下状態が変化したときのみ発生する。
タッチ中かどうかは上記イベントの発生状況によって判定するしかない。

(3)タッチが何本指なのかは[touches count]で取得出来る
iOS5ではGestureRecogniserでシングル/マルチ他の判定を行ってくれるようだが、iOS4までは処理内でこれを数えて処理を分けるしかない。

(4)マルチタッチは[touches count]==2だけで処理するとたぶん半分は抜けてしまう
実は人間の指の動作はそんなに厳密ではないので、Began*2という押され方をすることもある。それもマルチタッチと見なす方が「親切」だと思う。
私の作ったアプリはそうしている。
began(cnt=1)->moved(cnt=1)->began(cnt=1)->moved(cnt=2)なんてパターンもあるので注意。

(5)マルチタッチの場合、touchesで返ってくる座標情報の順番は不定
押下順ではない。従って、スライドの移動方向を得ようとするときには、全ての座標を読み取って、正しい組み合わせで算出する必要がある。
 (6)ストロークしたまま画面外に出る・中に入るときのbegin/endedの状態
    シミュレーター上
        外に出る    マウスクリックを離した時点でEnded(画面外座標を返している)
        中に入る    全て無視される(中に入っても検出されない)
    実機上
        外に出る    物理画面内なら、タッチを離した時点でEnded(画面外座標を返している)
        中に入る    全て無視される(中に入っても検出されない)
        iOS5では、スータスバーの位置から下ドラッグすると、Nortify画面が出てくる

まあ、他にもいろいろあるが、プログラムを組む上で最低限必要なのはこれくらいかと。
マルチタッチ時の座標の処理は結構面倒で、頭の体操になる。


「長押しはどう処理すんねん」とかあるかもしれないけど、それはいずれまた。

2012年1月23日月曜日

第7弾アプリ「手習い君」発売開始

第7弾「手習い君」を発売開始

AIG-Soft 第7弾アプリ「手習い君」が発売になりました。

般若心経を始め、小学生漢字やかな文字などを手書き感覚でなぞって練習するアプリです。
やっぱり、主な目的は写経です(^_^;)。
写経アプリは既存のものもありますが、全てが文字送りのボタンを押す必要があるなど
コンピューター的操作が必要で、およそ本当の写経とは違います。

「手習い君」は紙に字を書く感覚の再現を重視しました。 
自動スクロール機能などにより、ユーザーが写経に集中出来る状態を演出します。 

出来上った書面は、メールで送信したり、AirPrintで印刷したり出来ます。

よろしくお願いします。 

2012/02/05追記
V1.1にバージョンアップしました。細かいバグが修正されています。

ここの維持のためにも、ご購入いただけるとありがたいかとm(_ _)m。

2012年1月22日日曜日

列挙子の使い方と注意


配列要素にアクセスするとき、Cでは要素数を自分で管理する必要があった。
要素数を超えてアクセスしても言語側は知らんぷりなので、暴走の原因になることもしばしばである。
(高速化などトリックにも使えるのだが、それは上級者のみに許された究極の技。)
そもそも、要素数をユーザーが管理する必要がある。

しかし、Cocoaのクラスでは要素数をシステムが管理してくれるので、範囲を超えて越えてアクセスすると
例外がかかり止まる。また、要素数は[count]メッセージで取得出来る。
そのため、安全(要素個数を超えることなく)にループを回すことが出来る。

要素番号が必要な場合はこのループを使う。
NSUInteger i; // 要素番号
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
for (i = 0; i < [array count]; i++) {
    NSLog(@"index: %d, value: %@\n", i, [array objectAtIndex:i]);
}

Objective-Cでは「列挙子」というものを使ってループを構築することも出来る。
これは要素の終端を判定してループを構築するもので、要素番号を保持しておく必要がなくなる
(逆に言えば、ループ内では要素番号を参照することはそのままでは出来ない)。
これにはNSEnumeratorとwhileループを使う。
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
NSEnumerator *enumerator = [array objectEnumerator];
id obj;
while (obj = [enumerator nextObject]) { // nilで終了
    NSLog(@"value: %@\n", obj);
}
// enumeratorは解放の必要がない(というかしたら駄目)
objectEnumeratorの部分をreverseObjectEnumeratorにすると、配列の終端から先頭=0に向けての逆順になる。
例はNSArrayに対するものであるが、NSDictionaryでも同様な処理がかける。

Objective-C 2.0ではさらにfor文が拡張され、配列・辞書の内容が高速かつ安全に「参照」出来る様になっている。
高速列挙子である。ここで言う「高速」はコンパイラが最適化したコードを発生するという意味であり、
「安全」は終了判定を記述する必要がないことを意味する。
NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
for (id obj in array) { // 新しいクラス変数を作り、上記arrayの内容を参照する(最後まで)
    NSLog(@"value: %@\n", obj);
}

(高速)列挙子を使ったループをMutableで構築する場合、1つ注意がある。
それは、ループ内で要素の更新をしてはいけないことである。
NSMutaleArray *marray = [NSMutableArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil];
NSEnumerator *enumerator = [marray objectEnumerator];
id obj;
NSInterger i=0;
while (obj = [enumerator nextObject]) { // nilで終了
    NSLog(@"value: %@\n", obj);
    ~
    [marray replaceObjectAtIndex:i withObject:obj];
    i++;
}
とすると、例外が発生する。replaceObjectAtIndexのように、配列要素数が変化しない場合でも
同様である。要素を変更する場合は、素直に要素番号を持ってループを回すしかない。






2012年1月18日水曜日

ソース部分をSyntaxHighlighterで表示する

このブログに載せる記事の中で、プログラム部分をわかりやすくしたいと考えていた所、「SyntaxHighlighter」という物があることを知った。
javascriptを使って整形するらしい。

SyntaxHighlighter

これを使うと以下のように出来る。

「例」
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    // iPad
} else {
    // それ以外
}

「出来る」と書いたが、実際にはSyntaxHighlighterのHPのinstllationに書かれている方法は
Bloggerではそのまま使えない。
Bloggerにはjavascriptファイルを中に置くことが出来ないので、外部サーバーのどこかに置くか、
記事の中に全てを埋め込む必要がある。

最初は、script実体をファイルから本文内に展開していた。
scriptなので表示されないが、結構な行数である。
しかも、再編集をかけるとなぜか無効になってしまう問題があった
(スクリプティング部分が勝手に書き換えられてしまう感じ。再貼り付けすると直る)。

そのため、別の場所に置いたファイルを読み込ませるようにした。
これなら再編集してもおかしくならないし、比較的簡単。

ただし、SyntaxHighlighterからダウンロードしたファイルをそのまま展開したディレクトリ構造と、
installationに書かれているサンプルに書かれているディレクトリ構造は異なるので、
書き換えが必要である。
また、リストに入れる言語別に使用するスクリプトファイルも変更しなければならない。

また、Objective-CではC用のスクリプトを使うのだが、これにはバグがあって、
プロトコル宣言である<プロトコル名>を1行で書くと、リストの最後というか
<pre>の直前にそのプロトコル行が再表示されてしまう。
これを回避するには<とプロトコル名と>を改行で分けて記述すれば良い。
スクリプトを書き換えれば直るのかもしれないが、解らなかった。

HTMLモードで記述し直す必要があるので、過去にさかのぼっての適応は
「気が向いたら」することにした。

Bloggerが標準でサポートしてくれれば良いのだが。
それとも、私が知らないだけで方法はあるのだろうか?







2012年1月14日土曜日

plistからプログラム名とバージョンを得る方法

plistからプログラム名とバージョンを得る方法は以下の通り。

NSString *programName=[[[NSBundle mainBundle]infoDictionary]objectForKey:(NSString *)kCFBundleNameKey];
NSLog(@"programName=%@",programName);
NSString *programVersion=[[[NSBundle mainBundle]infoDictionary]objectForKey:(NSString *)kCFBundleVersionKey];
NSLog(@"programVersion=%@",programVersion);

defaultデータベースに書きこむ;これにより、iOS内設定でもこれらの値が読めるようになる。

Root.plistに書くkey名(Identifier)はdefault内のキー名またはRoot.strings内のキー名である。

NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:programName       forKey:keyProgramName];
[defaults setObject:programVersion forKey:keyProgramVersion];







2012年1月12日木曜日

iOSシミュレーターの限界

iOSシミュレーターはiOSアプリ開発に欠かせないものではあるが、
残念ながら、実機の全てを正確にシミュレート出来るわけではない。

以下の項目はiOSシミュレーターでは解らない。

・シミュレーター上ではメモリリークは発生してない(検出されない)のに、実機だと発生する。
実機をつないでProfileのLeakをするとそうなることがある。
シミュレーター(86)と実機(ARM)でライブラリの出来が違うのだろうか?
当然実機で直すのが筋なのではあるが、全く原因がわからない物も多くて、小規模な物なら放置しているのが現状。


・固有デバイスの動作
カメラ、ジャイロ、デバイスモーションなど。
GPSは「デバッグ~位置」から仮想的にデータを与えることは出来るが、
やはり実機で確認した方が良い。
BluetoothはOK、AirPrintもシミュレーターで印刷出来る。

・実速度
特にグラフィック周りの速度差が大きい。
実機でも、iPad2とiPod touch4は差が大きい(グラフィック割りのハードの差がはっきり出る)。

・メモリ容量
シミュレーター上は事実上無制限なので、その上で動いたからといって、実機で動くとは限らない。メモリもしくはUI要素をたくさん使うアプリでは特に注意。

・画面回転の中間状態(unknown)と上下向き(Faceup/down)
実機では正立と逆が一瞬で切り替わることがあるが、シミュレーターではそれが出来ない。

・電池残量
それに関わるNotificationも同様。

・メール実送信
Xcode3まではメーラーそのものも立ち上がらなかった。
Xcode4では立ち上がるが、実送信はされない。
FrontierMailでは送信出来る。

・imageの透過状態
実機では256色でしか透過しないが、シミュレーター上はフルカラーでも透過する。
これは単なるシミュレーターのバグだと思う。
iOS4/5/6用全てで発生する。

・3本以上指のマルチタッチ
ダブルまではシミュレーター上でもピンチ/スライドともに可能。

・スリープ
連続稼働させる必要があるアプリではこの確認が必須。
(自作の物では、ZeroRec/Komadori/ChariRecoがこれに当たる)

・CPU固有の挙動
シミュレーターは86のコードでコンパイルされ、実機はARMでコンパイルされる。
この違いは、メモリアクセスへの制約の違いとして現れる。
大雑把に言えば、86はメモリアクセスに対して制約がないが、ARMは
アクセスサイズとバイト境界の関係に制約があり、違えるとトラップを発生して落ちる。
ワークエリアに対しバイトではないサイズでデータを読み書きするプログラムは、
シミュレーター上では動作しても実機では動かないことがある。
X−BASIC for iOSではこれで大いに苦労した。

とにかくシミュレーターと実機は似て非なる物なので、アプリは必ず実機での動作確認が必要である。

特にメモリ容量の違いは大きい。シミュレーター上では動くのに実機では起動すら出来ないアプリはほぼ間違いなく「絶対に実機での動作確認をしてない」と思われる。有名どころでも多々存在する。

気をつけよう。

2012年1月11日水曜日

iOSシミュレーター上のアルバムに画像を入れる方法

iOSシミュレーター上のアルバムに画像を入れる方法。

  1. iOSシミュレーターを起動し、その上にFiderから画像をドラッグ
  2. iOS内のSafariが立ち上がり、画像を表示する
  3. 画像上でマウス左クリックを続ける
  4. 「画像を保存」「コピー」が出るので、保存を選択 
アルバム上に画像がないとデバッグ出来ないときはこの方法で入れ込む。

2012/12/07追記
iOS6上では上記の他に、Safariに直接画像をドラッグし、
アクションからアルバムに保存ができる。

2012年1月10日火曜日

Xcode起動時のプロジェクトの選択

Xcodeを起動した時、プロジェクトを選択するダイアログが出るが、
ここは、そのままにしていると、テストで開いたようなすでに存在しないプロジェクトまで
残り続けてしまう。


これを整理するには、
    Xcode-organaizer-projects

でプロジェクト履歴一覧を開き、Not Foundになっている、もしくは存在してもリストには出したくないプロジェクトを選択、BackSpaceキーを押して削除する。

プロジェクトの実体が消されるわけではないので、間違ったのなら、また開けば良いだけ。

2012/01/19追記
どうもこれでは消えない場合もあるようで。困ったなぁ。

2012年1月9日月曜日

Localize.stringsの中で#ifは使えるのか?

Localize.stringsの中では#ifは使えない。
これは、 Localize.stringsはプリプロセッサは通らない事を意味する。
故に、全てのプリプロセッサ命令も同様に使えない。

同一ソースからコンパイル条件によって複数のアプリを生成する場合、
Localize.strings内には常に、無駄になっても全てのアプリで使う分の文字列を
入れる必要があるということになる。


#ifが使えれば便利なのだが、将来のXcodeバージョンアップで何とかしてくれないものだろうか。

2012年1月8日日曜日

UIImagePickerControllerの使い方(4)

書くのをすっかり忘れてたので間が空いたけど、次は画像選択。

iPadでは、UIPopoverController内で表示させる必要がある。
iPod/iPhoneではNavigation配下のビューでOK。

@interface SetupTableViewController : UITableViewController
<
// デリゲートが必要
UIPopoverControllerDelegate,
UINavigationControllerDelegate,
UIImagePickerControllerDelegate
>
{
    UIPopoverController *imagePopoverController;    // imagePicker用
    IBOutlet    UIImagePickerController *imagePicker;
}

nib上でUIImagePickerControllerを配置、接続しておく。
imagePopoverController=[[UIPopoverController alloc]initWithContentViewController:imagePicker];
imagePicker.delegate=self;
imagePopoverController.delegate=self;
[imagePopoverController presentPopoverFromBarButtonItem:parentSender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

画像を選択すると、デリゲートが呼び出される。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info

画像そのものは、UIImage *で
    [info objectForKey:UIImagePickerControllerOriginalImage]
で得られる。

iPadではPopover内でimagePickerControllerを開くので、閉じるのもそれが行わなければならない。このあたり、iPhoneとは異なるので注意(ちまたの書籍では有名本を含めほとんどこの点については記載されていない。)
    [imagePopoverController dismissPopoverAnimated:YES];









Supported interface orientationの意味

info.plistのSupported interface orientationは、そのアプリのサポートする画面回転方向を設定するが、もう1つ「起動時の画面方向を指定する」意味も持つ。

特に重要なのがitem0で、これで設定した方向で起動しようとする。


横画面で起動するアプリでない場合、基本的にはここはPortraitのbutton home buttonにする。
top homeにすると、ホームボタンが上にある状態で起動した様になる。
シミュレーターで実行するとよく分かる。

起動画面の向きがおかしい場合、ここも確認するといいかもしれない。