Iganinのブログ

日頃の開発で学んだ知見を中心に記事を書いています。

XCodeのコンソールに出力されるCloudFirestoreのindex生成URLがうまく機能しなかった

はじめに

CloudFirestoreを使用してFirestore.firestore()でqueryを生成しデータアクセスをする際に、whereFieldやorderでデータの絞り込みや順番を変更することができます。この際に、指定条件によってはIndexの生成をコンソール経由で下記のように提案されます。

[Firebase/Firestore][I-FST000001] Listen for query at Data failed: The query requires an index. You can create it here: https://console.firebase.google.com/project/{project-name}/database/firestore/indexes?create_index=xxxxxxxxxxxxxxx

通常はyou can create it here:以下のURLの遷移先のFirebase Console上でindexの生成が提案されるのですが、特定のケースでURL先への遷移がうまく行かないことがあったので、事象の原因と解消方法を記載します。

環境設定

以下の環境を使用しています。

  • Xcode10.1
  • Swift4.2
  • FirebaseFirestore 1.0.2

発生事象および条件と解消方法

発生する事象は遷移後の画面がホワイトアウトし、正常に遷移できないというものです。

本事象は複数Googleアカウントを使用している場合に発生する可能性があります。 Index生成アドレスをブラウザに入力し、実際に遷移した後のURLを見るとわかりますが、下記のように/u/0/ がアドレス内に現れます。

https://console.firebase.google.com/u/0/project/{project-name}/database/firestore/indexes?create_index=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

元々のURLに /u/x の指定がないため本事象が発生しているようです。 複数アカウントを使用していると、この部分が本来 /u/1/u/2 などでなければならない場合があり、その場合に遷移に失敗してしまいます。 /u/0/u/{Number} に書き換えれば問題なく遷移できました。({Number}部分は実際に使用しているアカウントのコンソール上から下記の画像のように確認できます)

f:id:Iganin:20190323131906p:plain

Swiftにおける Voidと空Tuple - ()

はじめに

本日、UZUMAKIさん主催の「iOSアプリ設計パターン入門」の勉強会に参加しました。

内容はMVVMアーキテクチャに関してでしたが、議論の中でVoidや()の扱いに関して面白いものがでてきたので備忘もかねて記載します。

環境設定

以下の環境を使用しています。

  • Xcode10.0
  • Swift4.2

Void と 空Tupleについて

Voidは以下の形式で表される「型」です。

public typealias Void = ()

そのため、メソッドの引数等にとることはできません。 その際にはInstanceを入れる必要があります。 多くの場合、このような場合の引数として () を使用すると思います。

let sampleRelay = PublishRelay<Void>()

// このように Voidで型指定されている部分に ( ) を入れる。
sampleRelay.accept(()) 

ただ、 Voidが () の typealiasであることから、以下のように () は型としても機能します。

let a: () = ()

また、 Voidは型であるため以下のようにインスタンス化も可能です。

let a = Void()

したがって、 ()を代入する箇所では Void()の代入でも代替可能です。 ただ、 Void()はできますが、 ()()はできません。 Cannot call value of non-function type '()'というエラーが表示され、コンパイルエラーとなります。

まとめ

以上、取り止めがないですがまとめると下記のようになることがわかりました。

// Void は 型、 ()は型としてもインスタンスとしても機能する

let a: () = ()  // OK
let b = ()  // OK
let c = Void() // OK
let d: Void = () // OK
let e = ()() //  NG
let f: () = ()() //  NG

この辺りの言語仕様は普段あまり意識しませんが、色々触ってみると面白いですね。 個人的にはVoidがインスタンス化可能であることが意外でした。

IBOutletCollectionについて

はじめに

InterfaceBuilderの要素をコードと紐づけるために、IBOutlet、 IBActionが使われます。 ただ、複数の共通要素に対してはIBCollectionを使用すると便利です。 自分用のメモをかねて、IBCollectionに関してまとめます。

環境設定

以下の環境を使用しています。

IB要素の定義

IBOutlet - View要素とコードとの紐付け IBAction - ButtonのAction - Target紐付け IBOutletCollection - 複数View要素とコードとの紐付け

それぞれ、 UINibDeclarationsに以下のように定義されています。

#define IBOutlet
#define IBAction void
#define IBOutletCollection(ClassName)

// IBInspectable, IB_DESIGNABLEも定義されています
#define IBInspectable
#define IB_DESIGNABLE

IB Outlet Collection

IBOutlet, IBActionはStoryboard, Xibから紐づけることがおおいかと思います。  IBOutletCollectionも同様にStoryboard, Xibから紐づける際にIBOutletCollectionを選択して紐づけることが可能です。 またIBOutletCollectionはコード側に記載し、それをStoryboard,Xibとひもづけることもできます。

f:id:Iganin:20190130235856p:plain
IBOutletCollection例

@IBOutlet var buttons: [UIButton]!

IBOutlet, IBActionはweakで紐付けられることが多いですが、 IBOutletCollectionは弱参照では定義できないので注意が必要です。 ※配列による強参照での保持は問題ないのか、という疑問があります。このあたりご存知の方がいらっしゃいましたら共有ください。

格納順

IBOutletCollectionと紐づけた順で格納されていました。 ただ、この辺りはOS versionによって変わる可能性があるので、 tag等で並び替えできるようにしておいた方が安全かもしれません。

まとめ

似たような複数のView要素をIBOutletで接続すると、コードが煩雑になるため、 必要に応じてIBOutletCollectionを使用するとコードがスッキリします。

Codableに準拠したStructでネストされたプロパティをフラットにする際のTips

はじめに

Codableに準拠したStructやClassにおいて、ネストされた値をDecodeの際にフラットにしたいとします。 例えば以下のようなJSONに対して、下の階層の値であるaddress.nameを上位の階層のaddressNameとして扱いたい場合などです。 一般的にはこのような場合は、 nestedContainer を使用し、 init(from decoder: Decoder)を使用しますが、ちょっとしたテクニックを使用するとこの実装が不要となります。 本記事ではその方法についてのTipsをご紹介します。

let jsonData = """
{
"first_name": "Taro",
"last_name": "Tanaka",
"age": "30",
"gender": "male",
"address": {
            "code": 13,
            "name": "Tokyo"
        }
}
""".data(using: .utf8)!

環境設定

以下の環境を使用しています。

通常の方法

通常の方法を以下で記載します。 下の階層のプロパティにアクセスするために下の階層用のCodingKeyを定義します。 それを使用し、 nestedContainer 経由でプロパティの値を決めます。 この方法の場合、CodingKey の定義とinit(from decoder: Decoder) の実装が必要になります。

struct Person: Decodable {
    let firstName: String
    let lastName: String
    let age: String
    let gender: String
    let addressName: String

    private enum CodingKeys: String, CodingKey {
        case firstName = "first_name"
        case lastName = "last_name"
        case age
        case gender
        case address
    }

    // 下の階層のプロパティアクセス用にCodingKeyを定義
    private enum AddressCodingKeys: String, CodingKey {
        case code
        case name
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try values.decode(String.self, forKey: .firstName)
        lastName = try values.decode(String.self, forKey: .lastName)
        age = try values.decode(String.self, forKey: .age)
        gender = try values.decode(String.self, forKey: .gender)

        //  下の階層のプロパティにアクセスするために nestedContainerを使用し、Containerを作成
        let addressValue = try values.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
        // nestedContainer経由で下の階層の値を取得、自身のプロパティにセットします
        addressName = try addressValue.decode(String.self, forKey: .name)
    }
}

本記事での方法

Struct 内部にネストされた値のパース用のstructを定義し、パースされたstructをprivateで保持します。 プロパティから値を取得する場合は、 Computed Property経由でネストされた値にアクセスします。 この方法では通常の方法と異なり、 CodingKeyinit(from decoder: Decoderの実装が不要となります。

struct Person: Decodable {
    let firstName: String
    let lastName: String
    let age: String
    let gender: String
    // Computed Propertyを使用し、privateのプロパティから値を取得し返却する
    var addressName: String {
        return address.name
    }

    // パースにのみ使用するため privateで定義
    private let address: Address
    
    // AddressをパースするためにStruct内限定のStructを定義
    private struct Address: Decodable {
        let code: String
        let name: String
    }

}

まとめ

場合により使い分けるべきだと思いますが、本記事の方法によってstructの実装量をあまり増やさずにネストされた値をフラットにすることが可能になります。