[iOS] プロダクト名を変えてはいけない

2010年11月30日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

痛い目にあったので情報共有&覚書。

AppStore へリリース済みのアプリのプロダクト名を変更したところアプリが起動しなくなってしまった。今回のケースでは頭の大文字小文字を変更したところで問題が出た。

(例)"Some" => "some"

AppStoreからインストールしたアプリが iPhoneに存在する状態で、Xcodeでビルドした最新版を上書きでインストールすると次のエラーが出て起動しなかった。アイコンは出来ているがその後タップしても起動に失敗する。
Error launching remote program: No such file or directory 
(/private/var/mobile/Applications/8AFC8F0D-D8B7-4925-B56F-A4AD918D052C/Some.app/some).
Error launching remote program: No such file or directory
 (/private/var/mobile/Applications/8AFC8F0D-D8B7-4925-B56F-A4AD918D052C/Some.app/some).
The program being debugged is not being run.
The program being debugged is not being run.
何故かアプリ名が Some.app(頭大文字)になっている。ターゲットのビルド設定を見ても頭大文字の "Some" という記述は無い。Some.xcodeproj フォルダ内にある project.pbxproj(XML)をひらいてみたところここに productName="Some" という記述が残っていた。これを手で "some"に直すと初インストールの状態では動作するようになった。ただ既に旧バージョンが入っている状態での上書きには失敗する。

いろいろいじっていると次のエラーのケースもあった。
Error launching remote program: failed to get the task for process 2308.
Error launching remote program: failed to get the task for process 2308.
The program being debugged is not being run.
The program being debugged is not being run.

Bundle Identifier が同じなら大丈夫だろうと思っていたのが甘かった。

ということでリリース後にプロダクト名は変えない方が良いみたいだ。

[iOS] IBOutletCollection とは?

2010年11月29日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

twitter で iOS 4.0 から IBOutletCollection なるものが導入されたと小耳に挟んだので調べてみた。

IBOutletCollection


IBOutletCollecation は IBOutletを束ねて NSArray で扱えるようにした新し仕組み。iOS 4.0 より導入された。次のように宣言する。
@interface SomeController : UIViewController {

 IBOutletCollection (UILabel) NSArray* labels;
}
この例では複数の UILabel を NSArray で扱えるようにしている。( ) 内には NSArray で扱う型情報を指定する。複数の型が混在する場合は id の指定もできる。
例えば Interface Builder で複数の UILabel で定義する。
設定の方法は従来と同じで一つ一つ接続していく。
複数接続すると Multipleと表示される。

一旦接続できればプログラムでこれらの UILabel をまとめて扱えるようになる。
for (UILabel* label in labels) {
 labels.text = @"good!";
}


サンプル


こんなサンプルを作ってみた。
#import <UIKit/UIKit.h>

@interface IBOutletCollectionSampleViewController : UIViewController {

 
 IBOutletCollection (UILabel) NSArray* labels;
 IBOutletCollection (UITextField) NSArray* textFields;
 IBOutletCollection (id) NSArray* stuffs;
}

- (IBAction)dump;
- (IBAction)action;

@end
接続はこんな感じになっている。
ACTIONボタンを押すと次の処理を実行する。
- (IBAction)action
{
 for (UITextField* textField in textFields) {
  textField.text = @"Hello!";
 }
 
 for (UIControl* control in stuffs) {
  control.enabled = !control.enabled;
 }
}
複数のコントロールに対してまとめて処理を行うことが容易にできる。


ソースコード


GitHub からどうぞ。
IBOutletCollectionSample at 2010-11-29 from xcatsan/iOS-Sample-Code - GitHub


備考


- (void)awakeFromNib
{
 NSLog(@"labels: %@", labels); 
}
awakeFromNib で IBOutletCollection 指定したオブジェクトをデバッグ表示したところ nil となっていた。
IBOutletCollectionSample[16985:207] labels: (null)
IBOutletCollection の充填は遅いようなのでここでの初期化処理は行えない。


参考情報


Interface Builder User Guide: Xcode Integration
IBOutletCollecation の記述が追加されている。

[Mac] NSTableView - セル編集終了時のイベントを掴む

2010年11月28日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

公式リファレンスの Q&A に解説がある。
Technical Q&A QA1551: Detecting the start and end edit sessions of a cell in NSTableView.
方法は Delegate と Notification の2つの方法がある。


NSControlTextEditingDelegate


NSTableViewDelegate を見ると編集開始時に呼ばれるメソッドしか無いのでセル編集終了時のイベントが取れないように見える。
- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:
(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
NSTableViewDelegate Protocol Reference

が、NSTableViewDelegate の親プロトコル である NSControlEdtingDelegate に必要なメソッドがあるのでそちらを使うことができる。
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor;
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor;
NSControlTextEditingDelegate Protocol Reference


name:NSControlTextDidEndEditingNotification


Deletate とは別に Notification を使うこともできる。QA1551より転載:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editingDidEnd:)
        name:NSControlTextDidEndEditingNotification object:nil];

- (void)editingDidEnd:(NSNotification *)notification
{
    NSTableView* tableView = (NSTableView*)[notification object];
     :
}
NSTableView は -object メソッドで取得できる。


呼び出しシーケンス


こんな感じ。
セルをダブルクリックして編集開始
 |
 | → tableView:shouldEditTableColumn:row:
 ↓
キー入力開始
 |
 |→ control:textShouldBeginEditing:
 ↓
リターンもしくはフォーカスを移動して編集終了
 |
 |→ control:textShouldEndEditing:
 ↓
NSControlTextDidEndEditingNotification


備考


Delegate は便利なのだが名前に "Should" が含まれていることからわかるように編集が完全に終了した後呼び出されるわけではない。このメソッドで NO を返すと編集をキャンセルすることができる。Cocoa Bindings を使っている場合このタイミングが問題になる場合がある。例えば NSArrayController を使っていて、この Delegate が呼ばれた時にモデルの内容を保存する場合など。このタイミングでは NSTableView のセルでの編集内容がまだバインド先のモデルに反映されていない為、編集前の古い内容での保存になってしまう。この場合、編集完了のタイミングを掴むには Delegate ではなく Notification の方を使う。NSControlTextDidEndEditingNotification を使えば編集内容がモデルに反映された後のタイミングで処理を行うことができる。


サンプル


簡単に動作確認できるサンプルを用意した。編集時にイベントがデバッグコンソールに表示される。
[16744:a0f] -[AppController tableView:shouldEditTableColumn:row:]
|<NSTableView: 0x10051c1a0>|<NSTableColumn: 0x10051d360> identifier: name|4
[16744:a0f] -[AppController control:textShouldBeginEditing:]|<NSTableView:
 0x10051c1a0><<NSTextView: 0x100565e90>
    Frame = {{1.00, 77.00}, {171.00, 17.00}}, Bounds = {{0.00, 0.00}, {171.00, 17.00}}
    Horizontally resizable: YES, Vertically resizable: YES
    MinSize = {171.00, 17.00}, MaxSize = {40000.00, 40000.00}
[16744:a0f] -[AppController control:textShouldEndEditing:]|<NSTableView: 
0x10051c1a0>|<NSTextView: 0x100565e90>
    Frame = {{1.00, 77.00}, {171.00, 17.00}}, Bounds = {{0.00, 0.00}, {171.00, 17.00}}
    Horizontally resizable: YES, Vertically resizable: YES
    MinSize = {171.00, 17.00}, MaxSize = {40000.00, 40000.00}
[16744:a0f] -[AppController editingDidEnd:]|NSConcreteNotification 0x10016c7a0
 {name = NSControlTextDidEndEditingNotification; object = <NSTableView: 0x10051c1a0>; userInfo = {
    NSFieldEditor = "<NSTextView: 0x100565e90>\n    Frame = {{1.00, 77.00},
 {171.00, 17.00}}, Bounds = {{0.00, 0.00}, {171.00, 17.00}}\n
    Horizontally resizable: YES, Vertically resizable: YES\n
    MinSize = {171.00, 17.00}, MaxSize = {40000.00, 40000.00}\n";
    NSTextMovement = 16;
}}

ソースコードは GitHub からどうぞ。
NSTableViewSample at 2010-11-28 from xcatsan/MacOSX-Sample-Code - GitHub

[Mac] NSTableView - Type Select

2010年11月27日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

NSTableView の Type Select を有効にするとキーボード入力に合った行が選択されるようにすることができる。例えば下図の表示で 'c' と押すと name列の c で始まる行が選択される。


キー入力には2文字以上にも対応していて c → 4 と連続して押せば c5 を飛ばして c4の行が選択される。

設定は Interface Builder の "Type Select" で行う。


もちろんメソッドでも設定できる。
NSTableView Class Reference

[Mac] NSTableView のカラムの位置やソート状態を保存する

2010年11月26日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

NSTableView は実行時にカラムの順番を入れ替えたりソート順を替えたりすることができる。この状態を保存しておき次回表示時に同じ状態に復帰させるにはどうしたら良いか?



autosaveTableColumns


NSTableView には autosaveTableName と autosaveTableColumns が用意されていてこれを使うことができる。
NSTableView Class Reference

この2つは Interface Builder で設定することができる。
設定すると NSTableView 〜 というエントリがアプリケーション設定ファイルに記録されるようになる。

これを使うとカラムを入れ替えや幅が記録され、次回表示時に自動的に反映されるようになる。

なおこの機能を使うには各カラムの Identifier を設定しておく必要がある。
設定しない場合実行時に下記のエラーが発生する。
NSTableViewSample[15812:a0f] Column identifiers used with 
 NSTableView autosave feature must conform to NSCoding protocol: (null)


Bindings 利用時のソート状態を保存する


Cocoa Bindings を使っている場合、NSTableView の autosave* は使えない。この場合は NSArrayController などのコントローラの Bindings 設定を使う。

NSTableView で使用している NSArrayController の Bindings設定を開き、"Sort Descriptors" を設定する。


バインド先(Bind to:)は "Shared User Defaults Controller" とする。これでアプリケーション設定ファイルに保存ができる。"Model Key Path" には任意の文字列を入れる。この文字列がアプリケーション設定ファイルでのキー値となる(なのでアプリケーション内で複数の NSTableView を扱う場合はユニークにしておく)。Value Transformer には plist で保存・読み込みができるように "NSUnarchiveFromData" を設定する。最後に "Validates Immediately" を設定するとソート順が変更された時に設定ファイルへ書き込まれるようになる。

これでソート順の自動保存・復帰が実現できる。

ソースコード


GitHub からどうぞ。

NSTableViewSample at 2010-11-26 from xcatsan's MacOSX-Sample-Code - GitHub


参考情報


sortDescriptors
今回の方法が解説されていた投稿。とても参考になった。

[Mac][iOS] Static Library (8) フレームワーク検索パス

2010年11月25日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: [Mac][iOS] Static Library (7) カテゴリを使う場合の注意点 "-ObjC" と "-all_load"

利用しているフレームワークのパスなどを変更した場合、次のエラーが出ることがある。

"Frameworks" ではちゃんと目的のフレームワークを指すように設定しているにもかかわらず出る。

これはビルド設定の「フレームワーク検索パス」が違っている場合がある。ターゲットの情報を開き「ビルド」タブの「フレームワーク検索パス」が正しいかどうかを確認する。
この設定は "Frameworks" へ登録した時に自動的に追加されるようだ。ただその後、"Frameworks"の方でパスを変更してもここには反映されない為に冒頭のエラーが出てている。

[Mac][iOS] Static Library (7) カテゴリを使う場合の注意点 "-ObjC" と "-all_load"

2010年11月24日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: [iOS] Static Library (6) OCUnitで単体テスト

カテゴリが定義されている Static Library を使う場合、リンカフラグに "-ObjC" と "all_load" を設定する必要がある。リンカフラグを設定しない場合 "selector not recognized" 例外が発生しクラッシュする。
[49296:207] -[NSCFString extString]: unrecognized selector sent to instance 0x3044
[49296:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
 reason: '-[NSCFString extString]: unrecognized selector sent to instance 0x3044'
*** Call stack at first throw:
このことは下記のQ&Aで解説されている。
この Q&A によればこの問題は UNIXの Static library実装 と Objective-C の動的な性質の違いによって引き起こされるとのこと。この問題を回避する為にリンカフラグに "-ObjC" オプションを追加する。

リンカフラグは、ターゲットの情報を開きビルドタブの「他のリンクフラグ」に設定する。
なお Mac OS X の64bit環境および iOS ではリンカにバグがあり、"-ObjC" フラグを指定してもカテゴリを含む Static Library のロードが働かない。これを回避する為に "-all_load" または "-force_load" フラグを使う。

"-all_load" フラグはリンカに全オブジェクトファイルのロードを指示する。"-force_load" は Xcode 3.2 から導入されたフラグで、オブジェクトファイル毎にロードを指示することができる。このフラグはオブジェクトファイルへのパスを引数に取る。


Duplicate Symbol 問題


[iPhone][Objective-C] staticライブラリにカテゴリを含む際に気をつけること によれば "-all_load" フラグも万能というわけではなく libtool のバグが原因で "Duplicate symbol" エラーを引き起こすケースがあるとのこと。この問題を回避するにはちょっとしたトリックを施すと良いらしい。以下、引用。
具体的には、以下のように関係ないクラスの宣言を.mの先頭の方に付加すればOK。「FIXCATEGORYBUG_NSSTRING_SAMPLE」は他と名前の衝突が無ければ何でもよいです。
  • NSString+Sample.m
@interface FIXCATEGORYBUG_NSSTRING_SAMPLE @end
@implementation FIXCATEGORYBUG_NSSTRING_SAMPLE @end

@implementation NSString (Sample)
 .
 .
 .
@end
この現象は確認できていないので今のところ何ともいえないがエラーが出た時に参考になりそうだ。


参考情報


IPHONE - Objective-C categories in static library - efreedom

use force_load - Three20 | Google グループ

[iOS] OCUnitで単体テスト〜アプリケーションテスト

2010年11月23日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: [iOS] Static Library (6) OCUnitで単体テスト

iOS Development Guide: Unit Testing Applications によればXcodeでOCUnit を使ったテストは2通りある。

  • Logical Tests
  • Application Tests

前回やったテストが Logical Tests にあたり、これはビルド時にテストを実行する。一方、Application Tests はビルド時ではなくアプリケーション実行時にテストを実行する。今回はこれを試してみた。

こんなプロジェクトを作る。ApplicationTestSampleTesting がアプリケーションテスト用ターゲット。
アプリを実行するとこんな内容がデバッグコンソールへ出力される。


手順


手順は iOS Development Guide: Unit Testing Applications に分かりやすく記載されている。今回は新規にプロジェクトを作成して試してみる。

1. 新規プロジェクト

View-based Application テンプレートを使って新規にプロジェクトを作成する。名前を ApplicationTestSample とした。


2. アプリケーションテスト用ターゲット作成

デフォルトで用意されるターゲットを複製する(右クリックで「複製」を選択)。名前は <アプリケーション名>Testing とする。


3. テストケース用ターゲット追加

続いてテストケース用ターゲット(バンドル)を追加する。「ターゲット」を右クリックして「追加」→「新規ターゲット」でダイアログを開き "Unit Test Bundle" を追加する。名前は "AppTests" とする(名前の最後を "Tests" としておく)。

次にこのターゲットをドラッグ&ドロップでアプリケーションテスト用ターゲット "ApplicationTestSampleTesting" に追加する。
さらい "Products" 内の "AppTests.octest" を "ApplicationTestSampleTesting" の「バンドルリソースをコピー」へドラッグ&ドロップで追加する。
これでアプリケーションにテストバンドルが格納され、実行時にテストが走るようになる。


4. テストコード作成

テストケース用のクラスを新規作成する。
名前を AppTest として追加先のターゲットは "AppTests" とする。
中身はこんな感じ。
#import <SenTestingKit/SenTestingKit.h&;gt;
#import <UIKit/UIKit>

@interface AppTests : SenTestCase {

}

@end
#import "AppTests.h"

@implementation AppTests

- (void) testFail {
 STFail(@"Must fail to succeed.");
}

@end

5. 実行

さて実行してみよう。実機を接続して実行する。

出た。デバッグコンソールに表示されるので非常に見づらい
Xcodeの「デバッグ」の設定でデバッグコンソールの色が変えると多少ましにはなる(あんまりかわらないか)。
こんな感じ。

なお、アプリケーションテストは実機でしか試せない。
シミュレータで実行しようとするとビルド時にテストコードが走ってしまいテストに失敗した場合は実行ができないので注意。これはリファレンスにちゃんと書いてあるのだが見落としていて最初はまった。


ソースコード


GitHub からどうぞ。
ApplicationTestSample at 2010-11-23 from xcatsan's iOS-Sample-Code - GitHub


参考情報


iOS Development Guide: Unit Testing Applications
単体テストの公式リファレンス

Cocoa with Love: A sample iPhone application with complete unit tests
公式リファレンスの補足的な資料となっている

[iOS] Static Library (6) OCUnitで単体テスト

2010年11月22日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: [iOS] Static Library (5) Frameworkを作成する


ライブラリのテスト


ライブラリは画面が無いのでデバッグがしずらい。テスト目的の専用アプリを用意するのもいいのだが、開発中は単体テストを導入するのが正攻法だろう。SDKには単体テスト用フレームワーク OCUnit が標準で付属するのでこれを使ってみる。これを使うとビルド時にテストケースを実行して問題があればこんな感じで Xcodeのビルド結果画面にエラーを表示してくれる。

手順


1.準備


まず OCUnit検証用に Static Library 開発用のサンプルプロジェクトを新規作成する。

テスト対象としてこんなクラスを用意しておく。
@interface SampleClass : NSObject {

}

- (NSString*)greeting;

@end
@implementation SampleClass

- (NSString*)greeting
{
 return @"Hello";
}

@end
この状態でビルドするとライブラリ(*.a)が作成される。

2. Unit Test Bundle


テスト用ターゲットとして Unit Test Bundle を追加する。

名前を "Unit Test" としてみた。

このターゲットの情報を開き「一般」タブの「直接依存関係」へライブラリビルド用のターゲットを追加する。こうするとテスト用ターゲットを実行した時、事前にライブラリのビルドを実行してくれる。
またテストケースを実行するプログラムをビルドするのにライブラリ(*.a)をリンクする必要があるのでこのターゲットへ先ほどビルドしたライブラリファイルを追加する。Products にある *.a ファイルをドラッグ&ドロップで「バイナリをライブラリにリンク」へ追加することができる(メニューから追加しても良い)。

3. テストケースを追加する


新規ファイルとして "Objective-C test case class" を選択する。
追加したテストケースのクラスファイルのターゲットを "Unit Test"にしておく。これをしないとテストケースが実行されない。
テストケースがサンプルとして自動生成されるがこれは削除して良い

4. テストケース実行


まず最初にこんなテストケースを書いてみた。
- (void)testGreeting
{
 counter_++;
 SampleClass* obj = [[[SampleClass alloc] init] autorelease];
 STAssertTrue([[obj greeting] isEqualToString:@"x"],
     @"failed");
}
テスト用メソッド名はtestで開始する必要がある。

アクティブターゲットに "Unit Test" を選択してビルドする。
出た。


わかったことなど


テストケース実行が成功したのでいろいろいじってみた。わかったことは次の通り。

STAssert* マクロは可変引数を取る

STAssert*マクロは可変引数で NSLog() の様に追加情報を表示することもできる。こんな感じ。
STAssertTrue([[obj greeting] isEqualToString:@"x"],
     @"*%d-%@*", 1, @"A");

なおメッセージに ":" を含めるとそれより前の文字が表示されない。
(例)@"failed: A" => A と表示される

また、STAssertTrue(..., @"%@", self) とすると実行中のメソッド名が表示される。

メンバ変数

メンバ変数を使うことも可能。
@interface SampleClassTest : SenTestCase {

 NSInteger counter_;
}

テストケース実行順はメソッド名昇順

テストメソッドを追加してみた。実行順序はメソッド名昇順のようだ。
- (void)testFirst
{
 counter_++;
 STAssertTrue(NO, @"counter=%d", counter_);
}

テストケース毎にインスタンスが生成される

それぞれのメソッドでアドレスを表示させてみると別の値が表示された。テストメソッド実行毎にインスタンスは新規作成されているのがわかる。

複数のテストケースクラス

これも名前順に実行された。

デバッグ表示できない

NSLog(), fputs("..", stderr) などでテストケースからの出力を見ることができない。

初期化と後始末

setUp/tearDown メソッドを書くとテストケース実行前後に処理を実行させることができる。
-(void) setUp
{
    // 初期化処理
    counter_ = 100;
      :
}

その他

実機ビルドではテストできない。
テストメソッドはヘッダファイルで宣言する必要は無い

備考:テストマクロの種類

SenTestCase.h より
#define STAssertNil(a1, description, ...)
#define STAssertNotNil(a1, description, ...)
#define STAssertTrue(expression, description, ...)
#define STAssertFalse(expression, description, ...)
#define STAssertEqualObjects(a1, a2, description, ...)
#define STAssertEquals(a1, a2, description, ...)
#define STAssertEqualsWithAccuracy(left, right, accuracy, description, ...)
#define STAssertThrows(expression, description, ...)
#define STAssertThrowsSpecific(expression, specificException, description, ...)
#define STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...)
#define STAssertNoThrow(expression, description, ...)
#define STAssertNoThrowSpecific(expression, specificException, description, ...)
#define STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...)
#define STFail(description, ...)
#define STAssertTrueNoThrow(expression, description, ...)
#define STAssertFalseNoThrow(expression, description, ...)


参考情報


OCUnit については [SM gakusyuu]; サイトが詳しい。Cocoaアプリから Static Library、テストケースのデバッグ、果てはコードカバレッジ計測まで網羅されていて非常に参考になった。

OCUnitの使い方(Cocoa Application編) - [SM gakusyuu];
OCUnitの使い方(Static Library編) - [SM gakusyuu];
OCUnitの使い方(デバッグ編) - [SM gakusyuu];
OCUnitの使い方(コードカバレッジ計測) - [SM gakusyuu];

公式リファレンス(iOS向け)
iOS Development Guide: Unit Testing Applications


ソースコード


GitHub からどうぞ。
StudyUnitTest at 2010-11-22 from xcatsan's iOS-Sample-Code - GitHub

SimpleCap - 途中経過

2010年11月21日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

ウィンドウとメニューキャプチャで背景を含めるモードを追加した。
こんなのとか

こんなの

これらは従来だと範囲選択キャプチャでしかできなかった。

SHE(Selection History Expose)とか。

最初はどうかと思ったが何気に重宝する。もう少し何かが足りない気もするが。

範囲サイズの直接入力。

その他、SimpleViewのアイコン見直し、アプリ自由登録など。

現在は範囲選択のプリセット、プリファレンス構成の見直しなどやっている。

人気の投稿(過去 30日間)