iOS11でのクリップボードに文字列を渡す方法 - UIPasteboard

概要

これまでだと、以下の様にする事でクリップボードに文字列を渡すことが出来ました。

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.setValue("渡したい文字列", forPasteboardType: "public.text")

ただ、このやり方はiOS11からは出来なくなります。
実際にこのやり方で実装されたアプリでコピー&ペーストを行おうと、例えばSafariの検索窓とかに貼り付けても何も起きません。

原因と解決策

元々Appleのドキュメントにも、setVaueはObjectをクリップボードに渡すと記載されているので、あるべき姿となった形でしょうか。
(stringオブジェクトを貼り付けたので、何も起きていない。)
試しに、冒頭の

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.setValue("渡したい文字列", forPasteboardType: "public.text")

を、標準のメモ帳に貼り付けると、以下のスクショのようになります。

f:id:project-unknown:20171013001743p:plain

では、解決策ですが、以下のように処理を変更するだけです。

let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.string = "渡したい文字列"

見ての通り、UIPasteboardのstringに値を渡すだけで、これまで同等の機能を実現できます。

さいごに

最後に宣伝となってしまいますが、提供しているKeyHolderでも上記で記載した機能を利用しております。
もし興味があればダウンロード宜しくお願いします

www.project-unknown.jp

Unity C# でメソッド名やクラス名を簡単にログ出力する方法

概要

Unityでは、swift等の他の言語にあるような「FILE」や「LINE」に相当するものはプリプロセッサが無いため、利用できません。
その為、例えば、メソッドがどのタイミングでコールされたかを埋め込むには以下のように記載しなければなりません。

Debug.Log("[" + this.GetType().FullName + "] " + System.Reflection.MethodBase.GetCurrentMethod().Name);

上記の実行結果は、例えば「AdManager」というクラスの、「RequestBanner」メソッドで記載すれば以下の様に出力されます。

[RequestBanner] AdManager

これで、やりたい要件は満たせては居るのですが、毎回このように記載するのは冗長なので、なんとか良いやり方は無いか?が今回のお題です。

MonoBehaviourの基底クラスを作って、StackFrameを使う

基底クラスを作成出来るのであれば、基底クラスにログを仕込んでそれを継承して利用することが出来ます。
後述のstaticと違うところは、わざわざクラス名(もしくは名前空間)を意識しなくても利用できる所です。

public class BaseMonoBehaviour : MonoBehaviour {
    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    protected void LogCurrentMethod(string logMessage = "") {

        // 1つ前のフレームを取得
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(1);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        Debug.Log("*** [" + this.GetType().FullName + "] " + methodName + msg + " ***");
    }
}

上記を継承した、先で以下のように呼ぶだけで要件を満たせるようになります。

LogCurrentMethod();  // クラス名とメソッド名だけで良い場合
LogCurrentMethod("hoge"); // 上記と一緒にメッセージも表示したい場合

※usingディレクティブに敢えて「using System.Diagnostics」を指定していません。
指定してしまうと、このクラスを継承したクラスで「Debug.Log」を記載しようとした際に、「UnityEngine.Debug.Log」と指定しないといけなくなるためです。

上記の出力結果は以下です。

*** [AdManager] RequestBanner()***
*** [AdManager] RequestBanner() / [LogMessage] hoge ***

基底クラスが作れない場合は、static methodから呼ぶ

上述の基底クラスをstaticとして抜き出して実現します。
static化するにあたり、基底クラスと違うところは、クラス名をUnityEngineの

this.GetType().FullName

が使えない為、class名に関してもStackFrameから取得するようにします。
今回はUtilityクラスにstaticなログメソッドを宣言します。

class Utility {
    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    public static void LogCurrentMethod(string logMessage = "") {

        // 1つ前のフレームを取得
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(1);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        // 呼び出し元のクラス名を取得する
        string className = objStackFrame.GetMethod().ReflectedType.FullName;

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        Debug.Log("*** [" + className + "] " + methodName + msg + " ***");
    }
}

利用方法は以下の通りです。

Utility.LogCurrentMethod();

LogメソッドをWarning, Errorにも対応する

更に使い勝手を良くしてみましょう。
これまではInfo系ログだけ用意しましたが、ErrorやWarningも用意します。
上記で作成したメソッドにラッパーメソッドを用意して実現します。
※以下は、staticのケースでコードを載せていますが、基底クラスの場合もやり方は同じです。

public class Utilitys : MonoBehaviour {

    enum LogKind {
        info, warning, error
    }

    /// <summary>
    /// 現在のClass名, Method名をログ出力します。
    /// 引数を付けた場合は、引数の中身を文字列として出力します。
    /// </summary>
    /// <param name="logMessage">ログ出力したい場合は文字列を指定</param>
    public static void LogCurrentMethod(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.info, logMessage);
    }

    public static void LogCurrentMethodWarning(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.warning, logMessage);
    }

    public static void LogCurrentMethodError(string logMessage = "") {
        Utilitys.OutputLogMethod(LogKind.error, logMessage);
    }

    static void OutputLogMethod(LogKind kind, string logMessage = "") {
        // 2つ前のフレームを取得 (ラッパー経由の為2つ遡る)
        System.Diagnostics.StackFrame objStackFrame = new System.Diagnostics.StackFrame(2);
 
        // 呼び出し元のメソッド名を取得する
        string methodName = objStackFrame.GetMethod().Name + "()";

        // 呼び出し元のクラス名を取得する
        string className = objStackFrame.GetMethod().ReflectedType.FullName;

        string msg = "";
        if (logMessage != "") {
            msg = " / [LogMessage] " + logMessage;
        }

        string outputLogString = "*** [" + className + "] " + methodName + msg + " ***";

        switch (kind)
        {
            case LogKind.warning:
                Debug.LogWarning(outputLogString);
            break;
            case LogKind.error:
                Debug.LogError(outputLogString);
                break;
            default:
                Debug.Log(outputLogString);
                break;
        }
    }

}

最後に

2つのやり方を紹介しましたが、全てのクラスがMonoBehaviourを継承出来ない事を考えるとstaticに宣言して利用するのが汎用性が高いかもしれません。

FabricのCrashlyticsのアップデートに失敗した時の回避法

Fabricとは

みなさん使ってます?Fabric. 

Twitter社が、Crashlyticsを吸収しTwitterライブラリの導入支援に加え、クラッシュレポートをグラフィカルに確認できるようにしてくれるツールです。  

f:id:project-unknown:20170930152405p:plain
(Fabricツールのイメージ)

最近では、GoogleのFirebaseのクラッシュレポートが、Fabricを採用することが決まり、更に幅広く使えそうな事が見込めます。  

今日は、そんなFabricのCrashlyticsですが、アップデートしようとして見事に詰まってしまったので、その解決法の紹介です。

 

FabricのツールからCrashlyticsを入れようとするとLinkerエラー

 

先程も記載しましたが、FabricはTwitterライブラリを簡単に導入できるのに加え、Crashlytics の導入支援もしてくれます。

 

現在提供しているKeyholderは、元々FabricのツールからCrashlytics を導入していたので、今回も同様の手順でUpdateしようとしましたが、見出しの通りLinkerエラー。  

 

Fabricツール経由ではWorkspaceは適用しない?

 

いろいろ見ていて気になったのは、Fabricツールから起動しようとすると、XCodeのプロジェクトファイルが自動で立ち上がるところ。  

 

Keyholderは、Cocoapodsを使っている兼ね合い上、Workspaceで管理しています。  

且つ、エラーの発生箇所はKeyholderのプロジェクトファイルというよりは、同一Workspaceで管理している別パッケージで発生してました。  

 

Crashlyticsを、Cocoapods経由でインストール(解決策)

 

一気に解決策ですが、Favricツール経由ではなく、Cocoapods経由でCrashlytics を導入する事で、正常にインストールができました。

source 'https://github.com/CocoaPods/Specs.git'

target 'KeyHolder' do
  use_frameworks!

pod 'Fabric'
pod 'Crashlytics'

end

懸念点

 

元々Fabricツール経由で入れていたものをCocoapods経由にした所。  

 

ライブラリ的には問題ないとは思ってますが、ツール経由からCocoapodsに切り替えた事で古いファイルなどが残らないか気になってるところですね。  

 

現状は問題なく動作しているので、もうちょっとテストしたら本番投入してみます。  

 

もし、何か不具合があれば、この記事更新と解決法を探っていきます。

 

さいごに

 

Fabricは本当に強力なツールです。  

前Twitterでボヤきましたが、KeyholderのSwift化対応中にFabricを更新しないといけなかったのですが、本件で詰まってしまった為に、一度取り外してリリースしています。  

 

そのせいで、今現在Keyholderがクラッシュを引き起こしていても詳細を把握する事が出来ないので非常に不安な状態が続いています。

 

取り急ぎ、Fabricを導入したKeyholderをリリースに向けて着手中です。

Firebaseのルールを設定する - UnityでFirebaseを使ったオンラインランキングシステムを作るvol4

この記事はシリーズ物です。
シリーズの記事は以下を参照ください。

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

はじめに

この記事はFirebase RealtimeDatabaseのルールについて記載します。
ルールについては、Unity以外でも(iOS/Android Native)利用できる所ですので、可能な限りUnity以外でも適用できるように記載します。

本稿は、Firebaseで実際にTwitter認証を用いてデータを登録し、参照出来るようになったのは良いのですが、今のままでは誰でもデータを書き込みできるようになってしまっています。
性善説がまかり通る世の中であれば、特に気にしなくても良いのですが、そうは問屋が卸さないので、適切な権限を付与する必要があります。

Firebase RealtimeDatabaseは、データに対する書き込み権限や、どんなデータを登録できるのか?を「ルール」と言う概念で設定することが出来ます。

ここで紹介するお話は、初見だとかなり「うっ!?」となるかもしれませんが、実際にやってみると直感的で慣れてくると非常に管理しやすいですので、諦めずに頑張りましょう!

今回は、Firebaseのルールを用いてアクセス制限を注目して記載しています。
ルールの機能として、他には値を登録する際のValidate的な使い方もできますが、今回は取り上げません。

ルールを開く

Firebaseのコンソールを開き、プロジェクトを選択して、「Database>ルール」を選択します。

恐らく、デフォルトは以下のようになっているかと思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

上記は、「Firebase Authenticationで認証したユーザなら、誰でもデータの読み書きが出来る」の設定がされている状態です。

ルールの説明に行く前に

ルールとデータは表裏一体と考えても良いです。
ですので、ルールを設定する際は、常にデータ構造を意識しましょう。
ここで説明するルールは、前回Firebaseのデータを登録する際に設定した以下のデータ構造を前提に説明していきます。

root/
  UnitySample/
    Ranking/
      uid/
        name
        id
        score
        updatedate

ルールを設定する

細かく説明してもわかりにくいと思うので、今回やりたいルールを一気に設定しちゃいます。
今回は、以下のルールを設定したいと思います。

  • ランキングなので、他の人のScoreは見れるようにする
  • ユーザのScoreは、ユーザ本人だけが更新できるものなので、ユーザのデータへの書き込み権限は本人しか持たない

これのルールを設定すると、以下の通りになります。(見ての通り、ルールはjsonデータとして作成します)

{
  "rules": {
    "UnitySample": {
      ".read": "auth !== null",
      "Ranking": {
        "$user_id": {
          ".write": "auth.uid === $user_id"
        } 
      }
    }
  }
}

では、上記に出てきたものを細かく説明していきます。

rules

これは、これからルールを設定していくと言う宣言となります。
このrulesに囲われた範囲が、ルールについての定義を行っていることを指し示します。

UnitySample

データ構造にある、「UnitySample」に対する、ルールをこれから設定していく事を指し示します。
このようにルールでは、データ構造と密接した宣言になります。
上記より、UnitySample/Rankingに対するルール設定の場合は以下のように記載します。

"UnitySample": {
  "Ranking": {
    
  }
}

".read": "auth !== null"

.readは、読み取り設定のプロパティを指します。この場合は、UnitySampleの読み取り設定は、右辺の「auth !== null」が設定されます。

auth(auth変数)には、データにアクセスした際のFirebaseの認証情報が入っています。
authには、以下のプロパティがあります。

  • auth.provider
    • 認証情報のプロバイダを取得できます。 ("twitter", "fasebook"など)
  • auth.uid
    • ユーザを一意に示すIDを取得できます。主に、以下のキャプチャの部分を指します。

f:id:project-unknown:20170830001015p:plain

今回の場合、「auth !== null」ですので、auth情報を持っているユーザ。つまり何かしらの形でFirebaseの認証が終わっているユーザについては読み取り権限が付与されます。

親要素と、子要素で違う権限を付与したらどうなるの?

例えば、以下の場合はどうなるのでしょうか?

"UnitySample": {
  ".read": "auth !== null"
  "Ranking": {
    ".read": "auth === null"
  }
}

上記設定は、UnitySample以下は、auth認証を必要とするとしていますが、Ranking以下は不要と言う設定になっています。
このようなルールがコンフリクトする場合は、浅いレイヤーのルールが優先されます
なので、上記のような場合は、Ranking以下でも「auth !== null」が適用されます。

余談 「==」と「===」について

言語によっては、「===」や「!==」に馴染みのない方もいらっしゃるかもしれないので、補足すると、

  • 「==」は良く見る「等価演算子」
  • 「===」は「厳密等価演算子」

と呼ばれます。
等価演算子は、例えば

int val = 0;

if (val == false) {
    echo "hoge";
} else {
    echo "fuga";
}

上記の例の場合、出力結果は「hoge」になります。
このように等価演算子は型が異なっても評価を行うことが出来るのに対し、厳密等価演算子を用いた場合

int val = 0;

if (val === false) {
    echo "hoge";
} else {
     echo "fuga";
}

上記のケースでは、出力結果は「fuga」になります。
これは、「val」と「false」が型が違うので、評価対象にならずelseに流れるからです。
なので、通常の等価演算子よりも型を気にしてより厳密に評価を行うので厳密等価演算子と呼ばれています。

"$user_id":

この$user_idは、$location変数と呼ばれる変数です。別に「$user_id」じゃなくて「$hoge」でも良い、好きな名前を付けれます。
$location変数の意味ですが、その階層にあるNodeのKeyが入ります。
今回のデータ構造で言うと、「ユーザID」がここに入ります。
「ユーザID」はユーザ毎に異なるIDですので、$location変数を使って、その後の評価に使えるようにしています。

".write": "auth.uid === $user_id"

.writeは、.readを見てきたので薄々感づかれると思いますが、書き込み権限を表し、右辺の条件が設定されます。

右辺の、"auth.uid"は、上述説明の通り、ユーザを一意に表すIDが入っているんでしたね。
また、$user_idはユーザID、auth.uidと同等の値が入ります。

なので、この条件では、"auth.uid"と$user_idが一致する要素の場合、.write権限が付与されるようになります。

ルールをシミュレートする

さて、ルールの設定が出来ました。
次はうまくルールが効いているのかをシミュレートします。

Firebaseには、ルールが正常に設定されているかを検証するシミュレータが備わっています。 (以下キャプチャ参照)

f:id:project-unknown:20170830002155p:plain

ここで、実際に読み書きが出来るのかを確認できます。

読み取りをシミュレート

確認するのは、自分含めて、他のユーザのデータを読み込みが出来るか?です。
まずシミュレーションタイプを「読み取り」にします。

f:id:project-unknown:20170929010510p:plain

次に、ルート「/」且つ認証していない状態でアクセス出来るか確認します。
ここでの期待値は、ルートへのアクセスのルールは設定していないのでエラーになる事を期待します。

ロケーションを「/」に、認証済みのチェックを外して実行を押します。

f:id:project-unknown:20170929011519p:plain

画面の上部に「シミュレートされた read が拒否されました」と表示されれば成功です。

f:id:project-unknown:20170929010806p:plain

次に、認証済みにチェックを入れます。
プロバイダを選択する項目があるので、これまでのシリーズでTwitter認証でやってきましたので、Twitterを選択。

UIDの部分はご自身が登録した、Authenticationをコピペしてください。

f:id:project-unknown:20170830002717p:plain

これで実行しましょう。

f:id:project-unknown:20170929010806p:plain

エラーが起きましたね。これで問題ありません。
先程も記載しましたが、読み取りの設定が入っているのは、認証ユーザ且つ、UnitySample以下に対して読み取り出来るようにしているからです。

では、次に読み取り出来るはずの、UnitySampleのシミュレートをしてみましょう。

ロケーションに「/UnitySample」と入力し、実行ボタンを押します。

f:id:project-unknown:20170929011758p:plain

無事シミュレート成功しました。
念のため、自分のデータが見れるかまで見ておきましょうか、
ロケーションを「/UnitySample/Ranking/{$UID}/score」として、実行ボタンを押します。 ({$UID}の部分は登録したUIDを入力してください。)

f:id:project-unknown:20170929011625p:plain

こちらも問題なく読み取りが出来ることが確認できました。

読み取りは想定通り設定出来ているので、書き込みを見てみます。

書き込みをシミュレート

確認するのは、指定されたユーザのデータへ書き込みが出来るか?です。

まず、シミュレーションタイプを「書き込み」にします。

f:id:project-unknown:20170830002437p:plain

次に、ロケーションを、「UnitySample/Ranking/ユーザID」にします。
(ユーザIDはAuthenticationのUIDを記載してください)

データは、デフォルトである、以下で十分だと思います。

{
  "key": "value"
}

プロバイダは、認証済みにチェックを入れ、読み取り同様Twitterとします。

UIDも、先程と同じく、AuthenticationのUIDを記載します。

f:id:project-unknown:20170830002717p:plain

ここまで設定したら実行ボタンをクリックしてください。

「シミュレートされたwriteが許可されました」と表示されれば成功です。

f:id:project-unknown:20170830002824p:plain

また、ちゃんと他のUIDだと弾かれるか確認します。

プロバイダのUIDを適当な文字列に変更します。

f:id:project-unknown:20170830002951p:plain

この状態で、実行を押すとエラーが発生しています。

f:id:project-unknown:20170830003032p:plain

これで、他のユーザへの書き込みは出来ないことまで確認できました。

さいごに

今回の記事は、ただ読むだけだとイメージが湧きにくいかもしれませんが、
実際にご自身で触りながら読んでいただくと、そこまで難しく無いかと思います(冒頭でも記載しましたが、ルールは直感的に設定できるので)
逆に言うと、これだけの設定でそこそこ強固なアクセス制限等を掛けられるので、是非ものにしましょう!

Google AdSenseで広告を表示するまでに躓いた事

はじめに

この度、めでたくGoogle AdSenseが利用出来るようになりました(∩´∀`)∩
実は、このブログにGoogle AdSenseを導入するのは、2年越しの悲願でした。
(とは言っても、殆どの期間は動いて居なかったというのもありますが

今回の記事では、Google AdSenseが使えるようになるまでに、実際に出くわした障害について紹介していきます。

出くわした障害

私が出くわしたのは、珍しいもの(現状のAdSense上だとシステム化されていない為、Google社員の方に実際に対応を行ってもらった)で、
具体的に言うと、私が持っているアカウントが、AdSenseアカウントなんだけど、AdMobだけ利用できるアカウントになっており、
AdSenseでコンテンツ(この場合Webサイト)が扱えるようにアカウントのアップグレードが必要になっていました。

この状態の問題点は、AdSenseアカウントがあり、見かけ上コンテンツアカウントもあるように見えるので、Webサイトを審査する口がありません
んで更にたちが悪いのが、広告ユニットの作成まで出来てしまいます。
ただ、上述の通りAdSenseのコンテンツを扱えるようにはなっていないので、広告ユニットは作成出来ますが、広告が表示されません(広告が配信されていない)。

何故こんなことが起きたのか?

Project UnknownではWebサイトよりも先にアプリの方を仕上げて、アプリ内に広告を出す為にAdMobを登録しておりました。

www.project-unknown.jp

実際にKeyHolderがリリースされたのは、2015年なので、この記事を書いている段階では2年前ですね。

そして、2015年 -> 2017年の間にAdMobがGoogleに吸収されて、AdSenseでAdMobが取り扱えるようになりました。
この際に、AdMobアカウントを持っている人は、AdSenseアカウントを自動的に付与されました。
このアカウントが特殊で、AdSenseアカウントは持っているけど、AdMobしか取扱えない(しかも見かけ上コンテンツも取り扱えるようになっている)状態となります。

対応策

実質、この状態になったら、こちらでは何もすることが出来ません。
問い合わせが多いのかわかりませんが、Google社に直接尋ねる口すらありません。
(AdSense管理画面にフィードバック画面はありますが、あくまでbugfixを送るものなので解答は期待できません)

ではどうするか?

ユーザ同士が助け合うフォーラムがあるので、そちらで起票するしかありません。
このフォーラムの中で、Google社員の方と繋いでくれる人が現れるのを待ち、繋いでもらう形となります。

実際に赤裸々ですが、やり取りしたフォーラムです。

結果として、対応と同時に審査も行って戴いてみたいで、審査も無事通過しました。
(この問題の後に審査が控えていると思って身構えていたので拍子抜けでしたが…(;・∀・)

ここまでがイレギュラーな障害で、お次は他のサイト様でも記載されている、私が行ったAdSense対策です。

AdSense審査対策

他のサイト様を見ていると本当に色んな手を記載していたのですが、、、あまりに忙しくて実施することが出来ませんでした。
やったのは、8月中旬から9月上旬までほぼ毎日、ボリューミーな記事を投稿し続けた事ですかね。
ただ、実際に審査が行われたであろう日は、9月中旬なので、審査中にリアルタイムに更新し続けれては居ませんでした。

後は、画像を多様しちゃ駄目と言う記事もよく見かけたのですが、他の記事を見ても分かる通り、このブログでは画像をふんだんに使ってます
技術系の話題をメインに取り扱っているので、画像がなかったらどうしても内容が薄い記事になってしまうので…。

後気をつけた事としたら、最近の記事は、私が躓いた事や時間が掛かった事を紹介して、ブログ閲覧してくれる人に同じ轍を踏んでほしくない思いから、自分で言うのも変ですが、かなり丁寧に書いたくらいですかね(と言うか、記事自体が後で私が見た時に躓かないようのメモ代わりにもしているので詳細に書いておきたいんですよね)

なので、纏めると
他の人が役に立ちそうな記事を丁寧に書く。
が私が取った対策なのかもしれません。

はてなでAdSenseを載せるには

まず、はてなブログProアカウントにならないといけません。
Proじゃないと、はてなが設定する広告が勝手に出てしまうので、AdSenseの設定が出来ません。
(最近独ドメ取ったのは、Proにした勢いです…)

逆に言うと、はてぶProになればAdSenseの準備が整ったと考えても良いかもですね。

さいごに

AdSenseが取り扱える嬉しさから記事を書きたくて書いてきたのですが、眠気が限界でちょっと端折りが多い記事で申し訳ありませんmm
オーダーがあればもうちょっと細かい記事を書きたいと思います。

本当に書いた怖いソース(swift編) 強制ダウンキャスト as!

はじめに

どうもどうまずです。

本当に書いちゃった怖いソースをお見せしようと思います。 特に今回はクラッシュするレベルのコードです。

強制ダウンキャスト

まずはこちらのソースをご覧頂きたい。 (ちなみに、コンパイルエラーにはなりません。)

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  return collectionView.dequeueReusableCell(withReuseIdentifier:
      DetailCellIdentifier.memo.rawValue, for: indexPath)
      as! DetailMemoCell
}




おわかり頂けただろうか?
一見なんの変哲もない、collectionViewのセルの内容を返しているだけのソースに見えます。
しかし、「as!」と記載しています。
そう、つまりこれ、
強制ダウンキャストしているんです!
万が一、ダウンキャストが失敗した場合、
アプリが落ちます!

なんとも恐ろしいソースではないでしょうか!

collectionViewやtableViewのサンプルとかみると、結構高い確率で強制ダウンキャストしているので、私も右に倣えでやってしまいましたが、要注意しないといけませんね。 (サンプルを記載している方は、理解しやすいように余計な処理書いていないだけだと思います。)

テスト中ならまだしも、リリースしてからクラッシュはどうしても避けたいところですよね。

修正方法

nilが入らないようにするのは大前提ですが、想定外のことが起きるのは世の常。 万が一nilが入ってしまった場合に、落ちないようにしましょう。

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

  if let memoCell = collectionView.dequeueReusableCell(withReuseIdentifier: DetailCellIdentifier.memo.rawValue, for: indexPath) as? DetailMemoCell {
      return memoCell
  } else {
      return UICollectionViewCell()
  }
}

重要なのは

if let memoCell = collectionView.dequeueReusableCell(withReuseIdentifier: DetailCellIdentifier.memo.rawValue, for: indexPath) as? DetailMemoCell

です。 右辺の結果がnilでなければ、左辺に代入してネストした処理が実行されます。 この例では更に「as!」を「as?」に変更することでダウンキャストを行えるかも一緒にチェック(ダウンキャスト出来なければ nilが返るから、elseに入る)をおこなっています。

これで万が一の事態でもアプリが落ちるようなことはありませんね。

asっておまじないの一種と思ってた・・・

類似事例:強制アンラップ

似たような事象で強制アンラップも怖いです。
簡単な事例を一つ。

var test:String?
test = test2
print(test!)

test2という変数に何が入っているかわかないですが、仮にtest2がnilだった場合、「print(test!)」でクラッシュです。

修正方法

指摘されたのは、「強制アンラップを利用する時は絶対nilではないという自信がある場合のみ」です。 理由は下手するとクラッシュする恐怖があるためです。

そのため、アンラップする必要がある場合、強制ダウンキャストと同じく、「if let」を利用しましょう!

        if let test = test2 {
            print(test)
        } else {
            print("nilだよ")
        }

test2がnilでなければ左辺に代入され、ネスト内の処理が行われます。 ちなみに、testはアンラップされた形式になっています。

つまり
・オプショナルに慣れるまでは強制ダウンキャストや強制アンラップは避けましょう
・どうしても使う場合は、「if let」でnilか調査してから使いましょう

この2点を注意しましょう。

…いや、注意します。

注意

レビューアの観点によって、諸説あります。


リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

キーホルダーを作ってみました

ただの自己満記事ですが...

タイトルの通り、キーホルダーを作ってみました!!
アプリの方のキーホルダーではなくて、

f:id:project-unknown:20170827025326p:plain

これを

f:id:project-unknown:20170913000424j:plain

こう(ててーん

Pixivに自分の絵をグッズにしてくれるサービスがあると知り、今回利用させてもらった次第です。

factory.pixiv.net

なんだか自分達のキャラが物理的に世の中に生まれると言うのは、とても感慨深いですね。
はやくう〜のんのゲームを世の中に出せるよう頑張ります!

Unity Cloud Buildでエラーになった際の解決法 vol3

はじめに

今回の記事で三回目になりますが、Unity Cloud Buildで盛大に詰まった(1週間程このせいで開発が止まった)際の解決方法を記します。

本記事は、UnityにFirebaseとTwitter Kit for Unity, Admobを導入した際に出くわした内容ですので、記事で出て来るエラー内容はこれに付随しています。

が、対応方法を見ていただくと分かる通り、この例以外でも役に立つ方法ですので、同様のエラーに苛まれた際のお役に立てればと思います。

エラーとその解決方法

ファイル名に「error」が付いているとエラーに見える

事象

具体的には、以下のようなログです。

3: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkit.framework/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@2x.png 
4: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkit.framework/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@3x.png 
5: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@2x.png 
6: [Unity] *** {GUID} replaces {GUID} at path assets/plugins/ios/twitterkitresources.bundle/twttr-icn-tweet-place-holder-photo-error@3x.png 

実際に画面上だと、本当にエラーのように見えます。

f:id:project-unknown:20170907012001p:plain

原因と解決策

これはUnity Cloud Buildが失敗しているのと何ら関係ありません
ログのサマリが「error」という文字に反応して色味を付けているだけです。
私自身、これを疑い該当ファイルを消してbuildしてみたりしたのですが、上述通りbuildが失敗するのと関係ないので勿論コケます。

これについては、forumでも中の人が関係ないと断言しています

なので、解決策は何もする必要が無いです。

Google産SDKで必要な設定ファイルが無い

事象
 [Unity] Player export failed. Reason: No GoogleService-Info.plist files found in your project.
原因と解決策

これはCloud Buildじゃなくて実機テストでも発生するものです。
タイトル通り、必要な設定ファイルがありません。
上記エラーが出た際は、Firebaseで発行するGoogleService-Info.plistファイルをUnityに突っ込み忘れたのが原因です。

なので、解決策は、FirebaseやAdMob等Google産SDKの場合の設定ファイルをちゃんとUnityに入れる事です。

shader snippetでエラー

事象
[Unity] Shader compiler: internal error compiling shader snippet type=0 platform=5: Protocol error - failed to read correct magic number
原因と解決策

OnGUI等、古いAPIを使っていると発生し易いみたいです。
これについては、再度Buildを掛けると直ります。
(何度かお目にかかっているのですが、直接的な発生原因は不明、OnGUIを使った実装を避けるくらいですかね

Twitter KitでLogErrorが出ている。

事象
[Unity] TwitterKit.Internal.Utils:LogError(String) (at Assets/Twitter/Scripts/Utils.cs:33)
原因と解決策

ポカミスが原因

私が出くわしたTeitter Kit for Unity限定かもしれませんが、サマリログだとこの部分しかログが落ちていません。
この場合は、実際にSDKの中を読み込んでこのログが吐かれるきっかけを見たほうが早いです。

以下VSCodeの該当箇所を見た結果、(参照されている所が直ぐ追えるのが本当に便利…)

f:id:project-unknown:20170907013414p:plain

ここで分かるのは、Twitter Kit for Unityの設定にAccessTokenを仕込んでいない事が伺えます。
実際にProjectを作り直した直後で、たしかに仕込んで居なかったので、AccessToken等を埋め込むことで、このエラーは出なくなりました。

Bundle IDでエラー

事象
[Unity] Player export failed. Reason: Project Bundle ID {Bundle ID} does not match any bundle IDs in your GoogleServices-Info.plist files
原因と解決策

ポカミスが原因

これは、Firebaseで設定した際のBundile ID(アプリのID)と、Unity Cloud Buildに設定したBundle IDに差異がある時に発生するエラーです。
IDを揃えることで発生しなくなります。 (ポカミス多い…)

iOSのバージョンがサポートしていない

事象
73:        [xcode] /BUILD_PATH/{BUILD_PATH}/temp.44tpIu/Frameworks/Plugins/iOS/TwitterCore.framework/Headers/TwitterCore.h:24:2: error: "TwitterCore doesn't support iOS 7.x and lower. Please, change your minimum deployment target to iOS 8.0"
74:        [xcode] #error "TwitterCore doesn't support iOS 7.x and lower. Please, change your minimum deployment target to iOS 8.0"
75:        [xcode] /BUILD_PATH/{BUILD_PATH}/temp.44tpIu/Frameworks/Plugins/iOS/TwitterKit.framework/Headers/TwitterKit.h:15:2: error: "TwitterKit doesn't support iOS 8.x and lower. Please, change your minimum deployment target to iOS 9.0"
76:        [xcode] #error "TwitterKit doesn't support iOS 8.x and lower. Please, change your minimum deployment target to iOS 9.0"
原因と解決策

エラーの内容通り、TwitterKitがiOS9以上のAPIを使っているため、UnityのProject設定をiOS9以上にしないといけません。
このエラーを引き起こした際に、UnityのProject設定のiOS SDKの最低Versionを7.0にしていたのが原因でした。
こちら、最低Versionを9.0にすることでかいけつ出来ます。

linker command faild

事象
[xcode] clang: error: linker command failed with exit code 1 (use -v to see invocation)
原因と解決策

iOSでアプリ開発している人ならお馴染みで、たまにドツボに入るエラーです。
この手のエラーは利用しているライブラリのPathが足りていなかったりする時に発生し易いのですが、確実にこれじゃないのが辛い所です。
ましてや、Unity Cliud Buildは手元のXCodeじゃないので尚更調査し辛い所があります。

この手のエラーは汎用的なエラーであるために、このエラーを見ただけだと何が原因か推測し辛いので、Buildログを見て、何処らへんでエラーが発生したのかを見てアタリを付けていきます。

サマリログの一番したに詳細のログを見るボタンがあるので、これをクリックして中身を見てみます。

f:id:project-unknown:20170907013201p:plain

遷移先の詳細ログで、該当する箇所を検索します。

22370:        [xcode] ld: warning: directory not found for option '-L /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/GTMSessionFetcher /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/GoogleToolboxForMac /BUILD_PATH/Library/Developer/Xcode/DerivedData/Unity-iPhone-gnshgjhnidtrmtbkltwpchzfnjbj/Build/Intermediates/ArchiveIntermediates/Unity-iPhone/BuildProductsPath/Release-iphoneos/nanopb'
22371:        [xcode] ld: warning: directory not found for option '-F /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseAnalytics/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseAuth/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseCore/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/FirebaseInstanceID/Frameworks /BUILD_PATH/{BUILD_PATH}/temp.pDVpf7/Pods/Google-Mobile-Ads-SDK/Frameworks/frameworks'
22372:        [xcode] ld: library not found for -lz}
22373:        [xcode] clang: error: linker command failed with exit code 1 (use -v to see invocation)

どうやら、Firebase SDKがCocoaPodsを経由して「Firebase/Core」等を入れようとしたり、AdMobが「Google-Mobile-Ads-SDK」をCocoaPodsを用いて導入しようとしているのですが、それがうまく言っていないのか、FirebaseやAdMobが必要としているライブラリのリンクが貼れなくてエラーになっているみたいです。

ここでCocoaPodsについて軽く説明すると、
CocoaPodsはiOSエンジニアだと、結構当たり前に使うツールで、簡単に言うとOSSのパッケージ管理ツールです。
通常だと、OSSなどはGitや配布されているファイルを自分でDLしてXCodeに突っ込む等の作業が必要なのですが、CocoaPodsはそこら辺のOSSのinstallをサポートしてくれます。
また、OSSのVersion管理もしてくれて、OSSのVersionが上がったらそのinstall支援もしてくれる優れものですし、そのOSSが動くために必要な為の他のOSSも、ツールの作成者が登録してくれていれば自動でinstallしてくれます。私とかは無いと生きていけません。
FirebaseやAdMobもその恩恵にあやかってCocoaPodsを採用しているのでしょうね。

さて、本題に戻します。
ココらへんを見る限り、現在Unity Cloud BuildはCocoaPods経由でライブラリは入れられないっぽいです。
AdMobやFirebaseのSDKをCocoaPods経由でも入れられるようにupdateするみたいですね。
なので、現状SDKがCocoaPodsからライブラリを入れようとする所を止めて、手動でライブラリを入れる必要があります。

まず、CocoaPodsを使おうとしているコードを特定します。
私の場合は、FirebaseとAdMobが該当するので、以下、ファイルと修正内容を記します。基本は該当する所全てをコメントアウトします。(コードは編集後のコードを載せてます)

AdMobDependencies.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Google-Mobile-Ads-SDK" },
        //     namedArgs: new Dictionary<string, object>() {
        //         { "version", "7.13+" }
        //     });
#endif  // UNITY_IOS

AppDeps.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Firebase/Core" }, 
        //     new Dictionary<string, object>() { 
        //         { "version", "4.1.0" },
        //         { "minTargetSdk", null },
        //         { "sources", null }
        //     });
#endif

AuthDeps.cs

#elif UNITY_IOS
        // Type iosResolver = Google.VersionHandler.FindClass(
        //     "Google.IOSResolver", "Google.IOSResolver");
        // if (iosResolver == null) {
        //     return;
        // }
        // Google.VersionHandler.InvokeStaticMethod(
        //     iosResolver, "AddPod",
        //     new object[] { "Firebase/Auth" }, 
        //     new Dictionary<string, object>() { 
        //         { "version", "4.1.0" },
        //         { "minTargetSdk", null },
        //         { "sources", null }
        //     });
#endif

次に必要となるライブラリを入れます。
何が必要になってくるのかは、コードから判断します。
例えば、AppDepsを例に取ると、

new object[] { "Firebase/Core" }, 

この部分がCocoaPodsから入れたいライブラリ名となっています。
上記などを参考に必要なファイルを入れます。
この例の場合は、以下からライブラリを入手します。

Firebaseのセットアップのサイトの、CocoaPodsを使用せずに結合する項目から、「フレームワーク SDK の zip ファイルをダウンロードします」のリンクをクリックして、zipファイルをダウンロード。

f:id:project-unknown:20170907020639p:plain

ダウンロードしたzipファイルを解凍し、必要なFrameworkを探し出します。
まず上記のFirebase/Coreに当たるものを探すと、「Analytics / FirebaseCore.framework」が見つかります

f:id:project-unknown:20170907020841p:plain

このFirebaseCore.frameworkを、Unityの「Plugin/iOS」以下に突っ込みます。

また、今回私の場合、Firebaseの以下の機能を使おうとしています。

  • Auth(Authentication)
  • Database(RealtimeDatabase)

なので、このライブラリも探します。

f:id:project-unknown:20170907021029p:plain

f:id:project-unknown:20170907021043p:plain

上記が見つかりました。
キャプチャに出ている、以下のライブラリは全てUnityの「Plugin/iOS」以下に突っ込んでください。

  • FirebaseAuth.framework
  • GTMSessionFetcher.framework
  • FirebaseDatabase.framework
  • leveldb-library.framework

コメントアウトしたコードには、Authとかしか無いじゃん!とお思いかもしれませんが、上述の通り、CocoaPodsはinstallしようとするライブラリが必要なライブラリを勝手にinstallしてくれます。なので、FirebaseAuth.frameworkをCocoaPods経由でinstallしようとしたら、GTMSessionFetche.frameworkはCocoaPods側で勝手にinstallしてくれます。
今回は手動でライブラリを入れようとしているので、上記4種類のライブラリを入れる必要があります。
(Firebaseの場合は、Core以外、どういつディレクトリ内のライブラリが依存関係にあるように配置されているので非常にわかりやすくなっています。)

後は、AdMobのライブラリを突っ込む作業です。

FirebaseのライブラリからAdMobを入れた場合はzipファイルの中のAdMobフォルダ内に該当するライブラリがあるのでそれを突っ込めば良いのですが、私の場合は別途AdMobのサイトからUnity用SDKを導入しているので、念のためそちらのファイルからライブラリを取得します。
落としてきたzipに「GoogleMobileAdsSDK.framework」があるので、それをこれまでと同様に「Plugin/iOS/」以下に突っ込みます。

最終的に私のPlugin/iOS以下は下のキャプチャの様になりました。

f:id:project-unknown:20170907022125p:plain

まだここで終わりではありません

GoogleなどのSDKは、Objective-Cの「@import」を利用しています。
これについて詳細に記載すると、別途記事を起こすだけの分量になるのでかいつまんで説明すると、C#等でコードを書く時に

using UnityEngine;

とか書くと思うのですが、毎回これを書くのが冗長な為、「@import」で設定されたフレームワークは自動読み込みされるようにでき、このお陰で毎回必要なフレームワークをプロジェクトの設定でimportしなくても済む機能です。

この「@import」機能ですが、古いXCodeだとデフォルト設定で利用できたのですが、、最新のXCodeだとデフォルトではOFFになっています。

これまで投入してきたライブラリは「@import」の機能を利用しているため、これを使えるようにしてあげる必要があります。

やりかたは、Assets/Editor以下のどこでも構いませんので、「XcodePostProcessBuild.cs」のファイルを使って以下のコードを記載してください。

using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;

public static class XcodePostProcessBuild {
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string path) {
        if (target != BuildTarget.iOS) {
            return;
        }

        var xcodeProject = new PBXProject();
        xcodeProject.ReadFromFile(PBXProject.GetPBXProjectPath(path));

        var projectGuid = xcodeProject.TargetGuidByName(PBXProject.GetUnityTargetName());
        xcodeProject.SetBuildProperty(projectGuid, "CLANG_ENABLE_MODULES", "YES");

        xcodeProject.WriteToFile(PBXProject.GetPBXProjectPath(path));
    }
}

これで、先程記載した@import機能が使えるようになります。

長くなったので纏めると

  • この手のエラーはログの詳細を読み込んで、前後のエラーでアタリを付ける
  • 今回の場合の対応は
    • CocoaPodsがやってくれる作業を手動でやる
      • CocoaPods経由でinstallしようとしているライブラリのコードを消す
      • 必要なライブラリを自分で入れる
      • XCodeの設定を自前で設定する

さいごに

Unity Cloud Buildは非常に便利な反面、一度詰まってしまうと凄い時間がかかります。
ただ、それを鑑みても恩恵がでかすぎるので是非とも利用していきたい所。
私と同様のエラーで躓いてしまうのは非常にもったいないので、同様の所で躓いてしまった場合はその助けになれば幸いです。

ちなみに、私は最終的にUnity Cloud Buildのbuildを通すまでに冒頭で申し上げた通り1週間位かかっています。

以下実行履歴 (途中Projectを作り直しているので、20回以上はbuild挑戦しています。無料プランなので1buildが終わったら1時間のクールタイムが発生するので、単純計算でも20時間はかかってます(;´Д`))

f:id:project-unknown:20170907023645p:plain


番外編

実機でTwitterのSession周りでエラーが起きている

現象
(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

NullReferenceException: A null value was found where an object instance was required.
  at TwitterKit.Unity.Twitter.get_Session () [0x00000] in <filename unknown>:0 
 
(Filename: currently not available on il2cpp Line: -1)
原因と解決策

Twitter InitのScriptをアタッチしていない。
これ本当に忘れやすいので注意です。

過去記事

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

FirebaseとAdmobをUnity上に共存させて盛大に詰まった時の対処法

はじめに

標題の通りです。Firebaseでシステムを作ったのは良いけど、AdMobの設定が終わっているUnityのProjectに持っていったら盛大にエラーが出まくって詰みまくった時にどう解決したか、をレポートしていきます。

AdMobについては至る所で説明されているので割愛させていただきます。
Firebaseについては、本ブログで色々と記事を記載していますので、そちらを御覧ください。

www.project-unknown.jp

事象

AdMobとFirebaseをUnity上で同居させると、Editorエラーに始まりスクリプトエラーやらマニュフェストがどうたらとか、、、もちろんゲームを実行させることも出来ません。
(あまりのエラーの多さにキャプチャやらログを控えるのを諦めてしまった(;・∀・)

原因

心をポッキリ折ってくれた詰みポイントを交えて、原因を紹介します。

エラーの内容が多すぎ(詰みポイント1)たのですが、私が解決した際の助けになったエラーログが以下です。

TypeLoadException: Could not load type 'GooglePlayServices.UnityCompat' from assembly 'Firebase.Editor'. 

↓を見る限りだとどうもPlayServicesResolverを常に最新にしないと行けないとなっています。

github.com

PlayServicesResolverはGoogleから提供されるPackageに大体梱包されています。

が、どれが最新かわかりません。(詰みポイント2)

また、PlayServicesResolverはUnityの不具合なのかわかりませんが、Project Viewを綺麗にしようとして、別フォルダ等に移動していたら、同一ファイルと認識しません(詰みポイント3)

と言うか、Firebaseから入ってくるPlayServicesResolverはファイル名にバージョンが示されており、またAdMobから入ってくるPlayServicesResolverはファイル名にバージョンが入って居ないため、やっぱりどれが最新かわかりません(詰みポイント4)

解決策 (2017/9/3時点)

FirebaseとAdMobの共存で不具合が起きた場合は大体がPlayServicesResolverに行き着くと思います。
対応方針は、最新のPlayServicesResolverにすることです。

2017/9/3時点では、Firebaseから導入するPlayServicesResolverの方が最新でしたので、こちらを採用しました。(と言うか2つとも試して、エラーにならない方を採用した)

なので、現状私の構成は以下の通りです

f:id:project-unknown:20170903211533p:plain

参考

blog.naichilab.com

本当に書いた、怖いソース (swift編) viewDidLoadの肥大化 Initialization Closure

はじめに

どうも、どうまずです。
本当に書いた、汚いソース第2弾です。

今回はどんな怖いソースが出てくるのか・・・・

viewDidLoad

まずはこちらのソースをご覧頂きたい。

override func viewDidLoad() {
    super.viewDidLoad()
    
    mainCollectionView.delegate   = self
    mainCollectionView.dataSource = self

    // xibの読み込み
    self.mainCollectionView.register(UINib(nibName: MainNibName.title.rawValue, ・・・
    //以下略




おわかり頂けただろうか・・・
一見なんの変哲もないviewDidLoadですが、

書きすぎなんです

サンプルなどでも、delegateやdataSourceはviewDidLoadに記載されていることが多いのですが、本格的なアプリ開発になればなるほど、このviewDidLoadが長くなるんです。

そのため、ViewDidLoadに記載しなければならない処理は極力外だし・メソッド化して、読みやすくしとくようにしましょう。

修正後のソースは下記の通りです。

override func viewDidLoad() {
    super.viewDidLoad()
    
    collectionViewInitialize()
    
    // xibの読み込み
    self.mainCollectionView.register(UINib(nibName: MainNibName.title.rawValue, ・・・
    // 中略
}

func collectionViewInitialize() {
            
    mainCollectionView.delegate   = self
    mainCollectionView.dataSource = self

}

今回はちょっと過剰かもしれませんが、 viewDidLoadを簡素化しておくという発想が大切です。

さらに初期化でいろいろ記載することがあるようならば、 「Initialization Closure」という方法もあるようです。

参考サイト

qiita.com

例えば、下記のように単純なラベルを表示するコードがあるとします。

let testLabel = UILabel()
override func viewDidLoad() {
    super.viewDidLoad()
    
    testLabel.text      = "どうまず"
    testLabel.textColor = UIColor.blue
    testLabel.frame     = CGRect(x: 0, y: 0, width: 100, height: 50)
    
    view.addSubview(testLabel)
}

これもviewDidLoadの簡素化の観点からどうにかしたいソースです。
これを下記のように、クロージャで初期化しちゃいます。

let testLabel = {() -> UILabel in
    let label       = UILabel()
    label.text      = "どうまず"
    label.textColor = UIColor.blue
    label.frame     = CGRect(x: 0, y: 0, width: 100, height: 50)
    return label
}

override func viewDidLoad() {
    super.viewDidLoad()
        
    view.addSubview(testLabel())        
}

これでviewDidLoadには必要最低限の記載だけになりましたね。
私はaddSubViewを殆ど使ったことなかったので、こんなやり方もあるんだとちょっと驚きました。
ダイエット方法はいろいろありますね。頑張って痩せなきゃ・・・

注意

レビューアの観点によって、諸説あります。


リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

HomebrewがMacのVerupで動作しなくなった時にやったこと

f:id:project-unknown:20170903131826p:plain

はじめに

MacをEl Capitanにした時も発生したのですが、Sierraでも発生してこれまでとちょっと対策が違ったので記事にします。

PlantUMLをbrewから入れようとしたのですが、以下のエラーが発生しました。

/usr/local/bin/brew: bad interpreter: /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin: no such file or directory

ちなみに当たり前なのですが、以下のコマンドみたいにversionの確認すらできません。

brew --version  

検索してみても、似たような所で上記エラーと一緒にRubyの方でもエラーが発生している記事ばかりで軽く詰み掛けたのですが、色々と試行錯誤してみます。

brewのRubyのPathを変更する

とりあえずエラーに出ているrubyのPathが1.8と明らかに古かったので、そこを直してみます。

$ vim /usr/local/bin/brew

brefore

#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0

after

#!/System/Library/Frameworks/Ruby.framework/Versions/current/usr/bin/ruby -W0

結果

以下な感じでエラーが別な所に伝搬しました。

/Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require': /usr/local/Library/Homebrew/extend/pathname.rb:185: invalid multibyte escape: /^\037\213/ (SyntaxError)

brewを最新の状態にする

もう

$ brew update

上記すら効かない状態だったので、gitから最新の状態にする (以下、実際はadmin権限でやってます)

$ cd /usr/local/Homebrew
$ git fetch origin

これすらエラーになったらどうしようと不安になったのですが、これは問題なく動作しました。

この状態で再度以下のコマンドを叩きます

$ brew update
Error: /usr/local must be writable!

えー(;・∀・)

ちょっと/usr/local周りの権限を弄るのは怖いですが、どうやらよくあるエラーっぽくて、権限を変更してupdateするのが定石みたいなので、変更して対応します。

$ sudo chown -R $(whoami):admin /usr/local
$ brew update

これでうまく通りました。実行後に、

Homebrew no longer needs to have ownership of /usr/local. If you wish you can
return /usr/local to its default ownership with:
  sudo chown root:wheel /usr/local

こんな配慮されているということは公式的にも問題ない対応みたいですね。

流石に怖いので権限を元に戻しておきます。

$ sudo chown root:wheel /usr/local

最後に冒頭で失敗したversion確認。

$ brew --version  

Homebrew >1.2.0 (no git repository)
Homebrew/homebrew-core (git revision 4572; last commit 2017-09-02)

問題なく通りましたヽ(=´▽`=)ノ

UnityでFirebaseのRealtimeDatabaseとデータのやり取りをする - UnityでFirebaseを使ったオンラインランキングシステムを作るvol3

この記事はシリーズ物です。
シリーズの記事は以下を参照ください。
特に本稿は、以下の2つの記事を読み終えている事前提で記載していますのでご注意ください。

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

https://assetstore.unityhttps://assetstore.unity.com/packages/3d/environments/fantasy/alchemist-s-house-interior-47318.com/packages/3d/environments/fantasy/alchemist-s-house-interior-47318

はじめに

今回は、FirebaseのRealtimeDatabaseとのデータのやり取りを行います。
これまで、Twitter認証・Firebase認証とやってきて、やっとランキングのデータのやり取りを行います。
試行錯誤した結果を記載していますので、もし手順などの漏れで動かない等御座いましたら、@YuwUnknownまでご連絡ください。

注意

アクセス制限について

今回の記事では、Debug・実装のやりやすさから、FirebaseのRealtimeDatabaseに対しての読み書きの制限を行いません。
実際に運用する際には、セキュリティの観点からも、読み書きの制限を行うことを強くお勧めします
また、読み書きの制限については、次の回で説明する予定です。

データ通信のやり取りについて

今回の記事は、Firebaseの無料の範囲内で記載しています。
無料で出来る範囲は、公式の料金表に詳細が記載されています。

料金表に載っていない突っ込んだ情報は、以下の記事で実際にヒアリングした結果をレポートしていますので、ご確認ください。

www.project-unknown.jp

今回のランキングシステムは、データ量もさほど多くないですし、スコアを見る時だけ接続する等を実施すれば無料の範囲内で十分に使えるはずです。
またFirebaseのRealtimeDatabaseはローカルにキャッシュし、データ更新があれば実際にデータのやり取りを行う優れた機能ですので、そこまで気にする必要は無いかもしれません。

本稿の構成

以下で進めます。

  • FirebaseのRealtimeDatabaseにスタブデータ(自前で用意したデータ)を登録して、Unityから参照できるようにする
  • UnityからRealtimeDatabaseに書き込みをする

では行きます。

FirebaseのRealtimeDatabaseにスタブデータ(自前で用意したデータ)を登録して、Unityから参照できるようにする

Firebaseコンソールにスタブデータを登録し、ルールを編集して簡単にアクセスできるようにする

FirebaseのRealtimeDatabaseを見てみる

まずは、実際にランキングデータを格納することになる、FirebaseのRealtimeDatabaseを見てみます。
Firebase Consoleのプロジェクトに移動して、以下のキャプチャが示す所を見てください。

f:id:project-unknown:20170830222145p:plain

これが実際にデータを格納する所です。最初のデータが無い状態だとrootを指し示すものだけが表示されていると思います。
今開いているのが、データを弄るエディタにもなっているので、実際にデータを手入力で入れてみます。

RealtimeDatabaseにスタブデータを登録する

rootノードをマウスオーバーすると、「+」が出て来るのでクリックします。

f:id:project-unknown:20170831230219p:plain

すると、子要素を入れれるようになるので、ここにデータを追加していきます。

f:id:project-unknown:20170831230304p:plain

ここでは、子要素を更に入れ子にしたいので、「名前」のところだけ入力して、更に「+」ボタンを押します。

f:id:project-unknown:20170831230433p:plain

最終的に、以下のように入力していきます。

f:id:project-unknown:20170831230734p:plain

それぞれの要素の説明は以下です。

  • UnitySample
    • Firebaseでは複数のProjectをぶら下げる事が出来ますし、見て分かるように階層ごとにデータの意味合いをもたらして管理することも出来ます。ここではUnitySampleという名前のProjectという意味での識別子としています
  • Ranking
    • Rankingデータをこの要素の以下にぶら下げるように作っています
  • uid
    • ユーザを一意に特定する為のものです。TwitterのIDでも良いのですが、ここではFirebaseのAuthenticationが発行するuidを入れる用途にしています。
    • 上述の通り、このuidはユーザ毎のrankingデータを格納する為に用意しています
  • name
    • ユーザ名です。Twitterから取得します
  • id
    • ユーザIDです。Twitter IDを入れます
  • score
    • ユーザのScoreです。
  • updatedate
    • データの更新日を入れておきます。

これで、スタブデータを入れることが出来ました。
ここまでデータを入れてきておわかりの通り、FirebaseのRealtimeDatabaseはJSONライクのNoSQLっぽいデータの取扱となります。
SQL周りを知らなくてもなんとかなると言うのが気軽に使えて便利ですね。
ただ、その分DBMSでよく使うリレーションシップ周りのデータ管理をしようと思うと、自力でデータ構造を設計してあーだこーだ考えないといけなくなるので、そこはデメリットになるかも…。

RealtimeDatabaseのルールを編集する。

冒頭で記載しました通り、Firebaseへのアクセスに制限を加えることができます。
デフォルトでは、Authenticationの認証が終わっていないとアクセスする事ができないので、今回の説明を行う上でちょっと不便となるので、アクセス制限を開放します。
ルールについては、次回に詳細を書く予定ですので、本番リリースする際は、確実に適切なルール設定をお願いします
というのと、アクセス制限を開放するのは、テスト時であっても危険ですので、出来ることなら、開発が一段落したらルールを設定して制限するなど自衛を行うように出来ると良いですね。

まず、Database > ルールでルール画面を開きます。

f:id:project-unknown:20170831232437p:plain

恐らくデフォルトでは、以下が設定されていると思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

この状態だと、Firebaseの認証しているユーザのみ読み書きが出来る状態となっています。
これを以下に置き換えてください

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

これで、全てのユーザが読み書き出来るようになります。

UnityからFirebaseのRealtimeDatabaseとデータのやり取りを行う

UnityからFirebaseのRealtimeDatabaseのデータを参照する

前提として、FirebaseSDKがimportされている必要があります。
まだの方はVol2の記事に導入の仕方を載せてますので、importをお願いします。

さぁいよいよRealtimeDatabaseとUnityとを接続させます!

ここでの例は、Firebase Realtime Databaseにあるscoreが一番高いデータを取得する。を目標に実装を行います。

UnityからFirebaseへ接続する設定は、ここまでで全部終わっているので、コードをゴリゴリ書くだけです。

説明は後ほど記載しますので、まずは完成形のコードを記載します。

前回のFirebaseへTwitterアカウントでログインする際のコードを拡張します。

gist.github.com

では、以下に今回追加分になったコードの説明を記載していきます。

コードの詳細

フィールド

フィールドには、以下を追加しています。

// Firebase
private DatabaseReference _FirebaseDB;
private Firebase.Auth.FirebaseUser _FirebaseUser;
  • _FirebaseDB
    • Firebase RealtimeDatabaseへ参照を持ったObjectを保持します。(参照の仕方は後述)
    • 以後は、このObjectに対して読み書きの処理を行えば、適切なタイミングでデータを引っ張り出してくれたり、書き込みの通信を行ってくれるようになります。
  • _FirebaseUser
    • 前回の記事に載せるのを失念していたのですが、Firebaseに接続した際の認証情報を保持し、Firebaseとユーザ認証周りでやり取りする際には、このObjectに保持されている情報を利用します
Startメソッド

Startメソッドには、以下を追加しています。

// Firebase RealtimeDatabase接続初期設定
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("Firebase Realtime Database URL");

Firebaseの接続設定を行います。
中身まで読み取っていないですが、シングルトン設計になっているっぽくて、引数に参照先のURL(Firebase Realtime Database URL)を記載する事で、以後RealtimeDatabaseの参照先はそのURLを見に行くようになります。
URLは、以下のキャプチャの場所を参照して入力してください。

f:id:project-unknown:20170902125359p:plain

// Databaseの参照先設定
_FirebaseDB = FirebaseDatabase.DefaultInstance.GetReference("UnitySample/Ranking");

フィールドにある_FirebaseDBに、どの要素を参照するのかを保持します。
冒頭で作成したスタブデータの、

f:id:project-unknown:20170831230734p:plain

Ranking以下を参照して行きたいので、「UnitySample/Ranking」と記載します。
このように階層は「/」区切りで指定していけば、該当の要素まで一気に絞って参照することができます。

もちろん、「UnitySample/Ranking」ではなく「UnitySample」としても良いのですが、
そうすると、今回必要なのはRanking以下のデータなのに、UnitySampleにぶら下がった全てのデータを取得するようになり、データ通信的に無駄になりますし、後の処理も一々Ranking以下を参照するコードを書かないといけなくなり冗長となってしまいます。
この例の用に、参照する要素が特定出来るのであれば、参照先は一気にその場所まで指定してしまった方が良いです。

FirebaseLoginメソッド

このメソッドは殆ど変更はありません。
認証が完了した後の、以下2行を追加しています。

_FirebaseUser = task.Result;

// 認証完了したらデータを読み込む
this.GetRankingData();

最初の_FirebaseUserに、task.Resultを突っ込んでいるのは、後ほどFirebase認証情報を利用したいので、保持するようにしています。
二つ目の、this.GetRankingData()が、後述するランキングデータを読み込むメソッドを認証が完了した際に実行しようとしています。

GetRankingDataメソッド

今回の肝となる部分です。
ここでは、先程Firebase参照Objectである、_FirebaseDBを用いて実際にデータを取得しています。

_FirebaseDB.OrderByChild("score").LimitToLast(1).GetValueAsync().ContinueWith(task => {
});

ここで、Firebaseからscoreが1番高いユーザのデータを1件だけ取得する。
事を実現しています。

GetValueAsync

GetValueAsyncを使う事で、指定されたPath(UnitySample/Ranking)を1度読み込む事が出来ます。
読み込んだデータはFirebaseでスナップショットと呼ばれます。
なので、データを取得するだけであれば、以下でも十分です。

_FirebaseDB.GetValueAsync().ContinueWith(task => {
});

ただ、これだと「UnitySample/Ranking」パス以下のデータを全て取得してしまうので、不要なデータも受け取ってしまい通信量が増えますし、その後の処理も要るデータ/要らないデータとを分けて処理を行う必要が出てきてしまうため、非常に無駄な事をしてしまいます。
ですので、後述にある通り、ある程度必要なデータを絞って取得するようにします。

今回の例では、1度読み込むだけにしていますが、Realtime Databaseの旨味である、データに変更があったら通信を行うことも出来ます。
これについては別途記事にします。

OrderByChild

SQLを利用した事がある方だとなんとなく意味が理解できそうですが、これで結果を並べ替えることが出来ます。

  • OrderByChildは指定した子キーの値で結果を並べ替える事ができます。
    • また、子キーは指定したPath直下じゃなくても、子の子でも指定できます。
      • 今回の場合は、「UnitySample/Ranking」配下の「uid/score」で並べ替えを行います。
  • OrderByChild("score")とすることで、scoreを昇順に並び替えます。
  • OrderByChildは降順には並び替えることが出来ません。
    ただ、今回はランキングが一番高いユーザ情報を取得したいので、降順なデータが欲しい所。
    そんなときは、以下の、LimitToLastを使って降順なデータ取得を実現します。

データの並び替えには、他にも以下の機能がサポートされています。

  • OrderByKey()
    • 子キーで結果を並べ替える
  • OrderByValue()
    • 子の値で結果を並べ替えます
LimitToLast

これは、OrderByChild等で並び替えられたデータを、末尾から返却する最大数を設定します。
なので、OrderByChildで降順に並び替えを行い、LimitToLastで最後の方のデータを取得する事で降順が実現できます。
以下は具体例です。

// 元のデータの並び
1,3,2,6,5,4,8,7,9

// OrderByChildを掛けた後
1,2,3,4,5,6,7,8,9

// LimitToLast(2)で最後から2番目まで取得
8,9

// これをローカルで大きい順に並び替える
9,8

本当だったら、OrderByChildDeskとかあればいいんですけどね…。
LimitToLastと似た機能として、以下が提供されています。

  • LimitToFirst()
    • LimitToLastの反対で最初の方からデータを取得します
  • StartAt()
    • 指定したキーまたは値以上のデータを取得します
  • EndAt()
    • 指定したキーまたた値以下のデータを取得します
  • EqualTo()
    • 指定してキーまたは値に等しいデータを取得します
まとめると
_FirebaseDB.OrderByChild("score").LimitToLast(1).GetValueAsync().ContinueWith(task => {

これは、
scoreを降順で並び替えたデータの最後から1つ目までのデータを取得する
という事を行っています。

DataSnapshot snapshot = task.Result;

上述していますが、Firebaseで取得したデータをshapshotと呼称しており、
取得した結果(task.Result)をDataSnapshot型のオブジェクトに突っ込みます。

IEnumerator result = snapshot.Children.GetEnumerator();

snapshotに含まれる結果セットをIEnumeratorに一度突っ込み、後のデータを取り出しやすくします。

while (result.MoveNext())

ここで、取得した結果セットをあるだけwhileを回し、データの中にアクセスします。
このwhileの中で、Rankingの子要素にアクセスして、データを受け取っています。

駆け足でしたが、ここまででデータの取得処理が完了です。
一度実行してみましょう。

UnityEditor上だと、Firebaseの接続がうまくいかない事があるので、実機で確認します。
以下は、XCode上からiPhoneで起動してDebugLogの中身を表示しています。

f:id:project-unknown:20170902211227p:plain

無事にデータを取得する事が出来ています。
ただ、スタブデータを1件しか登録していなかったので、OrderByChildやLimitToLastが効いているかわかりにくいので、もう1つデータを登録します。

以下のように、uid2にscoreが200のユーザを追加します。

f:id:project-unknown:20170902211519p:plain

これで再度実行してみましょう。

f:id:project-unknown:20170902211624p:plain

無事取れていますねヽ(=´▽`=)ノ

さて、ここまででFirebase Realtime Database上にあるデータを取得するところまで出来ました。
次は、スタブデータでは無くて、実際にデータを登録します。

UnityからRealtimeDatabaseに書き込みをする

データ取得と同様、Firebaseの設定は終わっているので、コードを書いていきます。
これまでのコードに処理を書き足します。
まずは、最初に完成形のコードをお見せします。

gist.github.com

処理の流れを以下の通りに変えています。

before

  1. Teritterログイン
  2. Firebaseログイン
  3. Realtime Databaseからデータ取得

after

  1. Twitterログイン
  2. Firebaseログイン
  3. Realtime Databaseへデータ書き込み
  4. Realtime Databaseからデータ取得

細かい処理の流れの変更点は上記コードを見てもらうこととして、今回の肝であるデータを追加しているロジックを見ていきます。

WriteNewScoreメソッド

引数に渡されたScoreをFirebase Realtime Databaseへ書き込みに行く用途のメソッドです。
データはDictionary形式でデータ構造を作成します。

しつこいかもしれませんが、今回作成するデータ構造は以下の通りでしたね。

f:id:project-unknown:20170831230734p:plain

「_FirebaseDB」に「UnitySample/Ranking」までのPathを参照するようになっているので、それ以下のデータを作成していきます。

まず、最下層に位置する「name」「id」「score」「updatedate」のデータセットを作成します。

Dictionary<string, object> itemMap = new Dictionary<string, object>();
itemMap.Add("name",       _FirebaseUser.DisplayName);
itemMap.Add("id",         _UserName);
itemMap.Add("score",      score);
itemMap.Add("updatedate", System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));

前回の記事でも書きましたが、Display NameはTwitter Kit for Unityではサポートしていないので、Firebase認証情報から取得するんでしたね。

次に、「uid」部分のデータ・セットを造ります。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add(_FirebaseUser.UserId, itemMap);

冒頭でも記載しましたが、uidには、FirebaseのAuthenticationが発行するuidを登録したいので、「_FirebaseUser.UserId」からデータを取得しています。
そして、uidをkeyに作成した「name」「id」「score」「updatedate」を纏めたitemMapをvalueに登録します。

最後に

_FirebaseDB.UpdateChildrenAsync(map);

上記、Firebaseへ参照を持ったオブジェクトの、UpdateChildrenAsyscメソッドに、作成したデータをセットすれば、「UnitySample/Ranking」以下にデータを登録できます。

余談

ここまで記載した通り、Dictionary形式でデータセットを作成し、階層構造のデータをつくる分には分かりやすいのですが、毎回このような書き方をすると非常に面倒だったりする時があります。
この場合便利な記載方法があります。

例えば、もう既にuidまで登録されていて、そのuidに紐づくscoreを書き直したいんだと言う場合は、以下のようにも書けます。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add(_FirebaseUser.UserId + "/score/", itemMap);

_FirebaseDB.UpdateChildrenAsync(map);

このように、書き直したいデータまでKeyに一気にパスを貼ってしまって、それに該当するデータだけセットすれば、データ登録を行うことが出来ます。
極論で言うと、以下のような書き方も出来ます。

Dictionary<string, object> map = new Dictionary<string, object>();
map.Add("/Ranking/" + _FirebaseUser.UserId + "/score/", itemMap);

FirebaseDatabase.DefaultInstance.GetReference("UnitySample").UpdateChildrenAsync(map);

上記の例は、Firebaseへの参照を「UnitySample」までしか貼っていないケースで、RankingからscoreまでのPathを一気に貼る例です。

余談2

Sampleの以下のコードの説明がねーよって思っている人がいるかもしれません、

string key = FirebaseDatabase.DefaultInstance.GetReference("UnitySample").Child("Ranking").Push().Key;

今回の事例では利用していないのですが、Firebaseのデータ管理の意味で重要で便利な意味合いを持つ所なので、今回ご紹介しています。

この部分は、UnitySample/Ranking以下に1つユニークな文字列を作成する
これをどういう時に利用するのか?と言うと、
例えば、今回の例では、ユーザ毎にデータを登録して、更新があれば上書きする仕組みにしていますが、例えばスコアを登録する度に、Updateでは無く追加する場合に大いに役に立ちます。
具体的に言うと、

今回の例

  • 1位 : ゆう@あんのうん
  • 2位 : ぽぽた
  • 3位:どうまず

Push().Keyを使った具体例

  • 1位:ゆう@あんのうん
  • 2位:ゆう@あんのうん
  • 3位:ゆう@あんのうん

上記の用に、ユーザ名でユニークなデータ構造ではなくて、毎回スコア更新する度に別なデータとして登録する事で、まさに昔のゲームセンターにハイスコアを一人のユーザで埋め尽くすような機能も実装出来ます。
ただ、この場合問題となるのが、ユーザ名でユニーク化をしないので、何かしらデータを特定出来るユニークなKeyが必要となり、この例の様なKeyを使うことで同一ユーザでも別なデータとして取り扱うことが出来るようになります。

ただ、Keyは発行する度に異なりますので、自前で何処かに保存しておかないと、自分自身ですら分からないことになるので、取扱注意です。(Firebase上でこれを解決する場合は、「UnitySample/Ranking」とは別に「UnitySample/User/uid/」以下にそのユーザが発行したKeyをぶら下げると言う管理の仕方もありですね。

実行する

余談のほうが長いんじゃないかのレベルで記載してきましたが、さぁ実行してみましょう。
今回は、実際にデータを登録しているので、実機ログではなくて、Firebase Consoleのデータを見てみます。

f:id:project-unknown:20170902224140p:plain

(uid部分は私の実際にuidが入り込んでいるのでモザイクにしています。実際に見てもらえば分かるのですが、英数字の文字列が羅列されていると思います)

これでデータの登録も出来るようになりましたね!

また、例えば、以下の

this.WriteNewScore(100);

をスコア更新の意味で、

this.WriteNewScore(300);

に変更して、再度実行してみましょう。
新規にデータが登録されるんじゃなくて、既にあるユーザのScoreが上書きされたかと思います。

自分自身のデータを取得する

これまではランキングの上位10件を取得する機能を作って来ましたが、自分自身のデータが欲しい時があります。

再掲ですが、以下のデータから、自分自身のuserIDにぶら下がる情報を取得します。

https://cdn-ak.f.st-hatena.com/images/fotolife/p/project-unknown/20170831/20170831230734.png

欲しい情報は、Ranking直下にある、自分自身のUIDの情報です。
このような時は、Childメソッドを使って自分自身のデータを取得します。

_FirebaseDB.Child(_FirebaseUser.UserId).GetValueAsync().ContinueWith(task => {
    if (task.IsFaulted) {
        // 失敗時の処理
    } else if (task.IsCompleted) {
        // 成功時の処理
    }
});

_FirebaseDBは、

_FirebaseDB = FirebaseDatabase.DefaultInstance.GetReference("UnitySample/Ranking");

UnitySample/RankingまでのPathが貼られているので、その直下にある、自分自身のUserIDを指定し取得します。

まとめ

実際にFirebase Realtime Databaseへデータの読み書きを行う所まで行いました。
今回の所で、オンラインランキングシステムが実現できるようになりましたが、何度も記載している通り、今のままだとセキュリティリスクが非常に高いですので、次回はセキュリティ対策の意味を込めた「ルール」造りをしていきます。

参考

本当に書いた、怖いソース(swift編) マジックナンバー

はじめに

どうも、どうまずです。

私が本当に書いて指摘してもらった、汚い・危ないswiftのソースを心霊写真の紹介っぽく記載していきます!

反省も踏まえまして…

恐怖・マジックナンバー

まずは、こちらのソースをご覧下さい・・・。

switch indexPath.section {
  // スケジュール名設定
  case 0:
    return "スケジュール名"
         
  // 曜日設定
  case 1:
    // 曜日の先頭のセル
    guard indexPath.row != 0 else {
  // 以下略






おわかり頂けたでしょうか・・・

一見なんの変哲もないswitch文に見えますが、
case文のところよ〜〜く見て頂きたい・・・。

この0と1、意味を持っているんです!

あれです。
今はわかるけど、後で修正や保守しようとしたときに、 『これ、なんだっけ?』ってなるパターンです。 何とも恐ろしい!!!

これらはマジックナンバーと呼ばれ後々暴れ出すのです。

マジックナンバー (プログラム) - Wikipedia

こういう場合は、enumなどを利用し、 利用し、数字をちゃんと定義してわかりやすくしましょう。

コメントで誤魔化そうとしてもダメです。 最初はコメント書いてても、書き忘れて、結局わからなくなるんですよ。

修正後は下記のような感じです。

enum SectionRowNumber: Int {
    case scheduleName = 0
    case week         = 1
}

switch indexPath.section {
  // スケジュール名設定
  case SectionRowNumber.scheduleName.rawValue:
    return "スケジュール名"
         
  // 曜日設定
  case SectionRowNumber.week.rawValue:
    // 曜日の先頭のセル
    guard indexPath.row != 0 else {
  // 以下略

enum部分が長くなっていますが、ソースだけ見ても、意味がわかるようになりました。

直してやったぜ風に記載してますが、ダメなソース書いたの私なんですよね。

頑張ろう・・・

注意
レビューアの観点によって、諸説あります。

ブログアイコンをリニューアル

これまでBlogに、以下のアイコンを使っていたのですが

f:id:project-unknown:20170827023416p:plain

ずっと変えたい変えたいと思っていたので、ゲーム開発の素材より優先して作っちゃいました。

f:id:project-unknown:20170827025326p:plain

ぺぺーん!

Project.Unknownのマスコット、う〜のんです。
どうぞよろしくお願いします^^

UnityでFirebaseにTwitterアカウントでログインする機能 - UnityでFirebaseを使ったオンラインランキングシステムを作るvol2

この記事はシリーズ物です。
シリーズの記事は以下を参照ください

www.project-unknown.jp

www.project-unknown.jp

www.project-unknown.jp

やりたい事を簡潔に纏めるskill皆無な為、長いタイトルになってしまった…。

最初に懺悔いたしますと、かなり試行錯誤しながらFirebaseの実装を進めており、思い出しながら書いています。
もしこれで動かなかったなどありましたら、お気軽にTwitter@YuwUnknown までご連絡くださいmm

ここに載せている情報は、公式を参考に記載しているものです、
情報が古くなっている場合は、一度公式を参考くださいませ。

はじめに

Firebaseで用意されているAuthenticationは、簡単に言うとユーザ認証を指します。
独自でユーザ認証システムを行うとシステム構築が大変なので、TwitterやFacebook、Googleアカウント等の認証を作ってユーザのログイン機能を担ってくれます。

前回のUnityでTwitter認証を行うの記事でも書いたのですが、とりあえず今のところの目標はFirebaseでTwitter認証を行い、認証情報で取得したユーザ名でランキングシステムを作ると言う所を目指します。
そこで今回は、Firebase AuthenticationでTwitter認証を行います。

前回は、UnityでTwitter認証をする方法を記載しました。
そこで記載されている、AccessTokenとSecretが必要となるので、まだ見ていない or AccessTokenの取得方法が分からない方は、まず前回の記事を参考に取得できるようにしておいてください。

project-unknown.hatenablog.com

Firebaseの初期設定

Firebaseをまだ登録されていない方は登録しちゃいましょう!

firebase.google.com

また、契約やサービス内容についてヒアリングした結果も過去のブログに載せているので、もし料金が〜など不安な方が居ましたら、以下を一読頂ければと思います。
project-unknown.hatenablog.com

Firebaseのコンソールに移動して、プロジェクトを追加します。(私はfirebase-sampleというプロジェクトを作っています。)
f:id:project-unknown:20170828000653p:plain

プロジェクトの作成が完了したら、作成したプロジェクトをクリックしましょう。Overviewに移動するかと思います。

次に、Authentication -> ログイン方法 -> Twitterをクリックします。

f:id:project-unknown:20170828003602p:plain

ここで、前回のTwitter Developerに登録した発行した、AccessTokenとシークレットを入力します。
(混同しやすいのですが、これは開発者が登録した際のAccessTokenです)

iOS/Android用の設定を行う

OverViewからアプリを追加してください。
iOSとAndroid両方に展開予定であれば、iOS用とAndroid用2つ必要です。

f:id:project-unknown:20170904164755p:plain
追加したらこんな感じになっていると思います。

後は、設定ボタンをタップ

f:id:project-unknown:20170904165013p:plain

以下のようにinfoファイルをダウンロードして、Unityに突っ込みます。

f:id:project-unknown:20170904165101p:plain

ファイル名は以下の通りです。

  • iOS
    • GoogleService-Info.plist
  • Android
    • google-services.json

ここまで実施したらFirebaseの設定は完了です。

Firebase SDKを導入する。

公式からSDKをダウンロードします。
この中に、複数のPackageが梱包されています。
機能毎にPackage名が名付けられているので特に悩まないと思います。

ここでは、以下の2つをimportしてください。

  • FirebaseAuth.unitypackage
  • FirebaseDatabase.unitypackage

(FirebaseDatabase.unitypackageは次回以降で紹介するRealtimeDatabaseで利用しますので、今のうちに入れておきましょう!認証だけでいいんだよと言う人は、FirebaseAuth.unitypackageで十分です)

Firebase Authenticationのコードを書く。

やっとここでコードが掛けます。
やっている事は、以下です。

  • Game起動と同時にUnityからユーザのTwitter認証(Twitterログイン)を行います。
  • Twitter認証が完了したら、取得したAccessToken, シークレットの情報を元に、Firebaseへログインします。

gist.github.com

ここで一点注意点なのですが、今回使ってきているTwitter Kit for UnityにはDisplay Nameを取得する機能はありません。(2017/9/2現在)
Display Nameとは、以下のキャプチャの通り他のユーザに見える名前です。

f:id:project-unknown:20170902215427p:plain

これかなり不便ですね…。
ただ、Firebaseの方で取得する機能があります。
上記の例ですと、以下のところです。

newUser.DisplayName

今回はFirebaseと一緒に使うから良いものの、早めにここは追加してもらいたいですね。

では、これを実機で起動してみてください。
(Unity Editor上でも出来るのですが、Editor上だとFirebaseの動作がなんとなく怪しい動作を行うので、可能なら実機で確認しておいたほうが良いです)
以下はXCodeにFirebaseのログインが成功した際に出力しているログを載せておきます。

f:id:project-unknown:20170828010828p:plain

さいごに

ここまでで、FirebaseにTwitterログインを実現する所まで完了しました。
次はいよいよ、FirebaseのRealtime Databaseに認証したTwitter情報で書き込む所を記載しようと思います。

参考

project-unknown.hatenablog.com

UnityでFirebase Authenticationを使ってみる(公式資料)

SDK配布場所