NSFetchedResultsControllerDelegateのセル削除ではまった

題のとおり、NSFetchedResultsControllerDelegateでのdeleteで1h程はまりました。
40秒位で読める程度の中身のなさです。

【結論】
didChangeObjectの引数indexPathとnewIndexPathを書き間違えていた事。
単純かつエラーにならないので気づきにくかった。


【エラー内容】

CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification


【で】
サンプルと何も変わらないけれど一応コードです。
case文、NSFetchedResultsChangeDeleteの時にindexPathではなくnewIndexPathを入れてました。
newIndexPathは削除処理の時はnilになるので、削除できず落ちます。

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;
    
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
            
        case NSFetchedResultsChangeDelete:
            // 下の行のindexPathをnewIndexPathに間違えていた。delete時はnilがくるので落ちる。
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView reloadData];
            break;
            
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
            
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}



あまりに単純ミスなので同じ事する人はいないかもですが一応書きました。
解決のヒントはMaster-Detail-Applicationのデフォルトの処理でindexPathがnilじゃないじゃん!
ってところからでした。


その他の良くありそうなエラー
NSFetchedResultsControllerの検索条件が違う時にキャッシュが同名の時
NSFetchedResultsController performFetchでクラッシュ

ボタンファクトリーを作ってみる(create button factory with objectivec)

こんばんわー、色々なボタンを作るとコードが汚くなるのでボタンファクトリー(factoryClass)を作ってみます。
デザインパターンに精通している訳ではないので、玄人の方は是非アドバイスを下さい。
【 参考 】
ファクトリメソッド(wiki)
iPhoneのUIのスキンをカスタマイズする場合のメモ


ファクトリを用意すると何がよいのか?
・ボタンのUI調整コードをファクトリ内に追いやれる。
・ファクトリー内でボタンを変更するだけで一括で変更が可能。
・少しViewControllerに書くコードが減る。(ただでさえ処理が集まりやすいので)
・別プロジェクトに再利用しやすい。


作るときに既存のUIButtonクラスの構成を参考にボタンタイプ等をEnumで用意しました。
Enumなら変な数値が入るとエラーになるので安心して利用できます。
・プロジェクトテンプレート:SingleViewApplication
・NSObjectのサブクラスでButtonFactoryという名前でクラスを追加


以下ButtonFactoryクラスに記述していきます。
SkinnedButtonFactory.hファイル

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSInteger, SkinnedButtonType) {
    SkinnedButtonTypeDefault = 0,          // default
    SkinnedButtonTypeSmallImage            // imageDefault
};
@interface SkinnedButtonFactory : NSObject

+ (UIButton *)createButtonWithType:(SkinnedButtonType)buttonType
                            target:(id)target
                          selector:(NSString *)selName
                         imageName:(NSString *)imageName;
@end



SkinnedButtonFactory.mファイル(ガーって書いてありますがただボタン作ってreturnしてるだけです。)

#import "SkinnedButtonFactory.h"

@implementation SkinnedButtonFactory

+ (UIButton *)createButtonWithType:(SkinnedButtonType)buttonType
                            target:(id)target
                          selector:(NSString *)selName
                         imageName:(NSString *)imageName
{
    if (buttonType == SkinnedButtonTypeDefault) {
        
        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [button setFrame:CGRectMake(0, 0, 44, 44)];
        button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
        button.contentMode = UIViewContentModeScaleToFill;
        [button setTitle:@"" forState:UIControlStateNormal];
        [button addTarget:target action:NSSelectorFromString(selName)
         forControlEvents:UIControlEventTouchUpInside];
        return button;
    }
    if (buttonType == SkinnedButtonTypeSmallImage) {
        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
        button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
        button.contentMode = UIViewContentModeScaleToFill;
        [button addTarget:target action:NSSelectorFromString(selName)
         forControlEvents:UIControlEventTouchUpInside];
        UIImage *img = [UIImage imageNamed:imageName];
        [button setImage:img forState:UIControlStateNormal];
        return button;
    }
    return nil;
}
@end



ファクトリを使う側(はじめから用意されているViewController)で利用します。
例が微妙ですが、ENUMのSkinnedButtonTypeで違う値を渡すだけで簡単に生成するボタンを切り替える事ができます。
SkinnedButtonTypeSmallImageを渡してimage名を渡せばimageButtonを作成できます。

#import "MyAppViewController.h"
#import "SkinnedButtonFactory.h"

@interface MyAppViewController ()

@end

@implementation MyAppViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    UIButton *btn = [SkinnedButtonFactory createButtonWithType:SkinnedButtonTypeDefault
                                                        target:self
                                                      selector:@"pushButton"
                                                     imageName:nil];
    [self.view addSubview:btn];
}

- (void)pushButton{
    NSLog(@"ボタン押されたよ");
}



もっと色々凝ってみたいんですけど、知識不足かつ
費用対効果のバランスがなかなかわかりません。

Objective-Cのブロック(Block)が苦手だけど少し覚えた過程


世間様はiPhone5でにぎわってますねー。
まだまだ勉強中の身なんですが、Objective-Cのブロック(Block)についてテクニック的な話ではなく、
自分がブロックを学ぶときどうしたか?というプロセスってほど大げさでもない手順を書きます。
[参考]
Apple公式ブロックプログラミングトピック
https://developer.apple.com/jp/devcenter/ios/library/documentation/Blocks.pdf

Objective-Cの基礎(Blocks)
http://eikatou.net/blog/2012/07/objective-c_kiso_blocks/



ブロックって書き方がなんか苦手で、敬遠してました。
僕と同じで読み辛いなぁと思ってる人は結構いると思ってます。
そんな中、なぜブロックを改めてやるかというと、


公式がデリゲートよりブロックでしょという流れになっている


僕の中でこれが一番大きいです。ちょっと使いたいサンプルをみてもブロックだし、
iOS5位から今までデリゲートだった所もブロックに変わっちゃったし、、
大体ブロックがかんできて、読めないとそこで飛ばしちゃってたことが多かったです。
デリゲートに頼りすぎてました、、このままでは置いていかれると


逆に基本さえ押さえれば、幅が広がるなぁと思います。
なので、僕がブロックを学んだ時に(まだ終わってないけど)意識した事を書きます。


まだちゃんと理解しきっていないので、あれなんですけど、
人の勉強の仕方って結構僕は参考になるので書きます。
意識したのは以下2つです。


1. 基本形と触れ合う時間を大切にする。
2. 詰め込みすぎない。


まず1.について、
Apple公式からまんま持ってくると、サンプルのブロックって構文はこんなのです。
「 int (^myBlock)(int) = ^(int num) { return num * multiplier; }; 」
まずはこれを覚えないと始まらなかったです。
※そのまま公式PDFや玄人が使うブロックの記事を見て頭がかなり混乱しました。


まずはiPhoneのメモでちょこっとかけるようにユーザー辞書にいくつか単語を登録しました。
(@sazameki氏のtweetからいいなとおもって)
「はっと」を「^」
「せみ」を「;」
「あすた」を「*」
まずは左半分の宣言int (^myBlock)(int) から慣れようと思い普通の変数との違いを考えました。
普通の変数は以下のように宣言しますが、

int num;
型 変数名



ブロックはこうなります。

int (^myBlock)(int);
戻り値型(^ブロック名)(引数型)

変数宣言に引数がついただけ
位にとらえると分かりやすかったです。
これだけなら短いので隙間時間に1回書いたり、少し思い出したりして覚えられると思います。
これがすらすら書ければ、ブロック名の省略や可変引数とか無名ブロックとかも、
あーAがないだけねー、Bがないだけねーとかで差分で覚えられます。


2.の詰め込み過ぎないについては、
Appleの公式ドキュメントをずっとみてると、言い回しが難しくて試さないとテンパってきます。
なので、情報を小分けにして1つ1つ動作を確認して学びましたという事です。
実際- (void)presentViewController:.....なんかを使ってみたり、
「Command」+ 「クリック」で定義に飛んだりしながら、
フレームワークメソッドの引数に確かになってるなぁって
自分が思い出せる範囲で1言2言ずつゆっくりポイントを覚えるようにしました。


自分の感じたメリットはやはりドキュメント通りで、
・メソッドの引数に使えるので、そこでしかしない処理がカプセル化できるよー
・コールバックでメソッド増えないし無駄にオブジェクトも増えないよー
・アニメーション→完了時に通知みたいなのを同じ文脈で書けるので可読性あがる(当たり前に読み書きできれば)
という感じです。


とりあえず慣れるとかなり書きやすいのは確かです。
メモリ管理とか懸念はつきないですけど、

Xcode5でLLDBデバッガコマンドを使ってみる

※キーウインドウ上の曖昧な制約を表示する、[UIWindow keyWindow] _autolayoutTrace]を追記
Xcodeブレークポイントでプログラムを停止している時に入力できるコマンドです。



VisualStudioのイミディエイトウインドウライクに変数の値を変えられます。
値を変更して何度もテストしたい時に便利そうです。



・参考資料
WWDC 2012 Session VideosのDebugging with LLDB (https://developer.apple.com/videos/wwdc/2012/)※英語
GDBとLLDBのコマンド対応表 (http://lldb.llvm.org/lldb-gdb.html) ※英語,公式
チュートリアル等 (http://lldb.llvm.org/tutorial.html) ※英語,公式
変数のフォーマット (http://lldb.llvm.org/varformats.html) ※英語,公式
LLDBを使ってCUIで動作させているテストケースをデバッグする。(http://akisute.com/2012/07/lldb-cui.html)


簡単なコマンド一覧表

省略形 内容 正式名
h ヘルプの表示 help
s ステップイン thread step-in
c 続きを実行(次のbreakPointまで) process continue
po 式の評価(poはオブジェクト、pは基本型も可) expression -o --
br l breakPointを一覧表示 breakpoint list
bt 現スレッドのバックトレース情報を表示 thread backtrace
b objc_msgSend objc_msgSend関数にシンボリックブレークポイントを設定 -


[使用例]

・変数cellCountの値を変更する。
p cellCount = 6


・self.view.subViewsのカウントを一時変数fooに代入
p NSUInteger $foo = (NSUInteger)[[self.view subviews] count]

・変数fooの値を表示(基本型に利用可、オブジェクトはアドレスの表示になる。)
p $foo
(int) $foo = 6


・self.viewの値を表示する。(オブジェクトのみ)
コマンド po self.view
実行結果例↓
(UIView *) $1 = 0x0e3d5c00 ; contentOffset: {0, 0}>


・self.viewの階層を再帰的に表示
コマンド po [self.view recursiveDescription]
実行結果例↓
; layer = ; contentOffset: {0, 0}>
| >
| >
| >


・カレントスレッドのバックトレースを3つまで表示
コマンド bt -c 3
実行結果例↓
thread #1: tid = 0x1f03, 0x00002ab5 Test02`-[AppDelegate application:didFinishLaunchingWithOptions:] + 85 at AppDelegate.m:16, stop reason = breakpoint 5.1
frame #0: 0x00002ab5 Test02`-[AppDelegate application:didFinishLaunchingWithOptions:] + 85 at AppDelegate.m:16
frame #1: 0x000107b7 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 266
frame #2: 0x00010da7 UIKit`-[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1248


・キーウインドウ上の全ての曖昧な制約をプリントする
po [[UIWindow keyWindow] _autolayoutTrace]
実行例↓
- AMBIGUOUS LAYOUT

省略

- AMBIGUOUS LAYOUT
- AMBIGUOUS LAYOUT


・指定したViewのHorizontal(0)/Vertical(1)制約を表示
po [0xb9da840 constraintsAffectingLayoutForAxis:0]
実行例↓
<__NSArrayM 0xb8014f0>(
,
,
,

)

2つのファイルの差分を比較したりマージしたりする。

WInMargeみたく比較をしたいと思って、TextWranglerを使ったらできたのでメモ


メニューのSearch > Find Differencesを選択。


で、比較したい2つのファイルを「New」と「Old」にD&Dするか普通に選択します。

「compare」をすると差分が一覧で表示されます。
あとは「Apply →」で左の内容を右に、
「← Apply」で右の内容を左にマージできます。

NSRangeの使い方

NSRangeとは?

NSRangeはlocation(位置)とlength(長さ)の2つを持つ、"範囲"を表す構造体です。
文字列の検索、分割、切り出し等に重宝します。
※2013/06/11 正規表現検索のサンプルを追加


例えば以下のようにrangeOfStringメソッドを使うと、
"列です"という文字列が最初に現れた「位置」と「長さ」をNSRangeという「範囲」で取得することができます。

NSString *text = @"テスト用の文字列です 2012/8/13";
NSRange rangeTest = [text rangeOfString:@"列です"];

取得できる範囲のイメージは以下です。



検索に失敗した時

検索した文字列が見つからない時は、NSNotFoundという値がlocationに返却されるので、
if(range.location == NSNotFound)のように条件に使えます。


パフォーマンスについて

以下メソッドでoptionにNSLiteralSearchを指定する事で、
検索のパフォーマンスが大分変わるようです。

rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask



オプションはOR演算で組み合わせ可能です。

NSStringCompareOptions options = NSLiteralSearch | NSCaseInsensitiveSearch;

尚、NSLiteralSearch指定のみなら早くなりますが、合成文字を無視する事、
組み合わせによりむしろ遅くなる事があるので注意が必要です。
NSRegularExpressionSearchと組み合わせたら3倍程時間かかってました。
参考:http://blog.fenrir-inc.com/jp/2013/04/stringfind-options.html


以下はサンプルコードです。ご参考までに。

        // NSRangeのテスト用の文字列を準備
        NSString *text = @"テスト用の文字列です 2012/8/13";
        // 画像と同様のNSRangeのテスト
        NSRange rangeTest = [text rangeOfString:@"列です"];
        NSLog(@"rangeTestのrangeは%@",NSStringFromRange(rangeTest));
        
        // 「12」という文字列が最初に現れる場所を検索し、位置と長さを持つNSRange構造体を取得する。
        NSRange range1 = [text rangeOfString:@"12"];
        NSLog(@"range1のlocationは%lu",range1.location);        
        
        // 検索した文字列が存在しない時、range.locationにはlongの最大値が返却される。
        NSRange range2 = [text rangeOfString:@"9"];
        NSLog(@"range2のlocationは%lu",range2.location);        
        
        // 検索対象が空文字だった場合も、range.locationにはlongの最大値が返却される。
        NSString *emptyText = @"";
        NSRange rangeEmpty = [emptyText rangeOfString:@"aaa"];
        NSLog(@"rangeEmptyのlocationは%lu",rangeEmpty.location);
        
        // 検索対象がnilだった場合は、0が返却されます。
        // Objective-Cではnilへのメッセージは無視されますが、
        // 戻り値があるメソッドは0が返却されます。
        NSString *nilText = nil;
        NSRange rangeNil = [nilText rangeOfString:@"aaa"];
        NSLog(@"rangeNilのlocationは%lu",rangeNil.location);        
        
        // [実行結果]
        // rangeTestのrangeは{7, 3}
        // range1のlocationは13                
        // range2のlocationは9223372036854775807        
        // rangeEmptyのlocationは9223372036854775807
        // rangeNilのlocationは0
        
        // 検索対象が空文字だった時、検索した文字列が存在しない場合に返却される値は、
        // long型の最大値LONG_MAXを定数化したNSIntegerMaxをenumで再定義したNSNotFoundなので、
        // 検索対象が見つからなかったらxxするという条件に、以下のようにNSNotFoundを利用することができる。
        if (range2.location == NSNotFound) {
            NSLog(@"rangeOfStringで検索対象が見つからなかった時の処理。");
        }
        // rangeを作りたい場合はNSMakeRangeを使う、例えば先頭から3文字の切り出し
        NSRange rangeZeroTo3 = NSMakeRange(0, 3);
        NSString *fst3Str = [text substringWithRange:rangeZeroTo3];
        NSLog(@"%@",fst3Str);
        // [実行結果]
        // テスト

        // 正規表現による検索、以下文字列から「2013年」を探す。
        // 変数を正規表現に含めたい時はstringWithFormatで%@を使う。
        NSString *searchTarget = @"先日行われた2013年のWWDCはiOS7の発表がありました。";
        NSString *suffixJapaneaseYear = @"年";
        NSString *regexStr = [NSString stringWithFormat:@"[0-9]{1,4}%@",suffixJapaneaseYear];
        NSRange  resultRange = [searchTarget rangeOfString:regexStr options:NSRegularExpressionSearch];
        NSLog(@"検索結果は%@です。",[searchTarget substringWithRange:resultRange]);
        // [実行結果]
        // 検索結果は2013年です。

        // 100万回の検索でNSLiteralSearchを指定
        NSLog(@"start");
        NSString *ret;
        NSString *searchTarget2 = @"先日行われた2013年のWWDCはiOS7の発表がありました。";
        NSString *japaneaseYear = @"年";
        for (int i = 0; i < 10000000; i++) {
            // 以下コメントを切り替えて比較
            NSRange  resultRange = [searchTarget2 rangeOfString:japaneaseYear];
            //NSRange  resultRange = [searchTarget rangeOfString:japaneaseYear options:NSLiteralSearch];
            ret = [searchTarget2 substringWithRange:resultRange];
        }
        NSLog(@"end 検索結果は%@です。",ret);



[参考サイト]
・文字列からある文字列が含まれる全てのNSRangeを取得する。(stackoverflow)
http://stackoverflow.com/questions/8533691/how-to-get-all-nsrange-of-a-particular-character-in-a-nsstring

・任意の文字が現れる場所を確認する
http://program.station.ez-net.jp/special/handbook/objective-c/nsstring/range-of-string.asp

CotEditorで動くスクリプトを作るには?

夏休みなのでCotEditorで動作するスクリプトを自前で作りたいなと思い立ちました。
以下を参考にやってみましたが、意外に結構迷いました。
・CotEditorのスクリプトメニューでPHPを起動する方法(便利なサンプル3つ付き)
http://creazy.net/2009/08/coteditor_scriptmenu_php.html



迷ったあげく普通にCotEditorのヘルプに記載があることに気がつきました。
「CotEditorXInput」で検索してみると、ドンピシャなメニューが、、
Unixスクリプトとの連携」ここにほとんど載っています。
これで作れそうです。



以下は一応の大事っぽい情報。


・作ったperl等のスクリプトを置く場所。

~/Library/Application Support/CotEditor/ScriptMenu/ の"ScriptMenu" フォルダ


・ショートカット等の設定

例) ファイル名.@d.plの場合
ショートカットキーは「Command + d」になります。
尚、ScriptMenuと同じ場所にある、_aboutScriptFolder-jp.rtfに詳しい作成方法が記載されています。


・実行権限の付与
ファイルに実行権限を与える必要があります。

chmod 755 ファイル名



・隠しファイルの表示

コマンドを使わない人は事前に隠しファイルを表示しておいた方がよいですね。
mac os lion で隠しファイルを表示する方法



以下形式 ↓ の現在日時を入れるスクリプトのサンプル
「2012/08/16 23:01:39」

#!/usr/bin/perl
use strict;
use warnings;

# %%%{CotEditorXInput=None}%%%
# %%%{CotEditorXOutput= InsertAfterSelection}%%%
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)  =  localtime(time);;

my $fmt_yyyyMMddhhmmss = "%04d/%02d/%02d %02d:%02d:%02d";
print sprintf($fmt_yyyyMMddhhmmss, $year+1900,$mon+1,$mday,$hour,$min,$sec);

【その他参考にしたサイト】
・CotEditor で現在時刻を挿入する Python スクリプトを作った
http://d.hatena.ne.jp/ryskosn/20100920/1284975691


perlのパス名を調べる
http://www.tohoho-web.com/perl/prepare.html


[関連]
CotEditorまとめ