× [PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。 |
以下のページが参考になる
http://llcc.hatenablog.com/entry/2016/05/21/130359 要点 ・Firebaseコンソールから、プロジェクトのSymbolUploadサービスを有効化する ・秘密鍵を作成し、PCに保存 ・RunScriptにコマンドを記述 なおAndroidは以下など参考 http://skys.co.jp/archives/4445 未対応 PR |
SDKの機能を使うなら、NSObjectを継承しておこう
class MyClass: NSObject{ Argument of '#selector' refers to a method that is not exposed to Objective-C のようなエラーや、 Add @ObjC to expose this method to Objective-C みたいなエラー修正レコメンドが出たりする |
BITCODEで以下のエラーが出た場合
以下のように設定する |
本当に今更なのだがまとめ。
参考 ◯カテゴリ(category) カテゴリは元々クラスを分割して書くことが出来るようにするため考えられた言語機能 インスタンス変数を追加できない 「分割実装」 実行時に解決される だから、インスタンス変数を追加できない ソースコードが存在しない、ライブラリのクラスを扱うことができる 「実装を」分ける。 macの公式実装とか見ると、カテゴリ複数は「AppController.h」に書いてあって 実装側だけ複数の.mファイルに分かれていたりする。 既存のライブラリの拡張も可能(「拡張」と言っているがカテゴリー) 1クラスに1つしか書けない@implementationを、複数に分けるためにカテゴリーを作っている、とも言える カッコの中にカテゴリ名を書く。
実装側にインスタンス変数の宣言はできない。以下はエラーになる
◯クラス拡張(extension) privateな変数、メソッドの宣言に使う インスタンス変数を宣言できる 宣言したメソッドはクラス本体の(=カテゴリ無しの)@implementationで実装 公式で「無名カテゴリ」ともいうらしい コンパイル時に解決される だからインスタンス変数を追加できる 記述は、カッコのカッコの中は空
元の宣言(.hの@interface)とは別に、.mファイルの方に書くのが普通。 実装側でインスタンス変数の宣言も可能
|
|
アプリ内でNSTimerを発行したいときがある。
◯記述場所 NSTimerは、アプリが起動している間だけ生きていれば良かったので、Model層では扱う必要が無い。 だからViewControllerで完結させるように記述した。 ◯破棄 さて、NSTimerはキャンセルすることもできるようにしたい。 ということは、作成したNSTimerのインスタンスを保持しておく必要がある。 NSTimerインスタンスの生成・破棄及び参照保持と削除のタイミングは、 ・作成ボタン押下 インスタンス化、参照保持 ・キャンセルボタン押下 無効化、参照削除 ・タイマー発動 参照削除(←忘れがちだがここでもちゃんと削除しておく。そうでないとvalidでないNSTimerインスタンスの参照がずっと残ることになる) ということになる。 さてインスタンスの保持の方法だが、 [NSTimer] Array<NStimer> Dictionary NSMutableSet といろいろあるが、試してみて一番便利なのは NSMutableSet だった。 なぜなら、削除したいインスタンスを直接 set.removeObject(timer) という形で指定できるからである。 それ以外の型だと、削除していいインスタンスかどうかの判定のロジックを書かなければならなくなる。 ◯別のViewControllerから生成・削除させる キャンセルは実は別の画面から行われることもある、という場合。 あるViewControllerから別のViewControllerにどうやって情報を伝達するか。 NotificationCenterを使う。 ・通知を発信する側 var notification = NSNotification(name: "RemoveInAppNotification", object: self, userInfo: ["data": declareData!]) NSNotificationCenter.defaultCenter().postNotification(notification) なおuserInfoは必ず Dictionary? 型でないといけない ・通知を受け取る側 NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeInAppNotifications:", name: "RemoveInAppNotification", object: nil) func removeInAppNotifications(notification: NSNotification) { //notification.userInfo などを使って処理 } となる。 nameはもちろん揃っている必要がある。 通知を受け取る側は、addObserver()やremoveObserver()を正しく書いておく必要がある。 画面に出入りしたときにadd/removeObserverしたいところだが、この場合はいけない。 「別の画面から呼び出されたとき」に通知を受け取りたいからである。 もちろん、該当のViewControllerが常にメモリ上に存在している保証が必要だ(そんなことできるのか?) 以上まとめるとコードは以下のようになる
|
以下のリジェクト理由が来た
3.3 - Apps with names, descriptions, screenshots, or previews not relevant to the content and functionality of the App will be rejected さらに後ろにその詳細に付いても書いてある。 3.3 Details Also, we noticed your icon includes the following image, which is not relevant to the application content and functionality: We've attached the screenshot for your reference. というわけで添付されていたスクショを見ると、それはcocos2d-xのアイコンであった。 なるほど。 さっそくアイコンを削除。 バージョン番号、ビルド番号を上げ、アーカイブを作成してサブミット! …と思ったら、サブミットでエラー発生。 とのこと。 で、見てみる。 プロジェクトビューからプロジェクトを選択。 Infoタブを見てみると、確かに IconFile という項目が。その値が icon57.png になっている。 これはxcassetsが導入されてから不要になったはず。 というわけでそれを項目ごと削除。 これでOK、通った |
AdColonyのSDKを導入していて、エラーが出た。
こんな感じ。 同じ問題にぶつかっている人も→いた adcというのはおそらく AdColony の略だし、要するに、NSDataの拡張(Extension)が読み込まれていないということだろう。 それに対応するため、 Other Linker Flagsに
を追加しろ、とAdColonyのマニュアルには書いてある。ここ これはObjective-Cのイマイチなところだ。 しかしこれで別のところに問題が出た。
どうやらcocos2d-xのどこかに、未定義の変数が残っているようだ。 GCController*** という名前のものらしい。 該当するソースを読んでみると、どうもコントローラー対応周りの実装だ。 というわけで、 GameController.framework をプロジェクトに追加して再ビルドしてみる。 …するとうまく行った。 同じ要領で、 GoogleAnalytics周りでもエラーが起きた。 これは libsqlite3.dylib libAdIdAccess.a を追加することで治る。 ◯まとめ ・実装があるはずなのにリンクエラーになるメソッド、変数などは -all_load(または-ObjC)を付加するとうまくいく ・-all_loadをつけたことでエラーになる場合は、実装がプロジェクトに追加されていない。 実装がありそうなライブラリ、フレームワークを予想して追加してみる 本当は、-all_loadってむやみに使いたくない。実行ファイルが大きくなるから。 各社SDKは考慮しておいてほしいところ。 |
App内課金。
なかなか大変だが避けて通れない道。 手順を追っていこう。 ◯契約 1. iTunes Connect へ行く。 「契約/税金/口座情報」のページへ行き、必要事項を記入。これについては長くなるので、ググって別記事を参照のこと。 これをしていなくても以降の手順は行えるが、実際に端末でテストした時に課金アイテム情報が取得できず、詰まることになるので注意! ◯アイテム登録 1. iTunes Connect へ行く。 最近また変わったので、スクリーンショットも付けて。 まずアプリのページを開き、「App内課金」を選択。 2. App内課金の画面。 以下のようになっている(これはすでにいくつか課金アイテムを登録した後の画面) Create New をクリック。 3. 課金タイプを選択 消耗型(Consumable)…何度でも購入できるアイテム。 非消耗型(Non-Consumable)…一度しか購入できないアイテム。アプリを削除しても、ユーザがリストアできるようになっていなければならない(後述) その他は定期購読雑誌などに使うタイプの物。省略 4. 情報を登録 アイテム名、ID、価格、説明文を記述する。 またスクリーンショットも登録する。これは必須。無いとテストもできない。何度でも差し替えられるので、とりあえず何でもいいから画像を登録しておく。 5. 登録できたことを確認 Doneをクリックすると、App内課金のトップ画面に戻り、今つくったアイテムが表示される。 ステータスが「Ready to Submit」になっていればOK。 ◯実装 Xcode側の実装 1. ライブラリ登録 StoreKit.framework を追加 2. とりあえずRootViewController.mmに実装したものが以下。 外部から呼び出されて駆動するイメージで書いている。 (本筋と関係のない一部メソッドは省略)
以下、解説 ◯SKProductsRequest ◯SKProductsResponse プロダクト情報をAppleに問い合わせるためのクラス。 initWithProductIdentifiers: メソッドでプロダクトIDの配列を渡してインスタンスを作成する。 start: メソッドを呼ぶと問い合わせを開始する。 delegateをセットしたインスタンス(id ・問い合わせに成功した場合 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response; responseには、正常に情報が取れたSKProductの配列(products)と、情報が取れなかったIDの配列(invalidProductIdentifiers)が入っている。 ・失敗したとき場合 - (void)request:(SKRequest *)request didFailWithError:(NSError *)error; ・完了した場合(成功したあとにも呼ばれる) - (void)requestDidFinish:(SKRequest *)request; ◯SKProduct 課金アイテムの情報を持つクラス。メソッドは持っていない。 主なプロパティは以下。
国ごとの金額を文字列で取得する場合(日本では120円、アメリカでは$1、のようなこと) priceとpriceLocaleを組み合わせて以下のように記述する。
◯SKPayment Apple側に、購入する内容を伝えるためのクラス。プロダクトIDや数量をプロパティに持つ。 インスタンスを生成するときは、SKProductを引数に取る。 SKPaymentをSKPaymentQueueにaddPaymentすることで、購入トランザクション(SKPaymentTransaction)が作成される。 ◯SKPaymentTransaction 1つの購入トランザクション。 メソッドは持っておらず、プロパティの集合体のクラス。 主なプロパティは以下。
◯SKPaymentQueue 購入トランザクション(SKPaymentTransaction)を管理するクラス。 ・購入可能かのチェック [SKPaymentQueue canMakePayments] で、このアプリが課金アイテム購入ができるかどうかチェック。 ・オブザーバー登録 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; でオブザーバーを登録する。トランザクションに変化が起きたとき、オブザーバーに対して通知が行く(メソッドが呼ばれる)。delegateのようなもの。 selfは id オブザーバーがいないと、例えば購入が完了してもそれを関知できない。つまり課金だけしてその結果をアプリ内に反映できなかったりする。 だから必ずオブザーバーはつけておく。 バックグラウンドに行ったときにオブザーバーを削除するような実装もあるが、購入トランザクションの途中でバックグラウンドに行き、バックグラウンドの状態で購入完了してしまうと、まさに上記の現象になってしまう。 だからバックグラウンドに行ってもオブザーバーを削除しない方がいい。どうしても削除するなら、SKPaymentQueue内のトランザクションをすべて終了させてから。(ただし購入途中のトランザクションに対してfinishTransaction:を実行すると例外が投げられるので注意) ・購入トランザクションの開始 購入を始めるとき(ユーザが購入ボタンを押したとき)は、 [[SKPaymentQueue defaultQueue] addPayment:payment]; とする。paymentはSKPaymentクラスのインスタンス。 ・リストアを行う [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; これですべての購入済み非消耗型アイテムのトランザクションを元に購入情報をAppleに問い合わせる。 ・購入トランザクションの終了 トランザクションを終了するとき(正常に購入できた、正常にリストアできた、エラーが起きて終了するとき)は [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; とする。 ・購入トランザクションに変化が起きたとき - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions; メソッドが呼ばれる。これはSKPaymentTransactionObserver プロトコルのメソッド。 何が起きたかは、各トランザクションのtransactionStateを見れば分かる。 SKPaymentTransactionState 列挙子の値一覧
それぞれの状態に応じた処理を行う。 成功ならアプリ内に反映。アイテム付与など。トランザクションは終了する。 リストアならアプリ内に反映。アイテム数を確認し、正常な値にセット。トランザクションは終了する。 失敗ならエラー表示。トランザクションは終了する。 許可待ちなら、メッセージ表示。 購入中なら何もしないで待つ。 ・購入トランザクションが削除されたとき - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions; が呼び出される。 finishTransaction: の結果として呼び出される。 ・リストアがすべて終了したとき - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue; が呼び出される。 1つ1つのアイテムのリストアは - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions; において transaction.transactionState == SKPaymentTransactionStateRestored で呼び出される。 すべてのアイテムが終わったら、このメソッドが呼び出されることになる。 ・リストアが失敗したとき - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error; これが呼ばれる。どのような対処が有効なのだろうか… ◯ 流れで言うと、 iTunes Connectで設定したプロダクトID(前述のスクショで言うと、TEST_ITEM。com.yourcompany.app.TEST_ITEM とかではない。むしろこれだとSKProductResponseで情報が取れない)を起点に、 起動時(情報取得) 全プロダクトID →SKProductRequest (initWithProductIdentifiers: PRODUCT_ID_LIST) →SKProductResponse (- (void)productsRequest: didReceiveResponse:) →SKProduct (response.products) 購入時 プロダクトID →SKProduct (保持SKProductから検索) →SKPayment ([SKPayment paymentWithProduct: product]) →SKPaymentQueueに追加 ([[SKPaymentQueue defaultQueue] addPayment:payment];) →購入処理 (- (void)paymentQueue: updatedTransactions: で transactionState == SKPaymentTransactionStatePurchased) →失敗処理(- (void)paymentQueue: updatedTransactions: で transactionState == SKPaymentTransactionStateFailed) →許可待ち処理(- (void)paymentQueue: updatedTransactions: で transactionState == SKPaymentTransactionStateDeferred) リストア時 [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; →各リストア処理 (- (void)paymentQueue: updatedTransactions: で transactionState == SKPaymentTransactionStateRestored) →全リストア完了通知 (paymentQueueRestoreCompletedTransactionsFinished:) ・どんなときリストアするか 消耗型アイテム以外を購入できる場合は、必ずリストア機能が必要だ。 リストアボタンを置く必要がある。(ないとリジェクト) ゲームではリストアボタンを置く必要があるのか。 一度しか買えないけど、リストアする(端末間共有)することが必要かどうかはまた別問題。 例えば、取得経験値が永久2倍になるようなアイテム。ゲーム的に一度しか買えないようになっているものだが、これは非消耗型アイテムなのか。それとも、消耗型アイテムとして登録し、アプリ側で一度しか買えないように制限するのか。 非消耗型アイテムを「端末間共有されると想定される」ものとして定義するなら、この永久2倍アイテムは非消耗型アイテムではない。 消耗型アイテムとして登録するのが相応しい。 購入できる回数によって分けるということでは必ずしも無い。 非消耗型アイテムとして登録すると、ゲームの購入画面にリストアボタン(復元ボタン)を置かなければならなくなる。しかしそうすると、他の「消耗型アイテム」も復元されるはずではないか?とユーザは思ってしまう可能性がある(ユーザには消耗型、非消耗型の区別は見えないのだから当然だ)。そう考えると、すべてのアイテムを消耗型として作っておく方がユーザにとっても誤解がないということがあると思われる。 ◯レシート検証 検証ロジックは開発者が各自で行うべきという考えになっている。 クラックされないようにするためだ。 レシートデータは、iOS7以前だと購入トランザクションの中に入っていて、SKPaymentTransactionStatePurchased だと正常なものが格納されている。以下で取得できる。 NSData* receiptData = transaction.transactionReceipt iOS8以降だと、transactionReceiptは非推奨になっている。 どうするかというと、バンドル内に保存されているのでそれを読み出す。 NSBundle の appStoreReceiptURL というメソッドでパスが取得できる。 以下の流れになる。 NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData = [[NSFileManager defaultManager] contentsAtPath:url.path]; 検証のために参考になるページ Qiitaの記事 公式ドキュメント ◯動作確認の手順 まず、実機端末で行う事(シミューレーターでは不可) 1. メアドを作成し(gmailの+付きメアドでもいいかも)、iTunesConnectでsandboxテスターとして登録しておく 2. 実機端末で、AppStoreからサインアウトしておく 3. Xcodeからバイナリを転送。この時、開発用provisioningで作成したバイナリであること。本番用だとテスト用課金はできない 4. アプリを起動し、課金購入を行う。この時、アカウント情報を求められるので、さっき作ったアカウントを入力 5. 購入ダイアログが出る。[Environment: sandbox]と出ていれば、テスト環境での課金になっている 公式ドキュメント(日本語) |