2017年06月16日

[iOS] Xcode 8.3.3 で Firebase Crash Reporting がビルドできなかった件について

エモトです.GPD Pocketは来週発送みたいですね!楽しみですが,手に入れても一体何に使うんだろうか.

Xcode 8.3.3 にて Firebase Crash Reporting を ドキュメントにしたがって実装していたところ,なんやらよく分からないビルドエラーが起こりました.

いろいろ調べたところ,stackoverflow にて,run script で設定していた upload-sym を upload-sym-util.bash に入れ替えるとよいとのこと.
# replace upload-sym with upload-sym-util.bash in the script
"${PODS_ROOT}"/FirebaseCrash/upload-sym-util.bash  "/Path/To/ServiceAccount.json"
入れ替えたところ,ちゃんとビルドでき,クラッシュを計測することができました.ちなみに,私の環境にあった upload-sym-util.bash は実行可能なファイルではなかったので,パーミッションを変更し実行可能に変えました.
chmod +x upload-sym-util.bash 
ドキュメントと異なりちょっと気持ち悪いですが,コンソールで反映されていたので,今回はこれで勘弁してもらえればーーー.
posted by Seesaa京都スタッフ at 15:00| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする

2017年05月31日

[iOS] macOS SierraとXcode 7.3.1でクラッシュする

iOS担当のエモトです.GPD Pocket用の液晶フィルムが届きましたが,本体はまだ発送されていません.いつ届くのでしょうかね.クラウドファンディング自体初めてなのでそわそわしています.

弊社は受託開発を行っているため,少し前の Xcode を使って開発することがあります.先日,macOS Sierra で Xcode 7.3.1 を使用中に Xcode が必ずクラッシュするという状態に陥りました.開発中にアプリをクラッシュさせることはありますが,開発環境のXcode自体がクラッシュするのはなり焦りました.

xcode-bug-0.png

クラッシュするようになってから,その直近で行った作業をいろいろ検証したところ,Run Scriptの編集に原因がありました.macOS Sierra で Xcode 7.3.1 をお使いであれば,以下の方法で再現することができます.もし同症状の方がいらしたら,参考になれば幸いです.

1. Run Scriptを追加する
TARGETS -> Build Phases -> New Run Script Phase

2. 横三角ボタン(▶︎)で一度でも展開した(▼)後に他の画面に遷移するとクラッシュ.閉じたまま(一度も展開していない)ではクラッシュしない

xcode-bug-4.png


stackoverflow で漁ってみると,Sierra Betaから 7.3.1 との相性は良くない ようです.最も有効な対応策としては,同サイトでも言われるように Xcode 8.0 以上の最新版を使うことでしょう.どうしても環境を新しくできないのであれば,クラッシュ覚悟で展開後に一気に script を書くことでしょうか.賢くないですね.

また,macOS Sierra で Xcode 7.3.1 はファイル追加のフォームの崩れが起こるので,一刻も開発環境を更新するのが望ましいです.

xcode-bug-2.png


posted by Seesaa京都スタッフ at 15:35| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする

2017年04月21日

[iOS] モーダル表示処理のバッドノウハウ

iOS担当のエモトです.何に使えばいいのか分からないですが,GPD Pocketに出資してみました.かつてネットブックが盛んだったころ,EeePCを始め数台のネットブックを買っては遊んだ思い出を繰り返すんだろうなと思います.

モーダル表示したViewControllerが閉じたときの判定は,いうまでもなくdismissしたときのcompletionブロックを使えば取得できます.
- (void)dismissViewControllerAnimated:(BOOL)flag
					   	 completion:(void (^)(void))completion
しかしながら,サードパーティーSDKなどでモーダルの表示と削除がそのSDK内部で行われている場合はどうしましょうか.一般的なものであれば,デリゲートメソッドなどが用意され,取得可能だと思われます.
- (void)hogehogeWillDismiss;
- (void)hogehogeDidDismiss;
しかしながら,
- (void)hogehogeDismiss;
という場合もあります.これだとdismissがwillなのかdidなのかよくわかりません.実際に開発している人にはよく分かると思いますが,willとdidの違いは,画面制御にて大きな問題になります.開発者はdidを期待して実装したら,実はwillだったとなれば,アプリの想定外の動作やクラッシュの原因になります.

私自身もこれに似た設計のSDKに遭遇したことがあり,提供元に確認したところ,そのSDKの仕様から判定できないと回答されました.今回は試行錯誤のもとでその問題を回避した方法を紹介します.しかしながら,根本的な良い方法ではないことであるとご認識ください.

方針として,モーダルの表示と削除はサードパーティーSDKが行なっているので,内部の現象を外部から観測することで解決を試みます.まず,モーダルで表示されたViewControllerは以下のように取得できるので,これを手掛かりとして探っています.
UIViewController *vc = self.presentedViewController;
これを監視すれば解決しそうです.以前に当ブログでも紹介したKVO(Key-Value Observing, キー値監視)を使えば良い気もしますが,残念ながらpresentedViewControllerは監視できないので使えません.やりたくはないですが,タイマーを回して,presentedViewControllerを監視します.

例として,HogeHogeSDKというサードパーティーSDKを仮定します.このSDKは,``show:``メソッドで指定したViewControllerを基底としてモダール表示するとします.また,デリゲートメソッドとして,``hogehogeDismiss``が用意され,SDKで表示したViewControllerがdismissされたときに呼ばれるとします.しかしながら,dismissが完了されたかどうかは用意されてません.
@interface HogeHogeSDK : NSObject
- (void)show:(UIViewController*)viewController;
@end

@protocol HogeHogeSDKDelegate <NSObject>
- (void)hogehogeDismiss;
@end
手順ですが,
1. モーダル表示されたら,そのpresentedViewControllerを別の変数に代入しておく
2. デリゲートを受け取ったら,presentedViewControllerに対して以下の判定を行う
- nilなら,完了メソッドを行う
- nilでなければ,nilになるまで待ったのち完了メソッドを行う
注意点として,presentedViewControllerで取得されるUIViewControllerは非公開のUIViewControllerを継承したクラスである場合が多く,何が表示されているかわからない(逆にクラス名を保持しておけば判定材料になる).モーダル表示はSDKだけはなく,自身のアプリで表示されたり,OSが表示するものもあるので,完了条件はそれぞれの環境に合わせて設定しなければなりません.

@interface ViewController ()

// サードパーティSDK
@property (nonatomic, retain) HogeHogeSDK *hogeHogeSDK;

// モーダルで表示されるViewController
@property (nonatomic, weak) UIViewController *tempViewController;

// モーダルで表示されるViewControllerのクラス
@property (nonatomic, retain) Class tempClass;

// タイマー関数の制御
@property (nonatomic, assign) NSTimeInterval progress;
@property (nonatomic, retain) NSTimer *timer;

@end

@implementation ViewController

#pragma mark - HogeHogeSDK

// サードパーティSDKで何かしらを表示させる
- (void)showHogeHogeSDK
{
    [self.hogeHogeSDK show:self];
    
    // モーダル表示がアニメーション遷移の場合に直後だと取得できないので,ディレイ処理を入れる
    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    dispatch_after(delay, dispatch_get_main_queue(), ^(void){
        self.tempViewController = self.presentedViewController;
        self.tempClass = self.tempViewController.class;
    });

}

// サードパーティSDKの表示が終わったときに行う処理
- (void)compleHogeHogeSDK{
    // 別の画面に遷移など
}

#pragma mark - HogeHogeSDKDelegate

- (void)hogeHogeSDKDismiss:(NSString *)appID
{
	// モーダル表示がまだ残っているなら,タイマー処理を始まる.そうでなければ完了処理へ
    if (self.tempViewController) {
        [self startTimer];
    }else{
        [self compleHogeHogeSDK];
    }
}


#pragma mark モーダルを監視する

- (void)startTimer
{
    [self endTimer];
    self.progress = 0;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
                                                  target:self
                                                selector:@selector(handleTimer:)
                                                userInfo:nil
                                                 repeats:true];
}

- (void)endTimer
{
    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
        self.progress = 0;
    }
}


- (void)handleTimer:(NSTimer*)timer
{
    self.progress += timer.timeInterval;
    
    UIViewController *vc = self.presentedViewController;
    
    if (self.progress > 3) {
    // dismiss後にある一定以上たっても状態が変わらなかったら,強制的に終了させる
        [self endTimer];
        if (self.tempViewController) {
            [self.tempViewController dismissViewControllerAnimated:false
                                                             completion:^{
                                                                 [self compleHogeHogeSDK];
                                                             }];
        }else{
            [self compleHogeHogeSDK];
        }
    }else if (vc == nil
              || self.tempViewController == nil
              || (vc && [vc isKindOfClass:self.tempClass] == false)
              ) {
              /*
              以下のルールのどれかに当てはまれば完了メソッドを呼ぶ
              - 現在のモダールがnil
              - モーダル表示したときに代入したvcがnil
              - モーダルがあるが,SDKで設定したクラスではない
              */
        [self endTimer];
        [self compleHogeHogeSDK];
    }
    
}

@end
今回はこのような方法で問題を回避できましたが,決して良い方法ではありません.内部の現象をある仮定のもとで外部から観測しているだけなので,今回はたまたま上手くいっただけで,SDKの内部処理によっては失敗します.ぜひ,完了メソッドを実装されてないSDK提供者様は実装をお願いします.
posted by Seesaa京都スタッフ at 12:00| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする