FlutterアプリをXcode Cloudでビルドする

数年ぶりにFlutterを試してみたら、思いの外快適で安定している。大規模なアプリでどこまで使えるのかはわからないが、少なくとも趣味で片手間にアプリを書く程度ならかなり良さそうだ。勉強がてら何かしらアプリを一つ作りたいと思い、年末頃から週末の空いた時間に開発を続けているのだが、ようやくそろそろリリースが見えてきた。

“FlutterアプリをXcode Cloudでビルドする” の続きを読む

Google Apps ScriptのBigQuery Serviceのメソッドの引数の調べ方

Google Apps ScriptからBigQueryの機能を利用するにはAdvanced ServiceであるBigQuery Serviceを利用する。

https://developers.google.com/apps-script/reference#advanced_services

このAdvanced Serviceのメソッドを調べるには少し知識が必要になる。

“Google Apps ScriptのBigQuery Serviceのメソッドの引数の調べ方” の続きを読む

Google Apps Scriptでスプレッドシートの内容をオブジェクトの配列として取得する

Apps Scriptでスプレッドシートをテーブル的に使いたい時に、シートの内容をオブジェクトの配列として取得したいケースがよくある。

シート全体のセルの値はgetValues()もしくはgetDisplayValues()メソッドを使って取得することができる。

取得した値のうち、一行目をヘッダー行(keyの配列)として取り出し、残りの行を値として取り出すことで、オブジェクトの配列を作る。

function getRowsAsObjects(sheet) {
  var data = sheet.getDataRange().getDisplayValues();
  
  const header = data.shift();
  
  var rows = data.map(function(row) {
    var dic = {}
    header.forEach((h, idx) => {
      dic[h] = row[idx];
    });
    return dic;
  });

  return rows;
}

すると、このような結果が得られる。

[{name=Apple, date=2024/01/01}, {date=2024-03-13, name=Orange}]

よく書くコードなので、GASのライブラリとしてまとめた。

https://script.google.com/home/projects/1igMdPkdNu4Yg7u_rnBrXYTh7x15KotD0IpikNvrP52fCLV67ClXJ81ut

スクリプトID: 1igMdPkdNu4Yg7u_rnBrXYTh7x15KotD0IpikNvrP52fCLV67ClXJ81ut

使い方

var objects = SheetValues.getRowsAsObjects(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TestSheet"))

Apps Scriptでスプレッドシートのセルの値を画面に表示された文字列そのままの形で取得したい

Apps ScriptでSpreadsheet Serviceを使うと、Spreadsheet内の任意のセルの値を取得できる。

https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app

処理の内容によって、画面に表示されている内容を文字列としてそのまま取得したい場合と、日付などその文字列が差している内容を適切な型で取得したい場合があるが、Spreadsheetにはそれぞれの目的に合わせて値を取得する関数が用意されている。

getValues

https://developers.google.com/apps-script/reference/spreadsheet/range#getValues()

上のような値が入った状態で、getValues()を使って値を取得すると、下記のような結果が得られる。

values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TestSheet").getDataRange().getValues();

>> [[name, date], [Apple, Mon Jan 01 00:00:00 GMT+09:00 2024], [Orange, Wed Mar 13 00:00:00 GMT+09:00 2024]]

date欄に入力した値は 2024-01-01 という文字列だが、この文字列はSpreadsheet内では自動的に日付として認識されており、getValues()で取得した値は文字列ではなくdate型の値になっている。この挙動は時々厄介だ。ユーザが入力した文字列の値を想定していたら違う値が返ってきていることに気づかず、混乱の原因になったりする。

getDisplayValues

https://developers.google.com/apps-script/reference/spreadsheet/range#getdisplayvalues

getValues()の代わりにgetDisplayValues() を使うと、画面に表示されている内容をそのまま文字列として取得できる。

values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TestSheet").getDataRange().getDisplayValues();

>> [[name, date], [Apple, 2024-01-01], [Orange, 2024-03-13]]

どちらを使うべきか

どちらを使うのが良いかはケースバイケースで判断が難しい。

例えば、date欄で日付を入力してもらうことを想定している場合、上記のようにユーザがもし特定の行の表示フォーマットを変更してしまったとしても、getValues() では表示フォーマットの影響を受けずに日時の値を取得できる。

[[name, date], [Apple, Mon Jan 01 00:00:00 GMT+09:00 2024], [Orange, Wed Mar 13 00:00:00 GMT+09:00 2024]]

一方で、getDisplayValues()では、画面に表示されている内容がそのまま返されるため、これを日時として処理したい場合はどちらのフォーマットで入力されても対応できるように実装する必要が出てくる。

[[name, date], [Apple, 2024/01/01], [Orange, 2024-03-13]]

従って一概にどちらを使うべきとは言えず、ユーザにどのような形式での入力をしてもらうか、運用と合わせて実装方法を考えるしかなさそうだ。

AndroidアプリのCI/CD – Google Play Developer APIs

Apple StoreにXcode Cloudが登場してから、iOS / macOSの世界ではアプリのCI/CDのハードルが一気に下がった。一度その快適さが当たり前になってしまうと、Androidアプリのリリースを手動で行うのが余計にストレスに感じられるようになってしまったので、CI/CD環境を作った。

残念ながらGoogle PlayにはまだXcode CloudにあたるようなマネージドなCI/CD環境はないが、公式APIが用意されているのでGitHub Actionsなどと組み合わせれば自力でCI/CD環境を作ることはできる。ただし、CI/CD環境を構築するにあたって押さえておかなければいけない使い方・仕組みが色々ある。一度に説明しようとすると多少ハードルが高いので、まずはAPIから。

“AndroidアプリのCI/CD – Google Play Developer APIs” の続きを読む

Xcode Cloudでビルドの前後に処理を行う

今更だが、Xcode Cloudが便利だ。Apple純正のCI/CD環境ということもあって、設定で複雑なことをする必要がなく、やれることも限られている分わかりやすい。

大抵のアプリのビルドでは、Xcode Cloudの標準の機能だけで問題なくApp Storeへの申請/TestFlightでの配布ができているのだが、稀にビルドの前後に何らかの処理を行いたいケースがある。

https://developer.apple.com/documentation/xcode/writing-custom-build-scripts

Xcode Cloudには、Custom Build Scriptという仕組みが用意されており、このルールに従ってスクリプトを用意すると、下記のタイミングで任意の処理を実行できる。

  • レポジトリからのCloneが終わった直後(ci_post_clone.sh)
  • xcodebuildが実行される直前(ci_pre_xcodebuild.sh)
  • xcodebuildが終わった直後(ci_post_xcodebuild.sh) ※失敗時も呼ばれる

ビルド番号(CI_BUILD_NUMBER)や何をトリガーにワークフローが開始されたのか(CI_START_CONDITION)などの情報を環境変数として参照できるようになっている。

https://developer.apple.com/documentation/xcode/environment-variable-reference

デバッグ用の情報(COMMIT HASHなど)をアプリ内で参照できるようにする、など色々使える場面があって、覚えておくと何かと便利。

Godot 3系のstore_varで保存したファイルをGodot 4で読み込めない問題

GodotのFileクラスのstore_varというメソッドを使うと、容易にオブジェクトの状態を保存できる。

https://docs.godotengine.org/ja/stable/classes/class_file.html#class-file-method-store-var

store_varで保存したファイルをget_varで簡単に復元することができるので、Godotで色々なオブジェクトの状態を保存/復元する方法として紹介されることがある。便利なので自分のアプリでも使っている箇所があったのだが、今回そのアプリのGodot 4対応を進めようとして思わぬ問題にぶつかってしまった。

https://godotforums.org/d/31966-filestore-var-in-35-to-godot-4/16

フォーラムでも報告している人がいるように、Godot 3系のstore_varメソッドで作成したファイルをGodot 4のget_varで復元しようとすると正しく復元できないのだ。

https://github.com/godotengine/godot/issues/73530#issuecomment-1435792453

残念ながら、これは不具合ではなくGodot 4で想定済みの仕様のようで、今後バージョンアップなどで対応される予定もないそうだ。この問題に対する対策としては、下記の二つの方法が提案されている。

  1. Godot 4系にアップデートする前に、ファイルをJSONなど別のフォーマットに移行する機能を作る
  2. 自前でバイナリーローダーを書いて、3系で保存されたバイナリファイルを解読して4系に読み直す

後者は現実的に面倒で難易度も高そうなので、3系のstore_varで作成したデータを引き継ぐには前者の方法をとるしかなさそうだ。ただし、その場合は当面の間3系を使い続けることになってしまうので頭がいたい。

store_varは仕様上今後のアップデートでも同様の問題が起きる可能性があるので、オブジェクトの状態を保存したいときは面倒だがstore_stringとget_as_textを使って自前でシリアライズ/デシリアライズを実装した方が良いようだ。(現在公式ドキュメントのサンプルもそうなっている)

Godot 4でtresファイルのダイナミックロードがやりにくくなった問題

Godot 4で、フォルダ内にあるtresファイルを動的に読み込むプログラムを書いたところうまく動作しなかった。

for file in dir_contents(folder_path):
	if file.get_extension() == "tres":
		tres_files.append(file)

上のコードは、特定のフォルダにあるtresファイルを全てリストアップするためのコードだ。Godot Editor上でこのコードを実行すると、フォルダ内のtresファイルがリストアップされるが、iOS上ではファイルが一つもリストアップされない。調べてみると、.tresファイルは見つからないが代わりに.tres.remapというファイルが存在していることがわかった。

https://github.com/godotengine/godot/issues/66014

Godot 3では動作していたのだが、どうやら仕様が変わりtresファイルもRemapされるようになったらしい。Remapされたファイルはload()やpreload()でres://プロトコルでファイルを指定して呼び出す際には内部的に自動でRemap後のデータが参照されるため、プログラム内で参照先をハードコーディングしているような場合は特にファイルの実態を意識する必要がないが、今回のように「フォルダからファイルを探して動的に読み込む」というような実装では「フォルダ上に見つかるファイル(remapファイル)」と「res://プロトコルで参照する際のURL」に差異が生まれてしまうのでそのままでは動かなくなってしまう。

for file in dir_contents(folder_path):
	if file.get_extension() == "tres":
		tres_files.append(file)
	if '.tres.remap' in file: # 追加
		image_files.append(file.trim_suffix(".remap"))

Godot 4でそのようなことをやりたい場合は、フォルダ内から.tres.remap ファイルを探し、読み込む際には.remapを取り除いたURLでload() するように実装する。ただし、Godot Editor上で作業している際にはRemapが行われていない元ファイルを参照しに行くことになるので、上のコードのように.tresファイルと.tres.remapファイルの両方に対応できるようなコードを書く必要がある。

https://github.com/godotengine/godot/issues/25672

最初にはったIssueと、上に貼ったもう一つのIssueでも指摘されているが、なかなかわかりにくい仕様だと思う。

M1 iMacへの買い換え

2020年末に購入したM1チップ搭載のMacBook Proが快適だ。Xcodeを使ったアプリ開発やPremiereでの動画編集はもちろん、最近少しずつ増えてきたニューラルネットワークが内部的に使われているようなAIアプリのパフォーマンスにおいて、Intel搭載のiMacとは顕著な差があることを実感している。

早くiMacもApple Silicon搭載の27インチモデルが発売されないかとずっと待ってきたが、待ち続けているうちに2年が経過してしまった。2023年中には発売されるのではないかという噂もあったが、昨今のApple製品の値段設定を見ていると発売されてもかなり高価になりそうなこと、それを待つ間の作業今のiMacの作業効率で失う生産性のバランスを考えて、24インチモデルの現行iMacに買い換えることにした。

Apple Siliconが出た当初はDockerや音源プラグインなどで色々問題があったが、最近ではほとんど解消され、さらにはプログラムの最適化も進んでおり環境構築もかなりスムーズに終わった。環境が綺麗な状態で、パフォーマンスをテストしておこうと思う。

“M1 iMacへの買い換え” の続きを読む