C# List<T>の使い方のまとめ

はじめに

Listは動的に要素の追加や削除ができます。
配列(Array)を使っていて、配列に対して動的に要素を増やしたりで悩んだ際に、このListに行き着く人が多いのではないでしょうか。
(実際に私がそうでした)

Listを扱えるようになると、コーディングの幅が一気に広がりますので是非とも習得しましょう。
この記事では、Listの基本的な使い方について紹介します。

使い方

まず、usingステートメントに以下を宣言します。

using System.Collections.Generic;

このGeneric(ジェネリック)ですが、List型は、「型パラメータ」を利用します。
Listの「」がGenericを指しています。
なので、usingステートメントで型パラメータを扱えるようにしておかないといけません。

Listの宣言

Listの生成

初期化する際は、以下の例の様に宣言します。

// int型のListを生成
List<int> listValue = new List<int>();

上記はint型のListですが、の部分はGenericなので他の型も指定出来ます。
例えば、以下はDictionaryを指定した際のListです。

// Dictionary<string, object>型のListを生成
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();

Listの初期化

Listは配列と同様、初期化のタイミングで値を入れる事が出来ます。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> () {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

要素の追加

末尾に追加

末尾に追加する場合は、「add」を用います。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 末尾に追加
result.add("test-key3", "test-value3");
途中に追加

途中に追加する場合は、「Insert」を用います。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 2番目の要素に追加
result.Insert(2, "test-key3", "test-value3");

要素の削除

全て削除
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 全て削除
result.Clear()
指定したインデックスで削除

配列と同様に、指定したインデックスでの削除も出来ます

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 1番目の要素を削除
result.RemoveAt(1);

要素の取り出し方

配列と同様な取り出し方
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
// 1番目の要素を取り出す
Dictionary<string, object> element = result[1];
foreachと組み合わせる

ListはIEnumerableインタフェースを採用しているので、foreachで取り出すことが出来ます。

List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

foreach (Dictionary<string, object>element in result) {
   // elementに上から順にresultの要素が入る
}

Linqと組み合わせる

LinqはCollectionを柔軟に扱える機能だけあって、Listとの相性が良いです。
以下はLinqの使い方と活用事例です。

使い方

usingステートメントにLinqを追加します。

using Systen.Linq;
using System.Collections.Generic;

Listの中を検索して取り出し

// dictionaryのkeyが「test-key2」の物を探す
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
Dictionary<string, object> element = result.Where(key => key == "test-key2");

Listの中を検索して削除

// dictionaryのkeyが「test-key2」の物を削除
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}
result.RemoveAll(key => key == "test-key2");

存在確認

// dictionaryのkeyが「test-key2」の物があるか確認
List<Dictionary<string, object>> result = new List<Dictionary<string, object>> {
   "test-key1", "test-value1",
   "test-key2", "test-value2"
}

if (result.Ecists(key => key == "test-key2")) {
    // 存在
}

応用

応用といっても、これまでの延長線ですが、例えばゲームでHPが30未満のキャラクターに対して回復魔法を実行する機能を作ってみます

class Charactor {
    string _Name;
    int _HP;

    public GetName() {
        return _Name;
    }
    public GetHP() {
        return _HP;
    }
    public SetHP(int hp) {
        _HP = hp;
    }

    public Charactor(string name, int hp) {
        _Name = name;
        _HP     = hp;
    }
}

class Main {
    void execute() {
        List<Charactor> charactorList = new List<Charactor>() {
            new Charactor("主人公", 100);
            new Charactor("戦士", 130);
            new Charactor("僧侶", 50);
            new Charactor("魔法使い", 30);
        }
        this.Heal(charactorList);
    }

    /**
      HPが60以下のキャラを回復させる
     */
    void Heal(List<Charactor> charactorList) {
        var healTarget = persons.Where( chara => chara.HP <= 60);
        foreach (var chara in healTarget) {
            // 10回復
            chara.SetHP(chara.GetHP() + 10);
        }
    }
}

さいごに

Listはそれ単体でも拡張性の高いデータ構造で大いに役に立ちますが、Linqと組み合わさる事で、簡単に要素へのアクセス・検索ができ、コードの可読性・生産性が大いに向上しますね。
私自身、Linqなどは使い始めたばかりですが、色々使って見て便利なやり方などが見つかったら引き続き記事に記載していこうと思います。

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
オーダーがあればもうちょっと細かい記事を書きたいと思います。

written by ゆう@あんのうん

本当に書いた怖いソース(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点を注意しましょう。

…いや、注意します。

注意

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

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

完全に自己満記事です。

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

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

これを

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

こうした(ドヤッ

どうやらPixivのサービスの1つで自分の絵をグッズにしてくれるサービスがあるみたいで、今回利用させてもらった次第です。

factory.pixiv.net

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

written by ゆう@あんのうん, やよん

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

written by ゆう@あんのうん

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

written by ゆう@あんのうん

本当に書いた、怖いソース (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を殆ど使ったことなかったので、こんなやり方もあるんだとちょっと驚きました。
ダイエット方法はいろいろありますね。頑張って痩せなきゃ・・・

注意

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