Apollo-iosをバージョンアップしてcodegenでエラーが大量に出た時

apollo-ios 0.12.0から0.15.0にアップロードしたところエラーが大量に出てしまったためメモです。

リリースノートを見るといくつかの破壊的変更が入っていました。

変更内容

  • Apollo CLI を 1.9 から 2.17 に変更、これによってcodegenのコマンド引数が変更が必要
  • ApolloClientの引数に渡すHTTPNetworkTransportの引数が変更。これによって様々な機能をサポート(詳細は0.15.0のリリースノート)

apollo, apollo-codegen-swift のバージョンを上げましょう。

# apollo-ios 0.15.0 の場合
# Globalにインストールするかどうかはお任せします。チーム開発であれば、localでpackage.jsonで管理した方が良いでしょう。
$ npm install --save-dev apollo@2.17.0 apollo-codegen-swift@0.35.0

BREAKING, SUPER-BREAKING
この文字が入った、リリースノートには破壊的変更が入っているので、ソースコードの修正が必要になります。しっかり目を通しておきましょう。

ApolloClient

before

let apollo = ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration))

after

// configuration -> urlSession になっています。特に使っていなければdefaultが使われるので指定する必要はありません。
let apollo = ApolloClient.init(networkTransport: HTTPNetworkTransport.init(url: url))

BuildPhase codegen shell script

$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-cli.sh codegen:generate --queries="$(find . -name '*.graphql')" --schema=schema.json API.swift
$APOLLO_FRAMEWORK_PATH/check-and-run-apollo-cli.sh codegen:generate --target=swift --includes=./*.graphql --localSchemaFile="schema.json" API.swift

fetch

before

apollo.fetch(query: ABC.init(), resultHandler: { (result, error) in
}

after

let cancellable = apollo.fetch(query: ABC.init()) { result in
    switch result {
    case .failure(let error):
        if let resultError = error as? GraphQLResultError {
            // TODO:
        }
    case .success(let response):

    }
}

参考資料

Docs
https://www.apollographql.com/docs/ios/installation/

PullReq

https://github.com/apollographql/apollo-ios/pull/644/files

Bitrise で iOS / Android を動かすまでのメモ&TIPS

Bitriseは便利で楽チンの意味

提供している機能にすべて乗っかればたしかに楽ちん。
その分すこし値段が高めな印象。CircleCIやTravisCIと並列数のコスト面で劣る部分だと思います。本格的にバリバリ使うなら、今ならCircleCIが正解な気がする。
特定のCI依存は避けたい。そのためには、ワークフローは独自のスクリプトなどで用意しておきたいところ
この業界、デファクトスタンダードがどんどん変わるので

Bitrise
https://www.bitrise.io

Bitrise CLI

https://app.bitrise.io/cli
Bitrise CLIは単体で動作する。
BitriseはBitrise CLIを実行できる環境を提供している。
ワークフローの一部としてfastlaneを使うことも可能(面白い)
Travis + Bitrise CLI 構成もできる。

$ brew install bitrise
$ bitrise init

Slack Command

Slack (OUTGOING WEBHOOK) —> Bitrise (INCOMING WEBHOOK)

BitriseでIncoming Webhook用URLをコピーしたものをSlackのOutgoing Webhookで利用すれば、すぐ利用可能。

Manual
https://github.com/bitrise-io/bitrise-webhooks#slack—setup–usage

Remote Access

何らかの理由で失敗したときに、リモートアクセスを有効にして再実行が行える。
リモートアクセス機能自体は ngrok というサービスを利用している。
https://ngrok.com
AndroidならSSHで、iOSならVNCによるリモートアクセスもできる。
リモートでMacのGUIが表示されるのは面白い。
ワークフローが期待通り動かない時のデバッグに使えます。

Android

生成したapkをfabric betaとAmazon S3 にアップロードするところまでのメモ。

配布用のkeystoreは予め、Workflow Editor の Code Signing にアップロードしておきます。
$BITRISEIO_ANDROID_KEYSTORE_URL が用意されるので、File Downloaderをワークフローに追加してbuild.gradleで読み込んでいるPathに配置

debug.keystoreはリポジトリに登録しておくと楽です。
パスワードは android

$ keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 10000 -dname "CN=Android Debug,O=Android,C=US"

Android SDK License は設定不要。
/opt/android-sdk-linux/licenses にすでにある

すでに独自のスクリプトがいくつか用意されているなら、それを使ったほうが良い。

ビルドしたアプリの採番には、Bitriseのビルド番号を利用することができる。
ビルドバージョンのオフセット方法

謎な部分

fabric betaへapkをアップロードするときに、apkが無いとエラーが出た。
もともと設定していたapkの出力先(/bitrise/src/app/build/outputs/apk/dev/release/app-dev-release.apk)には無く、Bitriseが用意したディレクトリ(/bitrise/deploy)に移動していることが判明。
ビルドとfabric betaへのアップロードを同一のフローで行わず、間に元の出力先に戻すmove処理をいれたら解決した。(ワークアラウンド)

iOS

基本的なビルド知識

$ xcodebuild -workspace xxx.xcworkspace -scheme dev -configuration AdHoc-Dev

Code Signing

Bitriseの仕組みに乗っかりました。
以下のコマンドで、対話形式で必要な証明書類をBitriseにアップロードしてくれます。
ビルド処理が入るため、まぁまぁ時間かかります。
途中アップロードするためにトークンが必要になります。
以下のURLで1時間限定トークンを発行して使いましょう。
ttps://app.bitrise.io/me/profile#/security

$ bash -l -c "$(curl -sfL https://raw.githubusercontent.com/bitrise-tools/codesigndoc/master/_scripts/install_wrap.sh)"

Encrypt

セキュアな情報は暗号化しましょう。
pwgenはパスワードを生成します。gpgは暗号化します。
暗号化したファイルをリポジトリにコミット、BitriseのWorkflow Editor からSecretsタブで生成したパスワードを登録。
Decrypt fileワークフローを使って、復号化しましょう。

$ brew install pwgen 
$ pwgen -s 22 1

$ brew install gpg
$ gpg -c xxxx.json

Libraries

nodeやnpmなど一部のソフトウェアはすでにインストールされています。
anyenvなどを使って動作保証されたバージョンを使うようにすると良いです。
その際.node-versionなどのファイルを.gitignoreで除外しないように注意。

env

複数のCIを使っている場合、CIサービス固有の環境変数は、サービスに依存しない抽象的な名前にしておくと後々引っ越しの時や併用の時に楽です。

$ envman add --key GIT_BRANCH --value "${BITRISE_GIT_BRANCH}"

xcodebuildのoptionからbuild phase scriptに独自引数を渡せるのか
渡せる。以下で確認可

$ xcodebuild GIT_BRANCH=$USER -showBuildSettings

for Simulator

AppiumでE2EテストをSimulatorに対して行う場合、Simulator用のアプリを用意する必要があります。実機であれば
でXcode Archive & Export for iOSを使いますが、Simulatorの場合はXcode build for simulatorを使います。
その後のワークフローでtar gz圧縮を行いAmazon S3にアップロードすると良いでしょう。(シェル後述)
https://discuss.bitrise.io/t/build-ios-app-for-simulator-and-deploy-to-appetize-io/5622

以下のシェルで、ビルドされたアプリをいい感じに圧縮してくれます。
tarってフォルダ構成も保持されるので、cdコマンドで調整してたりします。

#!/usr/bin/env bash
set -ex

cd $BITRISE_APP_DIR_PATH
cd ..
BUILD_DIR=`pwd`
tar czvf simulator.tar.gz brooklyn.app

envman add --key ZIPPED_APP_FILE --value "$BUILD_DIR/simulator.tar.gz"

Amazon S3へアップロード

バケットと読み書きできるアクセスキーとシークレットキーを用意し、
アプリのビルド番号と組み合わせたファイル名で、アップロードされるように設定。
ディレクトリを事前に作るといったことは不要(ObjectIDという概念)

アップロードしたファイルをダウンロードするのは以下のような感じ

require 'bundler'
Bundler.require
Aws.config.update({
                      region: 'ap-northeast-1',
                      credentials: Aws::Credentials.new('AAAAAA', 'BBBBBB')
                  })
s3 = Aws::S3::Resource.new(region: 'ap-northeast-1')
obj = s3.bucket('バケット名').object('android/123/develop.apk')
obj.download_file("android.apk")

Bitrise利用を通して

全ての機能を使いこなせたわけでは無いのとUIへの慣れは必要ですが、一通り動かせるようにする間にだいぶ慣れます。
bitrise.ymlがBitriseCIでも利用されるととても良いので今後に期待します。

ロードマップを公開するのってすごく良いですよね。
https://blog.bitrise.io/bitrise-in-2019-a-roadmap-preview

余談

CircleCI の Performance Pricing Plan なんてものを発見。
従量課金で並列数無制限らしい。
https://www.patreon.com/posts/circlecinoxin-ti-23842006

署名の付け替え、これはやっていきたい
https://qiita.com/beakmark/items/33c0b73603e491f08a33

Fabric betaはFirebase App Distributionとして生まれ変わるらしい。
はよ。

最後に

Bitrise Tシャツ欲しいので、まだの方は、ぜひここから登録してくださいね
https://app.bitrise.io/referral/f9dec5c40d5048ee

Apple製品全てを、WWDC2019で開発者に公開された、betaに上げるメモ

2019/07/09現在、OTAアップデート出来るプロファイルが配布されているので以下はいらない手順ですが、、

プロファイルダウンロードURL
https://developer.apple.com/download/#ios-restore-images-ipad-new

macOS Mojave で iOS13 を入れるためのファイル
私の場合、macOS Catalina beta 1のインストールが出来なかったのでこれを利用しましたが、今は無くても大丈夫
https://www.dropbox.com/s/fxd29ndf7xx1lgn/MobileDevice.pkg?dl=0

  • iPhoneSE用プロファイル
    • macOSにダウンロード
    • 事前にiPhoneをバックアップ
    • iTunes起動してOption押しながらアップデートボタン押下
  • Watch
    • iPhoneからプロファイルインストール
    • Watchアプリを起動してソフトウェアアップデート
  • iPad mini
    • iPhoneと同じ
  • AppleTV
    • プロファイルインストール mac
    • AppStoreからApple Configurator2インストール
    • 同じWiFiでXcodeからペアリング
    • AppleTVは設定のリモートデバイスを開いてMacを選択してペアリング
    • プロファイルをApple Configurator2上に出てきたApple TV にD&D
    • プロファイルがインストールされます。

以下はApple TVとペアリングし、プロファイルをインストールするまでのスクショ

Xcode Devicesを開く

AppiumでiOSアプリの起動しない時にやったこと+おまけ

環境

Appium 1.12.1
Ruby 2.6.3
iOS 12.1)

一通りの環境構築は済み、appiumリポジトリで公開されている TestApp.app.zip は起動することを確認できた。
あとは目的のアプリに差し替えるのみだったが、そこから予想外の苦戦を強いられた。(私の理解不足)
問題は、アプリ起動直後にクラッシュしてしまうこと。

Appiumのエラーログ

[Xcode] 2019-05-14 12:14:14.305381+0900 WebDriverAgentRunner-Runner[54539:1740041] Enqueue Failure: Failed to launch {package name}: The operation couldn’t be completed. (FBSOpenApplicationErrorDomain error 1.) /Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent/WebDriverAgentRunner/UITestingUITests.m 37 1


https://github.com/facebookarchive/WebDriverAgent/blob/master/WebDriverAgentRunner/UITestingUITests.m

iOS Simulator か .app の Architecture に問題があるとはすぐにわかったが、
勘違いから解決するまでの道のりが少々長かった。

結論から言うと、Archive する必要はありません。iOS Simulator でデバッグ実行した .app を使うか、 xcodebuild コマンドでビルドした .app を使えば動きます。ただそれだけ。

ハマった内容として、Archive 後に Distribute App を行い Export した ipa ファイルが arm64になっていて、iOS Simulator で実行出来ない。
どうやって x86_64 を Archive 時に含めようか Build Config を試行錯誤して消耗してしまっていた。
そもそも、x86_64 アーキテクチャを AppStore で配布は出来ないので、Archive の段階で、Unsupported Architecture を言われます。

Unsupported Architecture が出たときの対応方法として、 lipo コマンドを駆使して x86_64 を除いた実行ファイルを作る回答があって、まったく真逆のことをしていたことになる。
https://stackoverflow.com/questions/30547283/submit-to-app-store-issues-unsupported-architecture-x86

勘違いした理由は、Appiumの理解が浅かったから。

iOS Simulator で動くアプリを用意する

# 利用できるSDKの中から、iphonesimulatorを探す。
$ xcodebuild -showsdks           
2019-05-15 00:29:08.697 xcodebuild[9847:2715491] [MT] DVTPlugInManager: Required plug-in compatibility UUID D7881182-AD00-4C36-A94D-F45FC9B0CF85 for GraphQL.ideplugin (com.apollographql.xcode.graphql) not present
iOS SDKs:
        iOS 12.1                        -sdk iphoneos12.1

iOS Simulator SDKs:
        Simulator - iOS 12.1            -sdk iphonesimulator12.1

macOS SDKs:
        macOS 10.14                     -sdk macosx10.14

tvOS SDKs:
        tvOS 12.1                       -sdk appletvos12.1

tvOS Simulator SDKs:
        Simulator - tvOS 12.1           -sdk appletvsimulator12.1

watchOS SDKs:
        watchOS 5.1                     -sdk watchos5.1

watchOS Simulator SDKs:
        Simulator - watchOS 5.1         -sdk watchsimulator5.1

# iOS Simulator向けにビルドする
$ xcodebuild -arch x86_64 -sdk iphonesimulator12.1 -workspace xxxx.xcworkspace -scheme xxxxx

ビルドに成功すると、DerivedDataフォルダに出力されるので、それを Appium 使えば良いです。

...
CodeSign /Users/yukitamazawa/Library/Developer/Xcode/DerivedData/xxxxx-aanbtbipxyjnexeibvxbsgaqyiqx/Build/Products/Debug-iphonesimulator/brooklyn.app
...
** BUILD SUCCEEDED ** [299.993 sec]

無駄に遠回りしてしまったのでした。

参考リンク

調査に協力してくれた @daidongon 、ありがとう!

おまけ:Xcode 関連のキャッシュをごっそり消すシェル

何度もTry & Error を繰り返す中で、 MacBook Pro の容量が逼迫することが何度もあったので、1発でごっそりキャッシュを消せるシェルを用意しました。
https://github.com/srea/dotFiles/blob/master/clear_cache.sh

# Xcode

echo "Xcode"
rm -rf ~/Library/Caches/com.apple.dt.Xcode/
rm -rf ~/Library/Developer/Xcode/DerivedData/
xcrun --kill-cache
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/*/Symbols/System/Library/Caches 
rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache"
rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache"
rm -rf ~/Library/Caches/SwiftLint
pod cache clean --all
rm -rf ~/Library/Caches/org.carthage.CarthageKit
rm -rf ~/Library/Caches/carthage

# Homebrew

echo "Homebrew"
rm -rf $(brew --cache)

第1章:クロスプラットフォームモバイルアーキテクチャ RIBs とは

まだ情報の少ない RIBs 紹介するシリーズです。(予定)

https://github.com/uber/RIBs

RIBs

  • 第1章 RIBsとは
  • 第2章 各コンポーネントの役割と RIB 同士の接続( Attach / Detach )(予定)
  • 第3章 Stream を使った RIB 同士のコミュニケーション方法(予定)
  • 第4章 WorkFlow で見通し良く URLScheme / UniversalLink を実装(予定)
  • 第5章 RIB ツリーの設計方法 (予定)

第1章

この記事では、RIBs の概要を説明します。

README.mdWiki の内容を抜粋した記事になります。
完全な和訳ではないことと私自身が 100% 理解仕切っているわけでは無いため、理解が間違っている箇所がある可能性がります。
記事を読んで少しでも興味が湧いた方は、本家のドキュメントをご覧になってください。

RIBs とは

Uber が開発したアーキテクチャで、フレームワークとして提供されています。
Router, Interactor, Builder というコンポーネントの接頭辞を取ったものです。
多数のエンジニアチームやネストされた状態を持つアプリ向けに設計されています。

初回コミットは 2017/10/4 で、比較的新しいアーキテクチャです。

We re-wrote the Uber application in 4 months with 200 engineers.

Uberは200名のエンジニアで4ヶ月掛けて書き直したようです。

特徴

スケーラブル

Uber では502名のモバイルエンジニア、650 RIBs まであり、1クラスは300行以内。 (資料)

クロスプラットフォームコラボレーション

アプリのビジネスロジックのほとんどは、 iOS も Android も似ています。 RIB を使用することで、共同設計されたアーキテクチャを共有できます。
またビジネスロジックのクロスレビューが可能とUberは申しております

グローバルな状態を最小限にする

グローバル変数やシングルトンオブジェクト等の状態変化は予測できず、予期せぬ動作を引き起こす可能性が高く、修正時の影響範囲を完全に把握することは困難です。 RIBs は深い階層内に状態をカプセル化し、グローバルな状態で起こり得る問題を極力回避できます。

テストが容易

クラスは単体テストが容易である必要があります。
RIB は 責任範囲が明確で親RIBと子RIBはインターフェースに依存した形でロジックは切り離されています。
その結果、テストが容易になります。

以下参考

InterfaceImplementation
BuildableBuilder
RoutingRouter
Interactable / PresentableListenerInteractor
ViewControllable / PresentableViewController

オープンクローズドの原則

開発者は既存コードをほとんど変更せずに新機能を追加できます。
具体的に言うと、親RIBに子RIB(新機能)を追加する場合、子RIBの依存関係の解決とAttach/Detach処理、子RIBのListenerに準拠することで追加が可能です。

ビジネスロジックを中心に構造化

ビジネスロジックと画面構造は厳密に合わせる必要はありません。
画面階層はRIB階層よりも浅いことがあります。
1つのRIBが、異なる場所に表示されている複数のViewの外観を制御できます。

(*個人的にまだしっくり来ていない箇所です)

明示的

複数RIBにまたがる依存は、ReactiveX を使用して表現され、RIB毎に DI を使用することで、依存関係が明確になりコンパイル時に保証されます。
不変量の作成も促されます。

開発用のツール

  • 大規模チームの生産性向上のためのツールが付属 (一部Androidのみ)
    • コード生成テンプレート
    • メモリリーク検出 ( Attach / Detach )
    • 静的解析 ( Android のみ)

RIBs と MV* / VIPER の違いは何か

MVC、MVP、MVI、MVVM、VIPER はアーキテクチャパターンです。
RIBs はフレームワークです。

違いは以下の通り

  • ビジネスロジックに主眼を置く
    • RIB は View を持つ必要は必ずしもありません。アプリケーションの階層は画面ではなくビジネスロジックによって決まります。
  • 独立したビジネスロジックとビューツリー
    • RIB は 画面とビジネスロジックを切り離します。その結果、深いビジネスロジックツリーを持ち、浅いビュー階層を保持したまま、レイアウト・アニメーション・トランジションを容易にします。

以下については他の MV* / VIPER でも実装することが出来ますが、他にもいくつか特徴があります。

  • クロスプラットフォーム
    • iOS と Android で RIBs を採用することが出来ます。
  • 大規模なアプリやチームで導入を容易にするツールが付属します。
    • コードテンプレート
    • 静的コード解析のための IDE プラグイン (まだ無いかも?)
  • DI / Rx を使って依存関係を解決
    • 各 RIB は依存関係を定義しており、親子関係のある RIB の場合、子の依存は親から提供されます。

ドキュメント

GitHub リポジトリの Wiki に詳細な説明が書かれています。
この記事を読んで少しでも興味が湧いた方は、ぜひ読んで見ましょう。

https://github.com/uber/RIBs/wiki

RIBs を採用したアプリ

参考リンク

Slack

https://uber-ribs.slack.com

こちらで招待を受けることが出来ます。
https://uber-ribs-invite-automation.herokuapp.com

Uber Blog

https://eng.uber.com/tag/ribs/

Slideshare

Speaker Deck

Vimeo

Youtube