ARCでUIButtonをタップするとEXC_BAD_ACCESSとなる問題

 プログラミング  Comments Off on ARCでUIButtonをタップするとEXC_BAD_ACCESSとなる問題
Jul 292012
 

Objective-CでARCをONにした状態で、UIButtonを設置。ボタンをタップするとメソッドが呼び出されずEXC_BAD_ACCESSとなる場合の解決法。Xcodeの問題でもiOSの問題でもない。ARCが主流になると新たな問題も出て来る一例。

基本の設置コード

ボタンを画面に一つ貼付けるコード。タップするとログを出す。

UIViewControllerのインスタンスを生成し、viewControllerのviewをwindowに貼付ける。viewControllerではviewDidLoadの時点でUIButtonのインスタンスをviewに貼付ける。

FTAppDelegate.m

#import "FTButtonViewController.h"
@implementation FTAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    FTButtonViewController *c = [[FTButtonViewController alloc] init];
    [self.window addSubview:c.view];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

FTButtonViewController.m

- (void)tapped:(id)sender
{
    NSLog(@"thx tapped");
}
- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view.
    UIButton * button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button setFrame:CGRectMake(50, 100, 100, 20)];
    [button addTarget:self action:@selector(tapped:) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"hello" forState:UIControlStateNormal];
    [self.view addSubview:button];
}

このボタンをタップするとメソッドが呼び出されずEXC_BAD_ACCESSとなる。ARCでは、この例の場合AppDelegateでviewはaddSubViewされているためretainCountは増えているが、viewControllerをstrong(retain)参照するプロパティが存在しない上、代入されていない。既にメモリ解放されている。

解決法

viewControllerがメモリから解放されないようにstrong(retain)参照(強い参照)を作成しておく。

#import "FTButtonViewController.h"
@implementation FTAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    FTButtonViewController *c = [[FTButtonViewController alloc] init];
    [self.window addSubview:c.view];
    self.controller = c;//UIViewControllerをstrong(retain)参照するメンバ
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

参考サイト

http://stackoverflow.com/questions/9103705/uibutton-touches-up-ibaction-causing-exc-bad-access-with-arc

 Posted by at 15:43
Jul 292012
 

iPhoneの画面の様々な高さのポイント数を一覧できるシート(Cheatsheet)の紹介。ステータスバーを含まないビューの高さ。ナビゲーションバーの高さ。タブの高さ、ツールバーの高さ等。プログラムで取得する方法と定数のまとめPDFファイル。

iosのビューの高さ一覧

iPhone用の画面サイズの様々な高さが収録されています。UIKeyboard(キーボード)の高さ等。A4で印刷するとちょうどいいです。

ダウンロード(PDFファイル)は以下。

iOSの画面サイズ一覧図解PDF

コピー印刷改変は自由です。

 Posted by at 03:21
Jul 092012
 

初期化に使われるイニシャライザー(initializer)のinitWithCoderとinitWithNibNameの違い。initからloadViewまでUIViewControllerの初期化から表示されるまでのそれぞれのメソッドの動作まとめ。nibを使う場合と使わない場合の違いを図解で解説。

iOS初期化メソッド(イニシャライザー)の図解

initWithNibName: bundle:

  • UIViewControllerのためのイニシャライザーである。
  • コードでUIViewControllerを作成する時に呼び出されるイニシャライザーである。
  • nibをロードできるようUIViewControllerを設定するイニシャライザーである。
このメソッドが呼ばれた時点では…
  • outletやactionは設定されていない状態
viewにアクセスするとnibをロードし、outletやactionの設定が開始される。

initWithCoder:

  • アーカイブオブジェクトのためのイニシャライザーである。
  • nibからオブジェクトをロードする時に呼び出されるイニシャライザーである。
  • nibの中に保存されたオブジェクトはアーカイブオブジェクトである。

このメソッドが呼ばれた時点では…

  • nibからオブジェクトが取り出されている(unarchive/deserialize)過程にある
  • outletやactionは設定されていない状態
  • UIViewControllerのcontextの中で、nibからUIViewControllerが生成される

initやユーザ定義の(initWith)イニシャライザー

  • nibを使わずコードのみでUIViewControllerを生成する場合に使う。

その他viewのライフサイクルで登場するメソッド

loadView

  • nibを使わない場合はオーバーライドする。
  • viewプロパティに指定するオブジェクトをここで生成する。
  • nibを使う場合はこのメソッドを利用しない。
- (void)loadView
{
    MyUIView *rootView = [[MyUIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.view = rootView;
}

viewDidLoad

  • viewが設定されている。
  • outletやactionの接続が設定されている。
  • nibを使う場合はnibが読み込まれた後に呼び出される。
  • nibを使わない場合はここでsubviewの設定をする。
  • (メモリの問題で)viewが解放されている場合、再度呼ばれる。

viewWillAppear

  • 画面の表示される直前に呼ばれる。
  • モーダル等のアニメが始まる前である。
  • (入れ替えの場合)他のUIViewControllerのviewWillDisappearが実行された後である。

viewDidAppear

  • 画面の表示が行われた直後に呼ばれる。
  • モーダル等のアニメが終わった後である。
  • 実際のframeサイズとなる。
 Posted by at 04:48

UIView の drawRect と setNeedsDisplay の関係

 プログラミング  Comments Off on UIView の drawRect と setNeedsDisplay の関係
Jul 092012
 

Objective-CでUIViewを使う場合、drawRect:とsetNeedsDisplay等の描画に関係するメソッドがある。関係のまとめ。手始めにUIViewControllerのライフサイクル、init初期化メソッド(initializer)からloadViewへと順に考えていく。

ライフサイクルの流れ(表示まで)

  1. init(initWith)
  2. loadView
  3. viewDidLoad
  4. viewWillAppear
  5. viewDidAppear
各メソッドの詳細のまとめ記事(http://blog.f60k.com/initwithcoder_…hnibnamebundle/)

drawRect

システムにおいて、ビジュアルの画面描画は負荷の高い処理となる。再描画を最小限にすることが表示を高速化するコツとなる。

いつどのようにdrawRectは呼ばれるか?

通常、UIViewはCALayerのdelegateであり、アニメーションなど表示更新のタイミングでdelegateメソッドを呼び出し、UIViewに実装されているdelegateメソッドdrawLayer:inContext:メソッドが呼び出される。drawLayer:inContext:メソッドは、デフォルトでdrawRectを呼び出す。つまり、ユーザ定義でサブクラス内でdrawLayer:inContext:メソッドを実装していない場合、単純にdrawRectが呼ばれる。

layer階層やsubviewで分割

drawRectメソッドは最も一般的な描画メソッドで描画のカスタマイズに使われる。描画範囲を最小にするためにはlayer階層やsubviewで分割し、細かい単位で行うのが良いとされている。drawRectはViewというのが最小単位となるためview全体のredrawになるとされているためあまりよい方法ではない、一般的(大衆的)な方法ですらある。なおlayer単位で描画する場合にdrawLayer:inContext:に記述する。

全体のビュー更新を避ける

setNeedsDisplayでもsetNeedsDisplayInRectでも全体のビュー更新を行うようにマークされる。例えば、画像(UIImage)をアニメーションさせたり移動や回転などさせる場合、画像用のviewやlayerを作成して行うのが良い。これによりiOSがscreenを更新する時点で、viewのdrawRectで呼ばずに済み親ビュー全体の更新を避けることができ、つまりCGContextDrawImageや-drawInRectも呼ばずに済む。

drawRect

  • このメソッドはビューの描画を更新する。
  • UIViewを一つの要素と扱う。
  • 一つの要素の単位で再描画される。
  • 描画のキャッシュ(pixels)がされる。
  • 更新が必要な場合に呼び出される。

setNeedsDisplay/setNeedsDisplayInRect

  • drawRectを呼び出す予約をする。

display

  • 即座にdrawRectを呼び出す。

 

遅い場合

UIViewの中から、つまり[self setneedsdisplay]になっているとselfに対応したUIView全体に更新(drawrect:)が呼び出される。親ビューの上にsubviewが乗っているケースでは、subviewの背景が透過でなければ親は見えない。この重なった状態でselfとsubviewにsetneedsdisplayを送るとちらつくので注意。

  • opaqueプロパティを見直す(半透明合成は重いのはパソコン全般昔からの通説)
  • layerに細分化する(描画範囲を狭める)
  • subviewにして更新はsubviewに対してのみsetneedsdisplayする(描画範囲を狭める)

 

参考サイト https://developer.apple.com/library/ios/#qa/qa1708/_index.html#//apple_ref/doc/uid/DTS40010245 http://developer.apple.com/library/ios/#DOCUMENTATION/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW10

 Posted by at 04:31
May 022012
 

XcodeでArchive(アーカイブ)を実行したとき、iOSとして書き出したいのだが、Generic Xcode Archiveになってしまう場合のトラブルシューティング。XcodeでArchiveしてAd-Hocアプリとして保存する場合、OrganizerのArchivesでTypeがGeneric Xcode ArchiveであればAd-Hocで保存する3つの選択肢が現れないので不可能。iOS App ArchiveになっていればiOSアプリとして書き出せる。

この問題はStatic Libraryを使っている場合に起こる。

Skip Installを設定

アプリ本体とライブラリ(Static Library)を1個のアプリとしてアーカイブする場合、

  • ライブラリをコンパイルしてaファイルが作成されて1つバイナリ
  • アプリ本体をコンパイルして1つバイナリ

2つ作成される。2つのバイナリが含まれた状態ではGeneric Xcode Archiveになる。

Debug時はアプリ本体バイナリからaファイルを参照して別のバイナリの中を利用(依存)してる形となる。Skip InstallをYESに設定するとStaticLibraryのバイナリは本体のバイナリの一部となり、iOS App Archiveでアーカイブされる。うまくいかない場合は、Build>Cleanをしてみる。

以下はKal(StaticLibraryのProject設定)

参考サイト

http://developer.apple.com/library/mac/#documentation/ToolsLanguages/Conceptual/Xcode4UserGuide/DistApps/DistApps.html

http://flakasoft.com/developer-tips/xcode-4-bug-solution-for-archiving-build-with-static-library/

 Posted by at 01:30

NSArrayから重複しないで取り出す

 プログラミング  Comments Off on NSArrayから重複しないで取り出す
Apr 082012
 

NSArrayから重複の無いように要素をNSDictionary型で取り出す方法のまとめ。これには数ステップ必要。まずNSArrayから重複を除いたNSArrayを作成する。このNSArrayに含まれるキーからindexを調べる。indexからNSDictionaryに到達する流れ。

前提として次のような配列があるとする。ここからcodeの重複が無いように配列を作成するには次のようにする。

NSDictionary *city1=[NSDictionary dictionaryWithObjectsAndKeys:@"Sapporo",@"name",@"1",@"index",@"1",@"code", nil];
NSDictionary *city2=[NSDictionary dictionaryWithObjectsAndKeys:@"Saitama",@"name",@"2",@"index",@"2",@"code", nil];
NSDictionary *city3=[NSDictionary dictionaryWithObjectsAndKeys:@"Tokyo",@"name",@"3",@"index",@"2",@"code", nil];
NSDictionary *city4=[NSDictionary dictionaryWithObjectsAndKeys:@"Nagoya",@"name",@"4",@"index",@"3",@"code", nil];
NSArray *arr = [NSArray arrayWithObjects:city1,city2,city3,city4, nil];

NSArrayから重複を除いたNSArrayを作成

重複を除いた配列を作る方法は2つ。keyPathを使うと配列の要素から合計を特殊なキー(@sum,@avg等)を指定して色々操作可能。@distinctUnionOfObjectsを使うと、その直後に指定したキー(.code)の重複が無いように取り出した値を配列にして返す。上の場合、「1,2,2,3」は重複が省かれ「1,2,3」の3つの要素が抽出されて配列として出力される。この配列にはNSDictionary型のデータ(city)は入らず、キーの文字列(code)の配列となる点に注意。さらにソートされないので注意が必要。

NSArray *codes = [arr valueForKeyPath:@"@distinctUnionOfObjects.code"];

NSArrayに含まれるキーからindexを調べる

最初のキーの参照するcodeの入るindexを探す。

NSString code = [codes objectAtIndex:0];
NSInteger index = [codes indexOfObject:code];

indexからNSDictionaryを取り出す

indexの数値が参照するNSDictionary(city)を取り出す。

NSDictionary *dic = [arr objectAtIndex:index];
 Posted by at 21:25

テンプレートを使わないCocos2D

 プログラミング  Comments Off on テンプレートを使わないCocos2D
Mar 082012
 

Objective-CでCocos2Dを利用したい場合に、テンプレートを使わないでプロジェクトを作成するための設定方法のまとめ。アプリの一部のビュー(画面遷移の中の一つ)に部分的にCocos2D(OpenGL)を使いたいなど限られたニーズの場合などテンプレートを使わないでフルスクラッチ?でプロジェクトを作成することも出来る。

1.新規Project作成

Xcodeで用意されているプロジェクト(SingleViewなど)からスタートする。

 

2.ライブラリ追加

http://www.cocos2d-iphone.org/downloadからCocos2Dをダウンロードして解凍。

Finderでcocos2dフォルダとexternalフォルダを確認した後、cocos2dフォルダをプロジェクトにドラッグアンドドロップで追加。(音の再生もしたい場合はCocosDenshionも追加)

FontLabelとTouchJSONも同様に追加。

 

3.フレームワークの追加(Link Binary With Libraries)

TargetsのBuild Phasesからframeworkの追加をする。(OpenAL/AVFoundationはCocos2Dで音の再生が必要な場合のみ)

4.リンカの設定(Other Linker Flags)

ProjectのBuild SettingsからOther Linker Flagsに-lzを追加。リンカエラー起こしたらココを確認。

 

5.AppDelegate

CCDirectorの起動スクリプトはテンプレートと同じ。コピペすればOK。

 

 Posted by at 02:13

XCodeのBreakpointの使い方

 プログラミング  Comments Off on XCodeのBreakpointの使い方
Feb 182012
 

XCodeでNSLogの代わりにブレークポイント(Breakpoints)を使う時の簡単なまとめ。Log messageを出す時には@exp@をどう使うか。Debugger Commandではgdbのコマンドを利用する。

コンソール出力

ブレークポイントのヒット回数を利用する

ブレークポイント設定をしたプログラムの書かれた行が何回呼び出されたか出力する。(例えば、以下ではTableCellの生成が何回呼ばれたかを調べている)

※OptionsのAutomatically continue after evaluating actionのチェックをONにするとブレークポイントに差し掛かっても出力をこなしながら、そのまま続行させることができる。

@exp@を利用する

以下ではTableCellに利用する文字列の中身を調べている。戻り値には型を付ける事が必要。

@(NSpoint)[obj location]@

@(bool)[str isEqualToString:@”xcode”]@

@(const char*)[obj title]@

@(float)[num floatValue]@

gdbコマンドの一部

ブレークポイントを設定するコマンド(break)

main.mの10行目で止めたい。

break main.m:10

DataManagerのinitWithTitle:メソッドで止めたい

break -[DataManager initWithTitle:]

変数が変化するとプログラムが停止するコマンド(watch)

1. title変数が変化したら止めたい

watch title

2. objのprop変数をデバッガ出力表示したい

p [obj prop];

3. selfのtimestampオブジェクトをデバッガ出力表示したい(オブジェクトである点に注意)

po [self timestamp]

条件

「一行目だったら、画面が開いていたら、アニメーションしてたら等」変数の条件にあわせたブレークポイントの設定方法。慣れると便利なブレークポイントでは、ドットシンタックスは使えない点、戻り値の型を明示的に与える点は注意が必要。

indexPath.row==50ではなく、(NSUInteger)[indexPath row]==50

以下のようにNSLogでソースを汚す必要も無い。

if(indexPath.row==50){
NSLog(@"%.2f, %.2f", point.x, point.y);
}

参考サイト

http://theocacao.com/downloads/DebuggingWithXcode.pdf

http://stackoverflow.com/questions/4797128/xcode-debugger-value-of-expression

http://cocoawithlove.com/2008/10/debugging-tips-for-objective-c.html

 Posted by at 02:39

iOSのマルチタスクバーは最近使用したアプリの一覧

 プログラミング  Comments Off on iOSのマルチタスクバーは最近使用したアプリの一覧
Feb 022012
 

マルチタスクバー(ホームボタンを2回押すと出る一覧)に表示されるのは最近使用したアプリの一覧である。マルチタスクバーにアプリが残っている時は消さないと「裏で起動(動作)したままになっている」という考え方は間違い。Appleサポートの記述を理解していない又は誤解しているジーニアスバーのスタッフもいるとのこと。

「iOSのマルチタスクバーに表示されているのは最近利用されたアプリの一覧であり、現在稼働中のアプリ一覧ではない(稼働しているものもあるが稼働していないものもある)」

iOS開発のデバッグ専門の業者のレポートですらiOSのマルチタスクを誤解していることがあった。マルチタスキングを使用しない設定(Application does not run in background(UIApplicationExitsOnSuspend=YES))をしたiOSアプリがマルチタスクバー(最近使用したアプリ一覧)に残るのはごく当然のことと言える。

マルチタスクバーという言い回しも情報源により曖昧で、

Appleサポートの「マルチタスク機能を理解する」では、「マルチタスクバー」ではなく「最近使用したApp」と表現している。

iPhone電源投入直後に何十個というアプリがマルチタスクバーに表示されるのは(最近使用したアプリ一覧であることから)当然で、これらが稼働中とは限らないアプリの一覧である。

UIApplicationExitsOnSuspend

アプリ起動中の状態遷移は次のようになる。

ホームボタンを押した時の状態遷移(UIApplicationExitsOnSuspend=NO)

Active > Inactive > Background > Suspended

ホームボタンを押した時の状態遷移(UIApplicationExitsOnSuspend=YES)

Active > Inactive > Background > Suspended > Not running

画像はAppleからの引用(© 2012 Apple Inc. All Rights Reserved.)

以下参考サイト

http://www.apptoiphone.com/2012/01/ios.html

http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html

 Posted by at 14:20
Jan 292012
 

Xcode4でCoreDataを使ってUnitTests(Logic)を行う場合の設定方法。新規プロジェクト生成でUnitTestsをする設定にチェックを入れた場合のDefaultはUnitTests(Application)となる。つまり、UnitTests(Logic)は開発者が新規設定が必要となる。

UnitTestsのタイプ

  • Application Test(UIViewなどを含めた全体テスト)
  • Logic Test(UI以外のデータのテスト)
アプリケーション(以下アプリ本体)のidentifierは(com.f60k.Hergo)という仮想アプリで説明する。

Logic Testの作成Howto

Testing Bundleを新規作成。Add Targetボタンを押すとダイアログが出現する。名前はアプリ本体の名前にLogicTestsを追記したものにした。指定したProductName(com.f60k.HergoLogicTests)は後で使うので覚えておく。追加されるとフォルダが作成される。

CoreDataの3つの重要ポイント(Logic Testの場合)

  • xcdatamodelはコンパイルに含める(モデルの設定)
  • NSBundleはidentifierから設定(モデルの設定)
  • NSStoreTypeはInMemoryに設定(永続ストアの設定)
xcdatamodelはコンパイルに含める
NSManagedObjectModelを作成するときには(.mom)ファイルが必要。コンパイルに含めた(.xcdatamodelファイル)から生成される。アプリ本体では自動的に追加されるが、Logic Testでは開発者が追加しなければならない。
NSBundleはidentifierから設定
開発マシンにはどこかに以下のように.momファイルが保存されている。これが実際のbundle(com.f60k.HergoLogicTests)の中身である。正しく生成され保存されているのがわかる。
bundleが存在するのは分かった。このbundleの位置(path)からmodelを取り出す。[NSBundle mainBundle]の指す位置はLogic Testでは取得できない。
mainBundleから取り出せたbundleのidentifierは
  • アプリ本体→com.f60k.Hergo
  • Application Test→com.f60k.Hergo
  • Logic Test→null(com.f60k.HergoLogicTestsとなれば問題ないが…)
Logic Testで正しく取り出すには以下のように開発者がハードコードで指定して取得する。(ハードコードの問題点は後半。)
[NSBundle bundleWithIdentifier:@"com.f60k.HergoLogicTests"];
 これによりidentifierが正しく特定され、bundleの保存されている場所(path)を探し出すことが出来る。
NSStoreTypeはInMemoryに設定
  • NSSQLiteStoreType
  • NSInMemoryStoreType

その他重要ポイント(Testに関係なく一般的に行うこと)

  • CoreDataのフレームワークをビルド設定に追加する
  • prefixヘッダにCoreDataを追加する
CoreDataのフレームワークをビルド設定に追加する
prefixヘッダにCoreDataを追加する
LogicTestsのSupporting Filesのpchファイルの中にCoreData.hを追記する。
#import <CoreData/CoreData.h>

Application TestではApplicationにターゲット依存しているため、通常のアプリ開発と同じ手順で問題ない。

ハードコードの問題点

Logic Testの設定をハードコードすると書き換えでミスをする可能性がある。自動で設定が切り替わるようにしておくと便利である。

自動切り替え設定のポイント

  • スキーマに環境変数を設定する
  • 環境変数の値を設定値の分岐に利用する
スキーマに環境変数を設定する
環境変数の値を設定値の分岐に利用する
getenv()を使って設定した変数を取り出せば、通常はSQLでLogic TestではInMemoryと使い分けが出来る。
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Hergo.sqlite"];
NSString *storeType = NSSQLiteStoreType;
#if DEBUG
if (getenv("logicTests"))
{
storeType = NSInMemoryStoreType;
storeURL = nil;
}
#endif
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:storeType configuration:nil URL:storeURL options:nil error:&error])
{

参考サイト
http://stackoverflow.com/questions/7274711/run-logic-tests-in-xcode-4-without-launching-the-simulator
http://iamleeg.blogspot.com/2010/01/unit-testing-core-data-driven-apps-fit.html

 Posted by at 22:51