2012/05/21

Blockを用いたHookの利用

viewWillAppearで生成したオブジェクトをviewDidDisappearで解放する場合等に、ブロックを使うと関連するコードをまとめて記述できます。この方法であればプロパティが増えてもそれぞれにhookを記述できるので、記述漏れを予防しやすいと思います。

- (void)viewWillAppear:(BOOL)animated
{
   [super viewWillDisappear:animated];
    
    self.viewWillDisapperHooks = [[NSMutableArray alloc]init];
 
    self.myobject = [[MyObject alloc]init];
    [self.viewWillDisapperHooks addObject:^{
            self.myobject =nil;
        }];
}


- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    for(void (^hook)(void) in self.viewWillDisapperHooks){
        hook();
    }
    self.viewWillDisapperHooks=nil;    
}

2012/05/18

hidden or visible?

CocoaのUI Viewは、オブジェクトを非表示にするためのhiddenという属性を持っています。ただ、他のフレームワークでは同じ目的で意味が反転したvisibleという属性があることが多く、僕はちょっと混乱していました。特にenableと組み合わせる場合に、ユーザ操作可能なのがYESなのかNOなのかがわかりにくくないですか?

この辺は設計思想に関わることなので、フレームワークのお作法に慣れる方が望ましいです。でも、嫌な人は我慢せずにさくさくっとカテゴリで拡張しちゃいましょう。当然プロパティアクセスも可能です。

UIView+Visible.h
#import <UIKit/UIKit.h>

@interface UIView (Visible)

@property(nonatomic,getter=isVisible) BOOL visible;

@end

UIView+Visible.m
#import "UIView+Visible.h"

@implementation UIView (Visible)

- (BOOL)isVisible
{
    return !self.hidden;
}

- (void)setVisible:(BOOL)visible
{
    self.hidden = !visible;
}

@end

外からはreadonlyで、中からはreadwriteな@property

さっき気がついたことのメモ。
readonlyで定義したpropertyを、実装ファイル側でreadwriteに上書きできます。immutableなクラスでpropertyを使う場合にとっても便利。

HOGEHOGE.h
@interface HOGEHOGE : NSObject
@property(readonly,nonatomic,copy) NSString* title;
@end
HOGEHOGE.m
@interface HOGEHOGE ()
@property(readwrite,nonatomic,copy) NSString* title;
@end

@implementation HOGEHOGE
@synthesize title=_title;
@end
ひとりでコードを書いていると、この手の基本的なことをずっと知らなかったりします。

2012/05/17

NSStringの定数定義

これも常識だと思いますが、備忘録としてメモ

定数の命名規則

歴史的にkで始まる定義をよく見かけますが、 Coding Guidelines for Cocoaにはそのようなルールはないようです。他の定数と衝突しないことが重要なので、僕は「クラス名」+「k」+「変数名」を使用しています。

グローバルな定数

他からも参照できるようにヘッダーファイルでextern宣言し、実装ファイル内に定義します。

HogeHoge.h file
extern NSString* const HogeHogekMogeMoge;
HogeHoge.m file
NSString* const HogeHogekMogeMoge = @"HAGAHAGA";

ローカルな定数

Headerファイルでは特に定義をせず、実装ファイル内でstaticに定義しスコープをコンパイル単位に限定します。

HogeHoge.h file
定義なし
HogeHoge.m file
static NSString* const HogeHogekMogeMoge = @"HAGAHAGA";

2012/05/16

ARC環境でweak pointerがnilになるタイミング

検証コードをまとめる余裕がないのでメモとして。

weak pointerにselfを保存している場合、deallocが呼ばれるより前にnilになるようです。つまり下記のような実行順序と考えられます。

  • オブジェクトを指すstrong pointerの数が0になる
  • オブジェクトを指すweak pointerがnullifyされる(nilになる)
  • オブジェクトがdeallocされる
考えてみれば当たり前ですね。

僕の場合、Notification Centerに登録する際に登録解除処理をブロックとして保存し、deallocで呼び出すつもりでした。ここでweak pointerを使うとdealloc呼び出し時にはnullifyされていて動作しません。逆に、strong pointerを使っているとそもそもdeallocされません。このような場合、unsafe_unretained pointerを使うことでオブジェクトを解放しつつ、nullifyを避けることができます。iOS4互換のためだけに存在すると思っていたんですが、こんな利用法があるんですね。

でも多分、今回のケースにおける正解は「そんなコードは書かない」だと思います:-p

2012/05/02

Javaプログラマ的なDynamoDBの捉え方

予測可能なパフォーマンスを、メンテナンスフリーで手に入れられるAmazon DynamoDB。非常に魅力的ではありますが、DynamoDBは徹底的にKey-Valueな考えが必要になります。というか、主キー以外でのルックアップは基本的に実行できないと考えた方が良いです。特にPrimary Keyの扱いが重要なので、ちょっとまとめみました。

DynamoDBのPrimary Keyには「Hash Type Primary Key」と、「Hash and Range Type Primary Key」の二種類があります。詳しい解説は他のサイトにまかせるとして、これらをJavaプログラマ的に捉えると下記のようになります。

Hash Type Primary Key

Map<HashKey,Object>
Hash Type Primary Keyを使用する場合には、本当に純粋なMapとして捉えられます。HashKeyが適切に分散さえしていれば、DynamoDBのすばらしい性能の恩恵を受けることができます。

Hash and Range Type Primary Key

Map<HashKey, SortedMap<RangeKey,Object>>
Hash and Range Type Primary Keyを使用する場合にも、Hash Keyの役割は変わりません。ただ、これに加えて、ソートされたRange Keyを用いることができます。Range Keyはソートされているだけでなく、Queryという機能を用いて柔軟にアクセスできます。ただし、Range Keyだけでは十分な分散は実現できません。

Primary Keyを用いないアクセス

Primary Keyを用いない場合には、Scanという機能で総当たり処理が可能です。ただ、Scanは主キーを無視した完全な総当たり処理であり、原則として実運用に用いるべきものではないと考えます。Primary Key以外で検索を行う必要がある場合には、別のTableをインデックスとして作成するか、DynamoDB以外と組み合わせることを検討した方が良いと思います。

Use it well

DynamoDBは強い癖がありますが、うまく使えば非常に強力な道具になります。エンジニアの腕の見せ所ですね。

2012/04/28

AWS Java SDK用のGuice Provider

AWS Java SDKに含まれるクライアントはすべてThread-Safeであり、クライアントを再利用すべきです。もちろん自前でClientを保持するクラスを書いても良いのですが、GuiceによるDIを使っているのであればProviderを定義するのがオススメです。

AWS SDKはもともとインタフェイスが定義されているので、そのインタフェイスに対してGuiceのProviderを定義し、共有クライアントをProvider内で管理します。クライアントを使う部分では、下記のように注入するだけなので、コードの見通しが非常に良くなります。

@Inject
AmazonDynamoDBAsync client;

もしくは

@Inject
Provider<AmazonDynamoDBAsync> clientProvider;

Provider自体はすぐに書けるコードなのですが、再利用性が高そうなのでgithubで公開しました。

参考URL

2012/04/27

Snowflake的なID生成方法

作っているプログラムでランダムなIDが必要となったため、ちょっとリサーチしました。久しぶりにJavaでコードを書いています。

UUID

完全に分散した環境で使用するのには、UUID(Universally Unique Identifier)が最適です。1IDあたり16バイトの容量が必要となる点をのぞけば、理想的なIDと言えると思います。ただIDは大量に使用されるので、16バイトというサイズはちょっと気になります。

Snowflake

SnowflakeはTwitterが使用しており、Apache Licenseで公開しているID生成方法です。ある程度分散した環境でも、1IDあたり8バイトの容量で利用できます。また非常に重要な特徴としてIDの先頭部分にタイムスタンプ利用しており、生成されたIDはある程度時系列に並びます。8バイト、つまり64bitを下記のように振り分けて使用します。

  • 先頭42bitにタイムスタンプ(ms)を割り当て、時刻を分散
  • 10bitのマシンIDを割り当て、ID生成を行うマシンを分散
  • 12bitのシーケンスIDを割り当て、同一時刻(ms)、同一マシンでのIDを分散
タイムスタンプに42bitしか割り当てないのは心もとないですが、基準時刻をシフトさせることで利用できる期間をのばしています。

参考

Snowflake的なID生成方法

Snowflakeは魅力的なのですが、テスト環境等も含めてマシンIDを管理するのは意外に面倒です。ただ、この体系の本質的な特徴は「先頭にタイムスタンプを割り当てた分散ID生成」であり、このテクニックはいろいろ応用できそうです。

今回使用する構成ではAmazon DynamoDBでDHCPのようにworkerを管理することにしました。DynamoDBをアトミックカウンターとして使用するという超贅沢仕様で、ついでにidの取得時刻、開放時刻等も記録してみました。worker idが枯渇するリスクを下げるため、worker idは24時間に一度更新しています。

2012/04/25

@propertyとAutomatic Reference Counting (ARC)

Objective-Cは段階的に進化してきたため、ネットで情報を検索する際には対象としている世代に注意する必要があります。特に、ARC導入前後はかなり考え方が変わっているので、簡単にまとめてみました。

共通の考え方

オブジェクトには参照カウンタが存在し、参照カウンタが0になった時点でオブジェクトは解放されます。JavaのGCのように定期的にメモリ解放が行われるのではなく、カウントが0になった時点ですぐにメモリが解放されます。

@property導入前

Next STEP時代から続くメモリ管理手法で、参照カウンタはすべてプログラマが手動で管理します。retainで参照カウンタを増やし、releaseもしくはautoreleaseで参照カウンタを減らします。setterもプログラマが明示的に記述し、必要に応じてretain/releaseを行います。

ARC導入前の@property (retain,assign世代)

@propertyに「retain」を指定した状態で@synthesizeを利用してメソッドを生成すると、「setするオブジェクトをretainし、以前setされていたオブジェクトをreleaseする」という処理を自動的に生成してくれます。あくまでも「プログラマの代わりにsetterを書いてくれる」だけなので、@synthesizeされたsetter以外では効果がありません。

ARC導入後の@property (strong,weak世代)

Automatic Reference Countingの名が示す通り、retain/releaseはすべて自動的にコンパイラが生成します。逆に、プログラマは明示的にretain/releaseを行うことはできません。簡単に書くとポインタを強参照(strong)と弱参照(weak)で区別し、強参照が増える場合にはretain、減る場合にはreleaseがコンパイラによって挿入されます。@propetyのstrong,weakで、その変数を強参照にするか弱参照にするかを定義します。当然、@synthesizeされたsetter以外でも効果があります。

ARCを使用するべきか?

使わない理由はないと思います。
ARCはコンパイル時のオプションであり、プロジェクトの一部分だけにARCを使用することもできます。また、ターゲットがiOS4の場合でも使用可能です。(ただしiOS4ではweakポインタが使えないので、zeroingのない__unsafe_unretainedポインタを使用します。)

XCode4.3環境でCocoaPodsをインストール

CocoaPods ではじめる Objective-C ライブラリ管理を参考にCocoaPodsをインストールしようとしたら、最初からハマりました...

sudo gem install cocoapods
Building native extensions.  This could take a while...
ERROR:  Error installing cocoapods:
 ERROR: Failed to build gem native extension.

/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby extconf.rb
mkmf.rb can't find header files for ruby at /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/ruby.h


Gem files will remain installed in /Library/Ruby/Gems/1.8/gems/xcodeproj-0.1.0 for inspection.
Results logged to /Library/Ruby/Gems/1.8/gems/xcodeproj-0.1.0/ext/xcodeproj/gem_make.out

最初はmacrubyをインストールしてmacjemでも試したのですが、同じようなエラーでした。調べてみると、まずXCode4.3環境ではmacrubyは必要ないようです。さらに、XCode Command Line Toolを追加でインストールする必要があります。さっそくインストールして再度トライ!

sudo gem install cocoapods
Password:
Building native extensions.  This could take a while...
[!] If this is your first time install of CocoaPods, or if you are upgrading, first run: $ pod setup
Successfully installed xcodeproj-0.1.0
Successfully installed cocoapods-0.5.1
2 gems installed
Installing ri documentation for xcodeproj-0.1.0...

No definition for generate_uuid

No definition for read_plist

No definition for write_plist
Installing ri documentation for cocoapods-0.5.1...
Installing RDoc documentation for xcodeproj-0.1.0...

No definition for generate_uuid

No definition for read_plist

No definition for write_plist
Installing RDoc documentation for cocoapods-0.5.1...

今度は無事にインストールできました。XCode4.3環境の人は、「XCode上でCommand Line Tools」をインストールしてから「sudo gem install cocoapods」するだけなのでとってもお手軽です。

参考リンク

2012/04/24

DelegateをBlockを使って実装

iOS、というかCocoa Frameworkが利用しているdelegateは非常に便利なのですが、複数のDelegateに対応する場合コンポーネントに関する記述が分散して読みにくくなる面があると思います。

iOS4からはBlockが利用できるので、delegateで実現されている一部の機能はBlockで記述した方が簡潔に記述できます。ちょっと調べてみると、やはりUIActionSheet等でdelegateを使うことに不満を持ち、サブクラスやカテゴリを利用してBlockに対応させている人も多いようです。

調べている過程でBlockも@propertyに設定できることがわかったので、それを利用してDelegateをBlock対応にするクラスを書いてみました。このDelegateオブジェクトにプロトコルを中継させることで、呼び出し元からはBlockのみで記述できるようになります。Delegateは通常弱参照しかされないので、オブジェクトの参照管理には注意してください。

具体的な実装例があった方がわかりやすいと思うので、UIImagePickerController用のコードを引用します。@property + @synthesizeを利用することで、かなり簡潔に記述できることがわかると思います。ダイアログ表示、通常処理、キャンセル処理が一カ所で記述できるので、非常に読みやすいコードになりました。

2012/5/12 追記
Delegateを実装していない場合の挙動に影響を与えないように、Blockを設定しているかどうかをrespondsToSelectorで確認するようにしました。


#import <Foundation/Foundation.h>

@interface BTKUIImagePickerControllerDelegate : 
    NSObject<UIImagePickerControllerDelegate,UINavigationControllerDelegate>

//UIImagePickerControllerDelegate
@property(readwrite,copy) void (^didFinishPickingMediaWithInfo)(UIImagePickerController *,NSDictionary *);
@property(readwrite,copy) void (^imagePickerControllerDidCancel)(UIImagePickerController *);

//UINavigationControllerDelegate
@property(readwrite,copy) void (^willShowViewController)(UINavigationController *, UIViewController *,BOOL);
@property(readwrite,copy) void (^didShowViewController)(UINavigationController *, UIViewController *,BOOL) ;

@end


#import "BTKUIImagePickerControllerDelegate.h"

@implementation BTKUIImagePickerControllerDelegate

@synthesize didFinishPickingMediaWithInfo=_didFinishPickingMediaWithInfo;
@synthesize imagePickerControllerDidCancel=_imagePickerControllerDidCancel;

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if(aSelector == @selector(imagePickerController:didFinishPickingMediaWithInfo:)){
        return self.didFinishPickingMediaWithInfo!=nil;
    }
    if(aSelector == @selector(imagePickerControllerDidCancel:)){
        return self.imagePickerControllerDidCancel!=nil;
    }
    if(aSelector == @selector(navigationController:willShowViewController:animated:)){
        return self.willShowViewController!=nil;
    }
    if(aSelector == @selector(navigationController:didShowViewController:animated:)){
        return self.didShowViewController!=nil;
    }
    return [super respondsToSelector:aSelector];
}

- (void)        imagePickerController:(UIImagePickerController *)picker 
        didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.didFinishPickingMediaWithInfo(picker,info);
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker;
{
    self.imagePickerControllerDidCancel(picker);
}

@synthesize willShowViewController=_willShowViewController;
@synthesize didShowViewController=_didShowViewController;

- (void)navigationController:(UINavigationController *)navigationController
      willShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animated
{
    self.willShowViewController(navigationController,viewController,animated);
}

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animated
{
    self.didShowViewController(navigationController,viewController,animated);
}

@end

呼び出し元からは、下記のようにBlockを設定できます。(ARC使用)

self.imagePickerDelegate = [[BTKUIImagePickerControllerDelegate alloc]init];
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self.imagePickerDelegate;

__weak BTKViewController* bself = self;       
imagePickerDelegate.imagePickerControllerDidCancel = 
        ^(UIImagePickerController *imagePicker){
            //Close Dialog
            [imagePicker dismissModalViewControllerAnimated:YES];
            
            //Cleanup
            bself.imagePickerDelegate = nil;
        };

2012/04/22

MKUserLocationのcalloutを非表示にする

MKMapViewには現在地を表示する機能があります。現在地には見慣れた青丸のマーカーが自動的に表示され、タップすると「現在地」と表示されます。これは便利なんですが、マーカーが現在地というのは自明なので吹き出しは不要に思えます。そこで、この表示(callout)を消せないか調べてみました。
MKMapViewでは、Mapに追加されたAnnotationを表示する際に、delegateのviewForAnnotationが呼び出されます。通常はここで対応するviewを生成し、calloutの設定も行うのですが、MKUserLocationではnilを返すことで標準のMKUserLocationViewが生成される仕組みになっています。
そこで、MKUserLocationViewを自分で生成しようと思ったのですが、どうやらこのクラスは非公開のようです。とはいえ、実際にviewが生成されるのはこの関数の呼び戻し後なので、viewの設定を書き換えることはできません。
そこで、このviewを返すタイミングでMKUserLocationのタイトルをnilに設定してみました。予想通りMKUserLocationViewはtitle属性をチェックしてcalloutの表示、非表示を決めているようで、タップに応答しなくなりました。
- (MKAnnotationView *) mapView:(MKMapView *)targetMapView 
             viewForAnnotation:(id ) annotation
{
    if([annotation isKindOfClass:[MKUserLocation class]]){
        ((MKUserLocation *)annotation).title = nil;
    }
    return nil;
}

Network Link Conditioner

Appleの配布している「Network Link Conditioner」を利用すると、ネットワークの遅延、帯域等を自由に制限することができます。もちろんiOSシミュレータにも制限は適用されるので、より実機に近い環境での検証が可能になります。もっと早くこの存在を知りたかった...

以前はXCodeと一緒に配布されていたようなのですが、今は追加ダウンロードになっています。XCode上でメニューからXcode > Open Developer Tool > More Developer Tools...を選択するとダウンロードページに飛ぶので、「Hardware IO Tools for Xcode」をダウンロードしてください。

参考

2012/04/21

gcdタスクを途中で中断する

iOS上で時間のかかる処理を実行する場合には、GCDを利用してバックグラウンド処理するのが効果的です。 ただ、GCDにはcancelの仕組みがないため、cancel方法は自分で実装する必要があります。

良くあるのは、viewのdidChange系通知を使用する場合です。ユーザレスポンスを高めるためにはdidChangeにはすぐ応答したいですが、ユーザの操作によってはdidChangeは連続して送られてきます。使用されないデータを計算するのは時間的にも消費電力的にも無駄になるため、最後の一回だけを実行することが望ましいです。

僕の場合には、下記のようなキャンセル状態を保存するクラスを作成し、それをGCDで実行するブロックに渡すようにしました。

@interface Sample : NSObject
@property(readonly) BOOL isCanceled;
-(void)cancel;
@end

@implementation Sample
@synthesize isCanceled=_isCanceled;

-(void)cancel
{
  @synchronized(self){
    _isCanceled = true;
  }
}
@end

新しくGCDに処理を投入する場合には、cancelFlagを生成してそれをブロックに渡します。もしすでに使用中のcancelFlagがある場合には、先にロックを取得した上でFlagをキャンセルしておきます。

 -- ジョブは完了済みか、実行中 --
[cancelFlag cancel];
 -- ジョブは完了済みか、キャンセル済み --
cancelFlag = [[Sample alloc]init];

GCDで実行するブロックの中では処理の合間にcancelFlagを確認します。この際に必ずしもロックは必要ありませんが、処理を完了する時には必ずロックを取得することで、ジョブの二重実行をさけることができます。

参考

2012/03/16

AWSのエンドポイント

最新のエンドポイント一覧を探している場合にはこちら
Amazon Web Services Glossary : Regions and Endpoints

AWSの各リージョンは完全に独立しているため、各リージョンのendpointを設定して使用する必要があります。日本から利用する場合にはAsia Pacific (Tokyo)リージョンですね。
FQDNの命名規則が統一されていないので、必要に応じて上記Glossaryを確認することをお勧めします。