iOS AppのInAppPurchaseの処理、となると大抵の場合はAppleから提供されているサンプルコードを利用すれば異なります。もちろん私もこのコードを使いながらも自分が使いやすいように購入部分のみをシングルトンクラスへ処理を移して、


Environment *environment = [Environment instance]
assert(environment);

BOOL request = [environment requestPurchase:@"MY_PRODUCT_ID"];
if (request == NO) { 
  UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:....] autorelease];
  [alert show];
}

 

のようなコードを打てば好きなタイミングでプロダクトIDさえ渡せば処理は勝手にやってくれます。購入後にサーバ側で購入に対するデータ処理は行ってくれるので特にクライアントはそれ以上の処理が必要ないぢゃんと踏んでいたのですが・・・・。バカだったと反省した今日この頃です。となると、最終的にAppleサンプルでいうcompleteTransaction, failedTransactionの部分にデリゲートをコールするなんていう仕掛けを追加しなければならないのですが、結構デリゲート嫌いなんです。特に、今回のような1デリゲートに1プロトコルしか必要のない時は。

ここで思い出したのが、最近GameCenterで見かけたコール部分に関数定義を渡すような方法。この名前を知らずにググっていたらStackoverflowに引っかかりました。

これを見ると単純に”blocks”としか書いてないですが、日本語の場合はブロック構文というみたいですね、多分。正直、このサンプルを見たときには意味不明な構文過ぎて理解しがたかったですが、それを除いても使う価値はあるものだな、と。ちなみに、私が書くとこんな感じになります。


Environment *environment = [Environment instance]
assert(environment);

BOOL request = [environment requestPurchase:@"MY_PRODUCT_ID" callback:^(BOOL result){
  // ここで購入後の適切な処理をする
}];

// ここのエラーブロックはリクエストそのものに失敗した場合
if (request == NO) { 
  UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:....] autorelease];
  [alert show];
}

-------

@interface Environment

void (^_cbPurchaseHandler)(BOOL result);



@implements Environment

- (void)requestPurchase:(NSString *)product_id callback:(void(^)(BOOL))cbHandler {
  // 適切なproduct_idかどうかを調べたり、各種購入の前確認を行う
  ...(略)...
  
  if (invalid) {
    return NO;
  }

  // コールバックのハンドラーをコピーする
  _cbPurchaseHandler = [cbHandler];
  
  // 購入処理を走らせる
  [[SKPaymentQueue defaultQueue] addPayment:payment];
  
  return YES;
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
  // Your application should implement these two methods.
  [self recordTransaction: transaction];

  // コールバックを呼び出して購入処理に対する付与処理を行う
  _cbPurchaseHandler(YES);
  [_cbPurchaseHandler release];
  _cbPurchaseHandler = nil;
  
  // Remove the transaction from the payment queue.
  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
  // キャンセルも含めて購入が失敗したことをコールバックに通知する
  _cbPurchaseHandler(NO);
  [_cbPurchaseHandler release];
  _cbPurchaseHandler = nil;

  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

 

あ、所詮ほげったほげぐらまが書くコードなのであまり突っ込まないでね。いつもほげほげしていますが、今日はいつになく有意義なものを覚えました、とさ。なんでこんな回りくどい実装をしたかというと、プロダクトIDを渡して購入処理をする部分(恐らく大抵のアプリで共通)、とその結果を受けて付与する部分(アプリ固有の処理)で分けたかったから。

大層な処理ブロックでもないのでコピーすればいいぢゃん、なんですがそこは宗教が許さないのでしょう。だって宗教なんだもん・・・・