NSNotificationCenterを使った通知のサンプル

オブジェクト間で通信、通知を行いたい事って頻繁にありますよね。
解決方法として色んなパターンがあると思いますが、cocoaフレームワークには大変便利な通知システムが用意されています。

使い方も簡単で、しかもオブジェクト間の関連性が薄いので大変使いやすいです。

通知は文字列の名称を使って識別されますので、ヘッダに定数を宣言しておきます。
通知を発行するクラスのヘッダに書くのが良いのではないでしょうか?

#define kDataManagerFinishLoading @"DataManagerFinishLoadingMsgKey"

通知を発行する側はこんな感じで簡単に通知できます。

// 引き渡しパラメータの作成
// Dictionaryでなんでも渡せます
NSDictionary* info = @{ 
  @"date":[NSDate date],
  @"title":@"目くじら", 
  @"count":[NSNumber numberWithInt:12] 
};

// 通知                    
[[NSNotificationCenter defaultCenter] 
    postNotificationName:kDataManagerFinishLoading  
    object:self 
    userInfo:info];

通知を受け取る側のコードはこんな感じです

最初に、NSNotificationCenter に通知の監視を登録します。
self.dataManagerってのが通知を発行するオブジェクトで、通知を受け取るオブジェクトがselfだとすると。

[[NSNotificationCenter defaultCenter] 
    addObserver:self 
    selector:@selector(handleDataManagerFinishLoading:) //←通知を受け取るセレクタ
    name:kDataManagerFinishLoading 
    object:self.dataManager];

受け取った時の処理を書いておきます

// kDataManagerFinishLoading の通知ハンドラ
-(void)handleDataManagerFinishLoading:(NSNotification *)aNotification
{
    //userInfoはDictionaryなので色んな情報を受け取る事ができます。   
    NSDate* date = [[aNotification userInfo] objectForKey:@"date"];
    NSString* title = [[aNotification userInfo] objectForKey:@"title"];
    NSNumber* count = [[aNotification userInfo] objectForKey:@"count"];
    
    //なにかしら処理

}

通知が不要になったら解除しておきます。
dealloc等に書いておくのがよいのではないでしょうか?

[[NSNotificationCenter defaultCenter] removeObserver:self];

以上

オブジェクトのプロパティを監視する

あるオブジェクトのプロパティ値が変更された時に何かしら処理を行いたい場合のサンプルです。

対象オブジェクト objA が title というプロパティを持っている場合を例とすると下記のような感じです。

objAにプロパティの変更通知を登録 (objAを生成したタイミングなどで)

// 
[objA addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];

通知はaddObserverで登録したオブジェクトの下記のメソッドが呼び出されます

// 監視対象が変更された時に呼び出されるコールバック
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object 
			change:(NSDictionary *)change 
		       context:(void *)context
{
    if ([keyPath isEqual:@"title"])
    {
	NSString* newTitle = [[change objectForKey:NSKeyValueChangeNewKey] stringValue];
        NSLog(@"newTitle :%@", newTitle);

        //変更された時の処理を書く

    }
}

通知が不要となったら登録を解除 (objAを廃棄する前に実行します)

//
[objA removeObserver:self forKeyPath:@"title" context:NULL];

cocoaフレームワーク、、便利過ぎる。。

外からは読み込み専用で内からは読み書き可能なプロパティ

まぁ、表題の通りなんですけど、たまにこういう内と外でスコープが異なるプロパティ必要ですよね。

C#とかだとアクセサにスコープを設定できるので簡単なんですけど、objective-cだとどうするのか?
色々調べたんですけど、超簡単に出来る事が分かりました。

要約すると
・interfaceではプロパティをreadonlyで宣言しておく
・implementationではプロパティを同じ名前、型でreadwriteで宣言する
そんだけです。

ヘッダファイルにはこんな感じに書いて


@interface Hoge : NSObject
{
}

@property (nonatomic,readonly) NSString* accountName;

@end

ソースファイルには上のプロパティを無名カテゴリで再宣言します

@interface Hoge ()

@property (nonatomic,readwrite,retain) NSString* accountName;

@end

@implementation Hoge

-(void)dealloc
{    
    [_accountName release];
    
    [super dealloc];
}

@end


こんな事出来るんですねぇ
objective-c、、自由過ぎる(笑)

objective-cでマルチスレッド処理(GCD)

Grand Central Dispatch(以下GCD)を使うと、マルチスレッド処理が非常に簡単に行えちゃいます。

使用方法やシチュエーションは色んなパターンがあるのですが、一番利用するであろう重たい処理を別スレッドで実行させるパターンをメモっておきます。
重たい処理をメインスレッドで実行すると再描画が行われなくなったりユーザ操作に反応しなくなるので必須ですよ。


//GCDで非同期処理実行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

  //スレッド内では個別の autoreleasepool を作成する
  @autoreleasepool{

    // [重たい処理を実行する]

    dispatch_sync(dispatch_get_main_queue(), ^{

      // [メインスレッドでUIの更新を行う]

    });
  }
});

このほかにも dispatch_apply や dispatch_group_wait を使うと簡単に並列処理もできますよ。
これは別の機会に書きますね。
(→書いてみました)

NSArrayのソート方法 (NSSortDescriptorを使って)

NSArrayのソート方法は色々あるんですけど、その中でNSSortDescriptorを使った方法をメモっておきます。
この方法はオブジェクトのプロパティ値の単純比較でソート出来る時に有効ですよ。

プロパティに「ValA」「ValB」があるオブジェクトを、ValAの昇順+ValBの降順でソートするサンプルです。


NSSortDescriptor* sortA = [[[NSSortDescriptor alloc]initWithKey:@"ValA" ascending:YES]autorelease];
NSSortDescriptor* sortB = [[[NSSortDescriptor alloc]initWithKey:@"ValB" ascending:NO]autorelease];

[_dataArray sortUsingDescriptors:@[sortA, sortB]];

※@[ A , B ] の記述はNSArrayを作成する記述です

この例のように “sortUsingDescriptors:” を使った場合にはレシーバー内の要素がソートされますし、”sortedArrayUsingDescriptors:” を使えばソート済のNSArrayが返されます。

言語を指定してリソースファイルから文字列を取得する

iOSに設定された言語に対応したリソースから文字列を取得するのであれば
こんな感じで簡単ですね。
NSLocalizedString(@”res_key”, nil)

さて、これをiOSの言語設定によらず、プログラム内で指定した言語のリソースファイルから文字列を取得するにはどうすればよいか?
一般的にはこんな事は不要なんだと思いますが、私自身必要に迫られましたので、メモとして残しておきます。

下記の手順で取得できます。
・言語に対応したリソースをNSBundleの形で取得する
・NSBundleから localizedStringForKey を使って文字列を取得

//リソースファイルのフルパスを見ると"ja.lproj" のようにかかれていますので
//対応する言語の識別子を設定する
NSString* lang = @"ja";

//バンドルを取得
NSString *path = [[NSBundle mainBundle ] pathForResource:lang ofType:@"lproj" ];
NSBundle *bundle = [NSBundle bundleWithPath:path];

//文字列の取得
// この例では"res_key"として設定されている文字列を求めています
NSString *text = [bundle localizedStringForKey:@"res_key" 
                  value:@"undefined" table:nil];

こんなの需要ありますかね??

AVFoundationを利用した動画表示用の便利クラス (使い方)

前回UPした動画再生クラス MovieView の使い方です
今回はサンプルとしてスライダーで再生位置の調整も行えるようにしてみました。

プライベートな変数にこんなのを用意して

UISlider* _slider;
MovieView* _movieView;

ビューの生成と破棄


-(void)dealloc
{
    if(_movieView){
        _movieView.delegate = nil;
        [_movieView removeFromSuperview];
        _movieView = nil;
    }
       
    [super dealloc];
}


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

     //スライダーの生成
     _slider = [[[UISlider alloc]initWithFrame:CGRectMake(0, 0, 300, 40)]autorelease];
        [self addSubview:_slider];
        [_slider addTarget:self action:@selector(seekBarValueChanged:) forControlEvents:UIControlEventValueChanged];

     //動画再生用Viewの生成
     _movieView = [[[MovieView alloc]initWithFrame:self.bounds]autorelease];
        _movieView.delegate = self;     //※再生だけならdelegateの設定は不要ですよ。
        [self addSubview:_movieView];
       
     //リソースからムービーのURLを取得して
        NSURL *url = [[NSBundle mainBundle] URLForResource:@"opening" withExtension:@"mov"];

     //再生開始
     [_movieView playMovie: url];
    }
    return self;
}

各種イベントのハンドリング


//スライダーからのコールバック
- (void)seekBarValueChanged:(UISlider *)slider
{    
    //ムービーのシーク
    float val = slider.value;
    float duration = _movieView.movieDuration;
    float time = val * duration / _slider.maximumValue;

    [_movieView seekToSeconds:time];   
}


#pragma mark - MovieViewDelegate

//再生開始時の処理
-(void)movieView:(MovieView*)sender movieWillPlayItem:(AVPlayerItem*)playerItem duration:(Float64)duration
{
    //スライダーを初期位置に変更
    _slider.minimumValue = 0;
    _slider.maximumValue = duration;
    _slider.value        = 0;
   
}

//再生が進んだ時に順次呼ばれる
-(void)movieView:(MovieView*)sender moviePlayAtTime:(Float64)time duration:(Float64)duration
{
    //スライダーの位置を再生時間に合わせる
    float value = _slider.maximumValue * time / duration;
    _slider.value = value;
}

 
以上のような感じです。
スライダー関連の処理が多くなってしまいましたが、単純に再生するだけなら

_movieView = [[[MovieView alloc]initWithFrame:self.bounds]autorelease];
[self addSubview:_movieView];

NSURL *url = [[NSBundle mainBundle] URLForResource:@"opening" withExtension:@"mov"];
[_movieView playMovie: url];

これだけでいけます。