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フレームワーク、、便利過ぎる。。

GCDを使って並列処理

Grand Central Dispatch(GCD)を使って並列処理を行うサンプルです。

ポイント
・dispatch_group_tを作成
・dispatch_queue_tの廃棄を行う為にdispatch_group_notifyを仕込む
・UIの変更等はメインスレッドで実行
・共通変数へのアクセスは @synchronized を使ってロックする
・dispatch_group_asyncのブロック内に autoreleasepool を作る

//GCDで複数のジョブを並列で実行してみるサンプル
-(void)gcdTest1
{
    dispatch_queue_t queue_main = dispatch_get_main_queue();
    dispatch_queue_t queue_job = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
  
    NSObject* syncRoot = [[NSObject new]autorelease];//スレッド間の同期用
    __block int counter = 0;
  
    for (int i=0; i<5; i++) {
   
        dispatch_group_async(group, queue_job, ^{
          @autoreleasepool{
              if (i==0)
              {
                  //グループの終了処理を登録しておく
                  //ここではqueue_mainを渡していますが、mainでこのスレッドを待機しているような状況では
                  //デッドロックが起こりますので、その場合はqueue_jobを渡して下さい。
                  dispatch_group_notify(group, queue_main, ^{
                      //dispatch_group_tをリリース
                      dispatch_release(group);
                  
                      //なにか処理を実行(メインスレッドで実行される)
                      NSLog(@"finish group job counter = %d",counter);
                  
                  });
              }
          
              //(ジョブを実行する)
          
              @synchronized(syncRoot){ //共通変数などへのアクセスは@synchronizedを使ってロックする
                  counter += i;
              }
          }
        });
    }
  
    //終了を待機する時は下記のコードで待ちます
    //待機させる時は dispatch_group_notify でリリースさせる必要はなく、
    // ※1でリリースすればOK
    //NSLog(@"waiting...");
    //dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //dispatch_release(group); //※1
}

上記ではdispatch_group_asyncを複数回発行して並列処理をさせていますが、単純にforを置き換えるような使い方であれば dispatch_apply を使ったら簡単に並列処理が可能です。
ただ、dispatch_applyは処理をロックしてしまいますので、完全に非同期で実行する場合には dispatch_async、dispatch_group_asyncのブロック内でdispatch_applyを実行する事が必要です。

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

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

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];

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