ねこ島

iOSのこととか、日常について雑に書きます

M1 Macへの移行作業ログ

M1 Mac miniを買ったのでiMac 5Kから移行作業をしている。
主にiOSアプリ開発周りで引っかかったところを挙げていきます。

pod installに失敗する

CocoaPods自体はインストールに成功するが問題はその先で、pod install時に以下のように怒られる

/src/vendor/bundle/ruby/2.6.0/gems/ffi-1.13.1/lib/ffi.rb:6:in `require': dlsym(0x7fcdf6710400, Init_ffi_c): symbol not found - /src/vendor/bundle/ruby/2.6.0/gems/ffi-1.13.1/lib/ffi_c.bundle (LoadError)

どうやらffi側の修正対応が必要そうで、issue見るとワークアラウンドがコメントされていた。 https://github.com/CocoaPods/CocoaPods/issues/9907#issuecomment-749389632

arch -x86_64 pod install

これで動いた。ちなみにTerminal自体をRosettaで開いても同じ挙動になる。

(WIP)CarthageとXCFramework対応

これが一番めんどくさかった。iOS Simulatorがx86_64向けにビルドされている既存のframeworkではビルドに失敗する。 解決するためには最近Carthageが対応したXCFrameworkに移行する必要がある。

なぜ移行する必要があるのかというと、今までiOS Simulator(x86_64), iOS(arm64)それぞれのアーキテクチャを元にfat binaryを生成していたが、M1 Macの登場によりiOS Simulator(arm64), iOS(arm64)にアーキテクチャが変わった。この影響で同じアーキテクチャ同士競合してしまい、fat binaryの生成に失敗する。

XCFrameworkはXcode11から用意された新しい形式で、サポートしているプラットフォーム毎のFrameworkを一つにまとめることができる。
プラットフォーム、というところがキモで、アーキテクチャの制約を受けないのでM1 Macでも問題なく動作する。

メリットもあって、CarthageだとBuild Phaseで実行していたcopy-frameworksも不要になる。AppStoreに提出するときにiOS Simulator向けのアーキテクチャをFrameworkから取り除く必要がなくなったためで、これによりビルドが更に早くなる。そういった点ではM1 Macを持っていようがいなかろうが、早めに対応するのが吉かもしれない。

前置きが長くなったが、Carthageでの対応自体は簡単で、オプションの--use-xcframeworksを付与してあげるだけ。

carthage bootstrap --platform iOS --cache-builds --use-xcframeworks

これをやるとXCFrameworkがCarthage/Buildの中に作られる。pre-builtされたFrameworkはCarthage/Build/iOSに入っていた。

次にXCFramework化されたライブラリをBuild Phasesで実行しているcarthage copy-frameworksから落としていくという作業が必要になる。これは手作業で行う。

XcodeGenを採用している場合、XCFrameworkはframeworkを使って指定する。
carthageを指定するとcopy-frameworksに追加されてしまうので今は使わないように。

dependencies:
  - framework: "Carthage/Build/APIKit.xcframework"
  - framework: "Carthage/Build/Auth0.xcframework"
  - framework: "Carthage/Build/Lottie.xcframework"

XcodeGen側でもXCFramework対応が進められているので、そのうちこの対応は不要になると思う。 github.com

pre-builtされたFrameworkがまだXCFramework対応されていなかったのでいったん作業はここまで。

(番外編)iCloud Driveが同期しない

おそらくBig Surの問題。 初期設定後いつまで経ってもiCloud Driveの中身が同期されなかった。

まず確認すべき点は同期の対象になっているかどうか。設定→iCloud Driveのオプションを選択して、デスクトップフォルダと書類フォルダにチェックが入っていることを確認する。 筆者の環境では入っていなかったので注意してほしい。

f:id:nekowen:20210223204618p:plain
iCloud設定

それでもダメな場合、brctlコマンドで現在のSync状態を確認と診断を行うと改善する可能性がある。問題なさそうだが念の為自己責任でお願いします。
以下はRedditで紹介されていたコメントを引用する。

まずSync状態を確認する。

brctl status

ログの上部の方に何かエラーが出ていないか確認する。筆者の環境では>>> BROKEN STRUCTUREと出ており、詳しい原因はAppleのサポートに聞かないとわからないが"何かがおかしいです"状態になっていた。iCloudの情報を保持するDBが壊れてるとかなのか?

次に診断を行う。ログを収集するけど良いか?と聞かれるので返答。

brctl diagnose

そうするとiCloudの管理するファイルやシステムなど色々なログが取られてzipに吐き出される。このファイルは使わないので削除してOK
診断を行うこと自体に意味があるらしく、診断後はBROKEN STRUCTUREの文字がなくなりiCloud Driveの同期が行われるようになった。

iCloudわからーん!

余談

M1 Mac miniを使ってみての感想だが、どの処理を行うにしても動作がスムーズで気に入った。移行元がiMac 5KのFusion Driveという点でそもそもSSD onlyとSSD + HDDでは比較にならないというのはあるかもしれないが…
そして移行作業も思ったほど大きな障壁はなさそうに感じた。M1 Macの販売から3ヶ月くらい経っているのでその間に色々なOSSやソフトウェアが早急に対応してくれていた点も大きい。 少なくとも開発環境においてはM1 Macは実用的なものになっているんじゃないかなと思っている。
ちなみにXcodeRosettaで動かせば従来のビルドとして走るのでフレームワークの対応が辛すぎる場合はRosettaメインに切り替えるのもアリ。シミュレータもx86_64で動く。ただネイティブの恩恵は得られなくなるけど。

Among Usで遊んだ

最近界隈で絶大な人気を誇る「Among Us」
以前から友人とちょこちょこプレイしていたのだが、最近前職の方達ともプレイする機会があったので思ったことを書いてみる。

Among Usとは

Among Usは、4人からプレイできるシンプルな人狼ゲーム。宇宙をテーマとしており、「宇宙人狼」とも呼ばれている。 対応プラットフォームはPC(Steam)/Switch/iOSAndroid
個人的にはPCでのプレイを推奨する。キーボード操作なのでキャラクター移動がやりやすいこと、後述するBotの導入が可能になるためだ。

store.steampowered.com

プレイヤーはゲーム開始時にランダムにクルーメイト(村人)とインポスター(人狼)の役割を割り当てられる。これは他の人には通知されない。

クルーメイトは用意されているタスクをすべて終わらせるか、インポスターを全員追放すれば勝ち。
インポスターはタスクが終わる前にクルーメイトをKillしてインポスターと数を同じにするか、特定の妨害タスクを時間切れに追い込めば勝ちになる。

プレイヤーが死体を発見して通報したとき、あるいは緊急ボタンを押すことで緊急会議を始めることができる。 ここで誰が死体を発見したか、そのとき何をしていたかなどを話し合い、疑わしいプレイヤーに投票して追放していく。ちなみにスキップも可能だ。

コミュニケーションの必要性

Among Usに限らず人狼系ゲーム全般にいえるかもしれないが、都度会議が発生するため他のプレイヤーとうまくコミュニケーションをとる必要がある。

f:id:nekowen:20201227135322j:plain
会議中の様子。このとき僕はKillされているので会議には参加できずみてるだけ。

黙っていればインポスターとみなされてしまうので、自分の行動履歴を皆に明確に伝えなければならないし、さらに自分がインポスターだった場合、「○○さんと一緒にいましたよね?」と周りにシロっぽくみせる必要もある。つまり話し方のスキルも問われてくる。

また会議時間もデフォルトでは2分ちょっととなっており、あまりだらだらと話している余裕はない。 誰かがリードをとってうまい時間の使い方をしないと無駄話で終わってしまい、そのままスキップ…ということもある。

他の方達とプレイした時は、全員の最終位置を聞き込み、互いにシロ証明ができればそこは除外、じゃあ怪しい人は?と進行がスムーズだった。手慣れておる。

事故も起きる

このゲームは実際にキャラクターを操作するので、Killするタイミングや場所によっては偶然通りがかったクルーメイトに事件現場をみられてしまうこともある。

これが起きると自分の罪を認めるプレイヤーもいるが、粘り強いプレイヤーは通報したクルーメイトをセルフレポート(自分でKillして自分で通報すること)だと言い張り、互いに生死をかけた議論を始めることになる。

こうなると他のプレイヤーは二人のうちどっちが嘘をついているか?を判断しなければならないので、お互いの証言から見極める必要がある。
特にこの会議で一人インポスターを吊らないと負ける場合なんかは、自分の選択次第で勝敗が決まるのでプレッシャーがヤバイ。そして大抵間違った選択をしてやらかす。

まとめ

モバイルからでもできるAmong Usは気軽にやれてワイワイできるので、なかなか外に出ることに抵抗感があるこのご時世にはちょうどよいゲームだと思った。

前職のゲーム会にもお邪魔させてもらったが、普段あまり話さない方ともガッツリ話す機会にもなったのでコミュニケーション不足を感じているなら是非Among Usを勧めたい。

(Tips)AutoMuteUsの導入

Among Usの会議にはDiscordの使用が推奨されている。

会議の間はマイクをオン、それ以外はオフにするという運用を各プレイヤーが行うわけだが、手動ゆえ、時々マイクがオンになったままという事故(ミュート芸とも言われる)が起きる。

このようなトラブルを避けるため、Among Us向けの自動ミュートBotOSSで公開されている。

github.com

Botを動かすには用意されているサーバーを使うか、セルフホストの2つの選択肢がある。

前者はサーバーが混んでいる時間にプレイをするとミュートが行われるまでにディレイが発生しやすくなるため、現状はセルフホストの方が良いと思う。

セルフホストもDockerを使う方法か、Windows環境であればDockerを使わずに実行ファイルを起動するだけで使えるのでそこまで難易度は高くない。

今のところ友人のDiscord鯖で運用しているが、ミュートする手間がなくなりなかなか快適なのでこれを使わずしてAmong Usができない体になってしまった。
プレイする際はついでに導入することもオススメしたい。

株式会社Kyashに入社しました

この記事はKyash Advent Calendar 2020 - Adventar 15日目の記事です。

Kyash iOSエンジニアの@nekowenです。
Kyashには11月に入社してもうすぐ1ヶ月となります。
この記事では僕がKyashに入社した経緯と、入社してから思ったことを書いていきます。

続きを読む

GoogleMaps+CarthageをXcodeGenで扱う

いつの間にかGoogleMaps SDKをCarthage経由でいれられるようになっていました。
個人的にCocoaPodsで管理するライブラリを減らしたいと思っていたところなのでこれは助かりますね。

developers.google.com

上記リンクに書いてあるとおりにプロジェクトファイルをいじればOKなのですが、XcodeGenを使っている場合はプロジェクトファイルではなく設定ファイルを書き換える必要があります。

追記した内容としてはこんな形です。

targets:
  HogeHogeApp:
    type: application
    platform: iOS
    sources:
      - path: HogeHogeApp
      - path: Carthage/Build/iOS/GoogleMaps.framework/Resources/GoogleMaps.bundle
        optional: true
        type: folder
    dependencies:
      - carthage: GoogleMaps
        embed: false
      - carthage: GoogleMapsBase
        embed: false
      - carthage: GoogleMapsCore
        embed: false
      - sdk: libc++.tbd
      - sdk: libz.tbd
    settings:
      base:
        OTHER_LDFLAGS: $(inherited) $(OTHER_LDFLAGS) -ObjC

それぞれ追記した内容の説明。

1. GoogleMaps, GoogleMapsBase, GoogleMapsCoreのリンク

    dependencies:
      - carthage: GoogleMaps
        embed: false
      - carthage: GoogleMapsBase
        embed: false
      - carthage: GoogleMapsCore
        embed: false

Carthageでビルドするとこれらのフレームワークを吐き出すので、dependenciesに指定します。
プレミアムプランを利用している場合はGoogleMapsM4Bの追加も必要です。

2. libc++.tbd, libz.tbdのリンク

    dependencies:
      ...
      - sdk: libc++.tbd
      - sdk: libz.tbd

依存関係の解決として必要になるのでこれも指定します。

3. Other Linker FlagsにObjCを追加

    settings:
      base:
        OTHER_LDFLAGS: $(inherited) $(OTHER_LDFLAGS) -ObjC

ドキュメントに記載があったのでそのまま追加。

4. GoogleMaps.bundleをワークスペースに追加

    sources:
      - path: HogeHogeApp
      - path: Carthage/Build/iOS/GoogleMaps.framework/Resources/GoogleMaps.bundle
        optional: true
        type: folder

こちらもドキュメントに記載がありますが、この作業を行わないとビルドは通っても起動時にGoogle Maps SDKが例外を吐いて落っこちますので必ずやりましょう。

iOS14から追加された「App Attest API」でアプリの不正使用を防ぐ

引き続きiOS14ネタとなります。

今回は、DeviceCheckフレームワークに新たに追加された「App Attest API」についてみていきます。

本記事は公開済みのドキュメントを元に作成しています。今後仕様変更などにより記載内容と異なる場合があります。予めご了承ください。

App Attest APIとはなにか

App Attest APIは雑にいうとアプリの不正使用をサーバー側で防ぐAPIです。

Appleバイスが生成した証明書とサービスアカウントの紐づけを行い、サービス利用時にそのAppleバイスだけが持つ秘密鍵で署名したデータをサーバー側で検証することで、サーバーへアクセスするデバイスを確実にできます。

また検証の際に署名やApp IDの確認が行われるため、サイドローディングされたアプリや改造アプリに対しても効果を発揮します。

サイドローディング... ストア(App Store)を経由せずにアプリを入手、インストールすること。iOSではデベロッパーアカウントを使って再署名することで可能となる。Androidでいう野良アプリのインストール。

さらにAppleが公開しているREST APIを呼ぶことで、不正行為を行っている可能性を評価したパラメータが取得できます(この評価はデバイス上の一意の証明書のおおよその数を元にしているようです)。

ただしJailbreakによってiOS自体に改変が加えられている場合、確実に不正使用を防げるわけではないため、不正対策の一環として考えておいた方が良いです。

どういったときに使えるのか

以下のようなサーバー側で制御ができるコンテンツに対して効果的です。

  • 課金した人が使えるコンテンツへのアクセス
  • オンラインゲーム

また実際に不正が確認された場合、DeivceCheckのAPIを使うことでより強力なハードウェアBANを行うことができます。

詳しくはこちら

qiita.com

どのような仕組みか

f:id:nekowen:20200810204436p:plain

ほぼWebAuthnの仕組みに沿っており(といってもWebAuthnを知ったのがつい最近なので違っていたら教えて欲しいです‥🙇‍♂️)

流れとしてはこんな形です。

  1. 初回起動またはアカウント作成時にキーペアを生成し、サーバーサイドに送ります。
  2. サーバー側は送られてきたデータから検証を行い、問題なければ公開鍵とレシートをユーザー情報と紐付けます。
  3. クライアントは認可が必要なAPIにアクセスするタイミングで署名を行ったデータを一緒に送信します。
  4. サーバー側は送られてきたデータから検証を行い、問題なければ通信成功とします

実装方法(クライアント)

1. サポートされているかどうかの確認

まずはAPIが使えるデバイスかどうかを確認する必要があります。

サポートされているかどうかはDCAppAttestServiceのisSupportedを確認します

let service = DCAppAttestService.shared
if service.isSupported {
    // サポートされている
}

2. キーペアの生成と検証を行う(インストール・アカウント作成時のみ)

クライアント側でgenerateKey(completionHandler:)を呼びキーペアを生成します。 キーペアは生成と同時にSecure Enclaveに保存されており、アプリ側から読み取ったり変更はできません。

keyIdが引数に渡されますが、これは生成されたキーペアに紐づいており、今後署名するときに必ず使うのでキーチェーン、あるいはファイルに保存します。

service.generateKey { keyId, error in
    guard error == nil else {
        //  Error
        return
    }
    
    //  Save KeyId
}

次に、サーバーからChallengeを受け取り、クライアント側でSHA256のハッシュ値を生成します。

let hash = Data(SHA256.hash(data: fromServerData))

keyId・ハッシュ値が揃ったら、attestKey(_:clientDataHash:completionHandler:)を呼びキーペアが有効であることをAppleに検証してもらいます。

成功するとDate型のAttestation Objectが渡ってくるのでこれをサーバーに送ります。

service.attestKey(keyId, clientDataHash: hash) { attestation, error in
    guard error == nil else {
        return
    }
    
    let attestationBase64 = attestation.base64EncodedString()
    // Base64に変換するかバイナリとしてそのままサーバーへ送る
}

※ちなみに、iOS14 beta 3まではこのメソッドを呼ぶと必ずエラーが帰ってきていました。beta 4にアップデートしたところ改善したので古いバージョンをお使いの場合は注意してください

サーバー側は送られたAttestation Objectを検証し、問題がなければ公開鍵とレシートをアカウントに紐づけてキーペアの生成は完了です。

3. アプリの有効性を検証する

全ての通信、あるいはビジネス上不正が起こっては困る通信に対してクライアント側で署名を行ったオブジェクトを送信します。

署名にはgenerateAssertion(_:clientDataHash:completionHandler:)を呼びます。

service.generateAssertion(keyId, clientDataHash: hash) { assertion, error in
    guard error == nil else {
        return
    }
    
    let assertionBase64 = assertion.base64EncodedString()
    // Base64に変換するかバイナリとしてそのままサーバーへ送る
}

ここで得られたAssertion Objectをサーバーに送ります。 サーバー側は送られたAssertion Objectを検証し、問題がなければ処理を進められます。

サーバー側の実装・仕様はAppleのドキュメントがあるのでこちらをどうぞ。

https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server

参考

https://developer.apple.com/documentation/devicecheck

https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity

engineering.mercari.com

iOS14で戻るボタンに長押しアクションが追加された

iOS14では、UINavigationBarの戻るボタンを長押しすることでスタックされているViewControllerの一覧がメニューとして自動的に表示されるようになります。

f:id:nekowen:20200802004222p:plain

iOS13までは戻るボタンを押すと、一つ前の画面にしか戻ることができませんでしたが、この機能により、ユーザーが戻りたい画面を自由に選択することができるようになりました。

メニューに表示されるタイトルは、以下に設定されているタイトルから自動で選ばれます。

  • .backBarButtonItem.title
  • .backButtonTitle
  • .title

何も設定されていない場合、「戻る」がメニューに表示されます。

また現状この機能は無効にすることができませんので、戻るボタンを自作していない限りは必ず表示されます。

既存アプリの対応について

特別な対応をする必要はほとんどありませんが、アプリによってはいくつか確認すべき点がありました。

titleViewを使っていないか?

titleに直接タイトルを設定せず、代わりにtitleViewを使用しているケースがあると思いますが、この場合メニューにタイトルが設定されません(戻るになる)。

WWDCのセッションでは、この場合backButtonTitleの使用を検討して欲しいと説明されています。

backButtonTitleはiOS11からのプロパティとなるため、iOS10以下をサポートしている場合はbackBarButtonItemを使用しましょう。

戻れてはいけない画面がメニューに表示されていないか?

少なくとも僕の周りでこのような実装をしているアプリは見たことがないのでレアケースだと思うのですが、いろんな事情で特定の画面に戻れないように実装している場合。

コード的にはhidesBackButton = trueが存在している場合です。

戻るボタンを隠していたとしても、その先の画面で戻るボタンが表示される機会があり尚且つスタックに積まれているとメニューから戻れてしまいます。

その場合はOSが提供する戻るボタンを使用しないか、UINavigationControllerのviewControllersから直接Viewを取り除くという荒技がありますが、推奨しません。

最後に

上記に当てはまらない場合でもQAは必須です。タイトルがおかしくなっていないか?想定しない画面は入ってないか?この辺りは最低限見たほうが良いです。

画面数が多いと大変ですが、予期せぬ不具合を起こさないためにもやっていきましょう。

VRで遊ぶときやヘッドホン装着時にお勧めのメガネ「Short Temple」を買った

僕は普段メガネをしているのだが、長時間ヘッドホンをしていると耳が痛くなってしまう。

耳にひっかけているテンプルがヘッドホンによって押さえつけられてしまうことが原因なのだが、メガネ着用者あるあるだと思う。

この問題を解決する方法としてはコンタクトにするか、メガネを斜めにかけるか、そもそもヘッドホンを使用しないなど…いろいろあるのだが、どれも決定打に欠ける。

もうちょっと快適なヘッドホン生活ができないかと調べていたら、こんな商品をみつけた。

www.jins.com

普通のメガネに比べてテンプルが短いのが特徴。

短いと当然耳には引っかけられないので、こめかみに挟んでメガネを固定している。なるほど、確かにこれならヘッドホンと被らないし、使えるかも!?と思い購入してみた。

普通のメガネに比べると小さいし、おもちゃか?と思ったのだが、装着すると案外悪くない。

ちゃんと固定できているし、下を向いても落っこちない。ちょっと踊ってみても落ちない。

ただ、人を選ぶと思う。

特に頭が大きい人は押さえつけられている感じがきついかもしれない。

あと、このメガネを汎用的に使おうと考えている人もきついかもしれない。

いくらこめかみで固定しているとはいえ、安定性はノーマルなメガネのほうが100倍良いので、このメガネをかけたまま走ったりするとこのホラーゲーム並みにメガネを落とすことになる

store.steampowered.com

www.youtube.com

僕はヘッドホン、VRで遊ぶ時のみこのメガネを使っており、逆にそれ以外で今まで使っていたメガネを着用している。

ということで、ヘッドホンをしていて耳が痛くなる人、VRやっててメガネきつい!ってなってる人、「Short Temple」おすすめですよ!