参加の経緯
ゆめみのインターン(iOS)に参加したところ「お前もiOSDCに行きたくないか?スポンサーチケットをくれてやる。交通費とホテル代も出してやる。」(意)*と言われました。
条件は参加体験記を書くこと、学んだ内容をあとでゆめみが開催する勉強会でLightning Talkすること、の2点でした。他人のお金で行く焼肉と勉強会は生活の質を大幅に向上させるため、二つ返事で了承し、この度iOSDC2023に参加させていただきました。
この記事はその体験記です。
*なおゆめみのiOSDCスカラーシップには人数制限があります。今年は早い者勝ちだったようです。
感想
参加前はiOSDCのことをかっちりした技術発表会だと勝手に思っていました。
しかし実際は左手でビールを握りながら右手でペンライトを振る、そんなお祭りでした。
1日の初めと終わりにある実行委員の長谷川さんの軽妙で聞き取り易いトークが素晴らしかったなあ。
あと無料でドーナツとコーヒーを飲み食いし、企業展示コーナーのfindyさんのブースでくじ引きの景品のAnkerをもらえて最高の3日間でした!
こんな大規模なイベントを企画しこの完成度で実行できる人たち本当に凄い。
iOSDCスカラーシップをプロリク(Proposal-Review-Request: 提案を意味する社内用語)していただいた方にも大感謝です。

私がこの記事を書きました。(生産者表示)

午前中に無料で提供されるドーナツ。朝に脆弱性を抱えるエンジニアを早朝に起動するための対応バッジ。

薄い本である。

ビール。iOSDCの本質である。
iOSDCの有効な利用法
正直なところ、10分~40分という短い時間で発表者が詰め込んだ膨大な情報を全て理解し記憶するのは困難。
大雑把に本質を把握して自身が開発する際に真似をしてみたり、将来発表者と同じ困難に出会ったときに、「そういえばあんな発表があったな、調べてみるか」と思い出せる記憶のトリガーを作る(脳を耕す)、という聴き方が良さそう。
また開発者は普通、家に引きこもっているか家を会社と往復するだけかのどちらかだが(偏見)、𝕏の顔見知りやQiita, Zennなどで見たことがある人と実際に会うのは、知識を共有したり鼓舞されたりするのに良いと思う。そんなのオンラインでもできるじゃないか、と思うかもしれない。だがあの高揚感ある会場で互いに持論や成果物を披露し合あうとより深い関係を構築できる。これはオフラインならではだと思う。
知識の共有及び鼓舞の一例:
自分の場合、会社の先輩に「macで日・中・英の入力ソースの切り替えで不便しているんですよね」と相談したところ、それを解決するOSSのmacアプリを教えてもらえた。ただし現在の自分の環境では動かなかったので、このアプリを改造して遊ぶ予定。
聴いたTalkの内容及び感想
以下は発表を聴きながら大雑把にまとめた内容ですので多数の間違いが含まれる可能性があります。
以下のメモはあくまでも、オリジナルのトークへの手がかりとなるように意図されたものです。
発表スライドはこちらにまとめてくださっている方がいました。
参加したセッション
day 1
タクシーアプリの多言語対応・ローカライズにおける課題と解決策
文言管理ツールを使用しアプリを多言語化している。
以前:
多言語化したいStringをスプシで整理。ユニークなキーを付与し、iOS/Androidで横断して対応。
日/英で文法の違いがある。→語順が異なるので修飾するべき位置が異なる。
アプリ開発が進行するにつれて文言が変わることがあるので面倒!
文言管理ツールの操作コストを意識せずに開発するようにするには?
文言管理ツールの更新作業が属人化していたが、それを誰でもできるようにした。
以後:
GitHub上で管理。
topicブランチをmainから切る。
文言を変更する際はtopicからfeatureを切って変更を加える。
GitHub Actionが言語管理ツールを更新。
文言の更新にPRが必須になったのでチェック可能に。
感想:
GitHub Actionsカッコイ。触ってみたい!
カンファレンスでネットワークの低レイヤーを学ぶ with Swift
一番低階層の物理層からネット接続を見ていく。
物理層
電波:2.4GHz、5Ghz
同じ帯域が混雑するからテザリングはNG
それぞれのAP(ルーター?)からは5Ghz帯でも、20MHz程度ずれた電波をそれぞれ出すため、干渉しなくて済む。
Wi-Fi Analyzerは周囲一体にどんなWi-Fiが出ているかを調べるツール
人体の6割は水、電波の障害物。だから頭上から電波を降らせる。
ハンドオーバーとは移動した際に異なるAPに接続すること。
ハンドオーバーを上手に行うには、接続を切れやすくする。
あえて弱めに電波を出し、通信が遅くなった端末とは通信しない設定を入れる。
データリンク層
MACアドレス Media Access Control Address
iPhoneはMACアドレスから個人を特定できないようにするため、MACアドレスをWi-Fi接続のたびにランダムに変更する。
VLAN Virtual-LAN
VLANのおかげでロールを分けられるのでより安全。(例:カンファレンス管理者・参加者・機器)
IPアドレス 昨年のiOSDCオフライン参加者は574人
IPアドレスの嬉しいこと
-
IPアドレスを集約し、集約した各グループでフィルタリングができる
集約:複数のIPアドレスの範囲を1つにまとめること
信用できるIPアドレスは〇〇〜△△
信用できないIPアドレスは△△〜□□
信用できないIPアドレスが付与された機器から信用できるIPアドレスが付与された機器へのアクセスを禁止する、などができる。 -
冗長化できる
173.31.0.8.1と言うIPアドレスの裏側で主サーバー173.31.0.8.2と副サーバー173.31.0.8.3を用意する。
SwiftUIの進化についていくためにやったこと
Alert, NavigationLinkがdepicatedに。
一方NavigationStackは対応ver的に実装ができない
マルチモジュール構成に移行中
AlertViewが廃止、alert modifierに直接渡すように
WatchOS開発最前線
WatchOS6から単体アプリが作れるようになった。
表示できる情報量が少ない。例えばTextは詰め込んでも1画面に5つまで。
なので情報を徹底的に減らして簡潔にするべき。例えばiPhoneの時計アプリは4タブあるが、Apple Watchだとそれぞれ別アプリとして存在する。
Apple Watchは有線接続できないので実機デバッグが面倒。
Apple Watchのパスコード設定をなくすと安定する。
Digital Crownは垂直方向のコントロールに使う例がApple純正アプリでも増えてきた。
UIKitベースのCustom UIContentConfiguration APIを用いた複雑なカスタムセルの作り方
(UIKitわかんねえ…)
Configurationとはデータに基づいて異なるUIを出すようにする定義のこと。
ContentConfiguration
ContentViewCell(外身)とContentConfiguration(中身)を分離。
違う外身で同じ中身を共有することもできて嬉しい。
UIHostingConfigurationはSwiftUIでViewを作成できる。
Performanceを意識するならUIContentConfiguration
実装をSwiftUIのみで簡単に書きたいならUIHostingConfiguration
day 1
TextKit 2 時代の iOS のキーボードとテキスト入力と表示のすべて
TextKit 1はNSLayoutManagerを中止としたクラスで構成されるAPIだった
TextKit 1は文字列の範囲とグリフ(TextKit 1の最小操作単位。)の範囲が一致する場合にのみ上手に運用できる。
例えばインドのとある地方の文字だと、グリフの組み合わせ方が複雑なため文字列の範囲と一致しない。
TextKit 2はNSTextLayoutManager。
TextKit 2は高度に抽象化されている。グリフは隠蔽されている。最小操作単位は行。
行より細かい画面上の矩形を求めることはできない。
画面上の位置から文字列上の位置を求めるに工夫が必要。
レイアウトから文字列を求めるときはtypographicBoundsをヒットテスト。
NSTextAttachmentViewProvierを使うと文字列に動画も埋め込める(!?)
TextKit1のAPIに少しでも触れると、2から1に自動でFallbackしてしまう点に注意する。
Fallbackする際_UITextViewEnablingCompatibleMode(?)というプライベートAPIが呼ばれるので、呼ばれた際にエラーを出せばいい。
SwiftUIではNSTextAttachmentやTextKit APIは一切使えない。
NSAttributedStringは使えるが煩雑。
アプリケーションの目的や必要な機能に応じてTextKit 1, TextKit 2, SwiftUIを使い分けよう。
Mastering SwiftSyntax
ParserはソースコードからSyntaxTreeを生成する。
SyntaxTreeは文字列ではなく構造として扱われるので安定的。
TokenはSyntaxの最小単位
TokenはTokenSyntaxという「構文ノードの型」
Token -> Node
let a = 0
let
, a
, =
, 0
はtoken。
nextToken,previousToken,firstToken,lastTokenで構造を無視して当該Tokenを取得可能。
SwiftSyntaxBuilderをimportすると、StringからSyntaxが自動で作成されるので、StringとToken(Node?)を直接比較できる。
Syntaxのハイライトに関するコードはKishikawa KatsumiさんのGitHubリポジトリから。
SyntaxRewriterのVisitPreを使うと、Visitの前に呼ばれる。Rewriteする必要がなくてもSyntaxRewriterを使うことがある。
SwiftSyntaxは型の暗黙的な宣言を読み取れない。明示的な型アノテーションが必要。
Pareserは文法エラーであっても、コンパイルが通ってしまう。
Parserは自動でコードを補完する。
SwiftSyntax Treeha不完全なコードから生成される場合もある。
旅行アプリでより正確にパスポートを読み込む技術
パスポートをOCRしたい
フライトに必須の情報
- 姓名
- 戸籍上の性別
- 生年月日
- パスポート番号
- パスポート有効期限
パスポートの写真や情報が書かれたページ下部に記号が書かれた領域(MRZ:Machine Readable Zone)がある。
MRZは国際機関によって規格が定められており世界共通
MRZのOCRに適しているのはMLKit?Vision?
SPM対応 | 実装容易性 | OCR精度 | |
---|---|---|---|
MLKit | 工夫が必要 | 容易 | たまに< がく と誤認識される |
Vision | 可能 | 容易 | たまに< がく と誤認識される |
大差がないがSPMで配布したいためVisionを使用。
MLKitでもVisionでも文字列操作は必要
パスポートにはNFCタグが埋め込まれており、そちらから情報を取得することもできる
NFCはISO7816で規格が定められている
誤りが含まれる可能性があるOCRと異なり、NFCは通信に成功した場合の精度は100%
パスポートはセキュリティが強いSAC:Supplemental Access Control形式。
パスポート番号・生年月日・有効期限をキーとしてアクセスをして、さらなる情報を取得する。
合計88文字のMRZをバイト単位で表現し、オブジェクト化。
OCRの精度は80〜90%程度。
そこでパスポート番号 生年月日 有効期限を取得。
それらの値をキーにNFCで精度100%でデータ取得する予定だった。一方で、Visionが思ったよりも制度が高いこと、OCR後にNFC読み取りは面倒でユーザ体験が悪いことの2点から、OCRのみで実装した。OCRした文字列に誤りが含まれる場合ユーザーが手動で修正する。
公的証明書のICチップ読み取りはセキュリティの強いTypeBが使用されていて難易度が高い。
ActorでCoreDataをスレッドから解放しよう
Swift ConcurrencyのもとでCoreDataをより安全に使いたい
スレッドセーフとは常に同一のスレッドからアクセスされることを保証する概念。
NSManagedObject, NSManagedObjectContextはそれを生成したスレッド以外のスレッドから参照したらスレッド違反。
Actorを使うとスレッドセーフ?
だめ。Actorはある瞬間において単一のスレッドから参照されることを保証するが、常に同じスレッドから参照されること(スレッド同一性)を保証しない。
Sendableとはデータ競合が発生せずに安全に受け渡しできる方を示すプロトコル。
例えば値型はSendable
NSManagedObjectやNSManagedObjectContextはSendableではない。
GlobalActor
特定のactor contextからのみ同期アクセスできる。
MainActorを適用するとMainActor contextからのみアクセス可能
NSManagedObjectやNSManagedObjectContextをSendableにしたい。
そのようなGlobalActorを型に適用すれば良い。→CoreDataActorを作成
MOCにはGlobalActorを適用した型でラップ。
CustomActorExecuter Swift 5.9 ~
actorのjobをどう実行するのかをカスタムできる。
GlobalActor
+ AuctomActorExecuter
で実行スレッドの同一性を保証できる。
同一Actorからは同期的にアクセスできる。
他のcontextからは非同期アクセスをコンパイラに強制される。
他社からのアプリ譲受による実践的な学び
アプリ譲受する際の注意点
App Store Connect上での設定の仕方。
アプリの所有者の切り替えとアプリアップデートは2、3日の差が発生するので注意が必要。
小さなバグが産んだ悲劇、そこから学ぶ耐障害性の高いアプリ設計
メンテナンスモードに切り替わらなかったため、ユーザーを混乱させた。
障害?一時的な過負荷?ユーザ側の環境?
メンテナンスモードとは全ての機能を遮断するモード
メンテナンス状態の時にユーザーに現状報告をする必要あり
- 障害なのか定期メンテナンスなのか
- いつ復旧するのか
今回の障害の原因は過負荷。メンテモードに切り替わらなかった理由はメンテナンス状態かどうかを取得するAPIもパンクしていたため。
対策:GOの場合は、GCS:GoogleCloudStrageにJSONファイルを配置し、メンテナンス状態か否かをBool値で管理。
iOS/Androidで一方のみで障害が発生する可能性もあるため、このJSONファイルはそれぞれのプラットフォームごとにある。
強制アップデート機能
- バージョンアップしないとアプリの機能をロック
- バージョンアップするべき理由を説明したい
- AppStoreへ誘導
強制アップデートの必要性もGCSのJSONファイルで管理。
アップデートを強制するタイミングはアプリ起動直後が望ましい。
アプリ内部にどのようなバグがあるか不明なため。
根本的な原因の追求と対策
メンテナンスモードの改善、強制アップデート機能の追加、目に見えないバグを潰す。
今回の障害の詳細
GPS精度はコロコロ変わるが、変わるたびにサーバーを叩く意図しない動作をしたため過負荷になった。
コードレビュー、QA、シミュレーションなど様々な工程をすり抜けてリリースされてしまった。
どれくらい通信されているかを可視化するデバッグモードを追加。
空間オーディオを活用しよう!
空間オーディオ:空間上に仮想的に音源を配置
ヘッドトラッキングにより、没入感を強化している
5.1chの0.1chは低音専用。低音と中高音は同じスピーカーから出したくない。低音は指向性が弱いので一つのみで良い。
オブジェクトベースオーディオ
音声+音源の位置を記録し、再生時に再現(レンダリング)。
頭内定位… ヘッドフォンで聴いた時に音源が頭の内部にあるように聞こえてしまうこと。
耳の入り口に音源があるふうに聞こえるようにしたい。
頭部伝達関数を加味して調整する。
バイノーラル録音:あるいはダミーデッドや録音者の耳にマイクを装着して録音する。
ステレオフォーマット
Dolby Atmos 7.1.2ch 下位互換性あり 0.0.2chは頭上
DTS:X
360 Reality Audio 他二つの頭上を頂点とする半球とは異なり全球に対応する。足元から響くドラムの音も再現可能。
ユースケース
-
コンテンツ再生
allowedAudioSpatializationFormats
isSpatialAudioEnabled -
通話
ルーキーズLightningTalk
-
7ヶ国語1200行の翻訳の安全に運用する方法
L10nLintを開発。 -
Swiftコードのパフォーマンス悪化
-
Core Bluetooth
Central Peripheral -
SwiftOpenAPIGenerator
appleが提供しているSwiftPackageプラグイン
day 2
Human Interface Guidlineから読み解く標準アプリの素晴らしい体験
-
設定
一貫して標準的なパターンを使用する
資格的にグループ分けして必要な情報を見つけやすくする
テキストサイズの変更に対応するForm
重要度の高い情報の提示にはモダリティを使用
いくつもの手順を経る独立したタスクに中断するには全画面モーダル
モーダルは常に閉じるボタンを表示 -
メモ
可能な限りアプリの全域でドラッグ&ドロップに対応する
コンテンツのドラッグ&ドロップをコピぺにするか、カットぺにするか。
ipadで2画面を跨いでドラッグ&ドロップしたらコピペ。1画面ならカットぺ。
モーダルでないシートでは、親viewの操作に変更を加える補助的な役割を提供 -
カレンダー
必要に応じてドラッグ先のコンテンツをスクロールする
ドロップ後はドラッグ先の選択は維持し、ソースの選択状態は必要に応じて更新する。
日付ピッカーの分は細かすぎず5分間隔。
分秒はタップするとキーボード入力で1分単位でも入力可能 -
ヘルスケア
グラフをシンプルにする。より詳細な情報は必要に応じて確認できるように。
おそらくグループボックスを使用し、論理的に関連あるデータをまとめる。 -
ミュージック
ユーザーが移動しても常に自分の位置がわかるようにLargeTitleを使用
タブバーはアクションの実行ではなくナビゲーションのために使用
各タブタイトルに簡潔な用語を使用する
旅行アプリにおけるLive Activityを用いたフライト追跡
Dinamic Island
ロック画面下部
Push通知を起点にLive Activityを開始することはできない
Appがforegroundの時のみ開始できる。
表示後8時間でシステムが自動更新を終了。
その後ロック画面なら4時間、Dinamic Islandは即時終了。
Widget Extension tergetから作成する
infoplistからキー追加
ActivityAttributesに準拠した構造体で、Widgetに表示する情報を定義する。
tergetmembershipに主Appを登録。
Live Activityには許諾が必要なんだ…
Brase SDK
複雑さに立ち向かうためのコードリーディング入門
認知プロセスに基づいたコードの読み方
情報過多症候群
脳内で処理すべき情報が多すぎて脳が疲労を起こし機能が低下することで起こる一連の症候
コードの読み方と認知機能は関連する。
エンジニアは読む時間 > 書く時間
なのだから読み方を学ぼう!
複雑の要因
- 情報の不足
- 処理能力の不足
- 前提知識の不足
認知プロセス
-
短期記憶
4〜7個を30秒保持する。それ以上はFirstInFirstOut -
ワーキングメモリ
短期記憶を利用するので、限界に達することがある。 -
長期記憶
知識不足と関連
そもそも知らない場合記憶から取り出せない。
新しい情報を取得… 短期記憶
関連記憶の検索…ワーキングメモリ
関連記憶の読み出し…長期記憶
認知プロセスをいかに妨げられないようにするか
チャンク化: 人間が一度に処理できる情報の単位
c, a, t -> cat
230184... -> 電話番号
苦労なく記憶できていることはチャンク化して認識できているということか。
チャンク化のコツは長期記憶で知識を保持しておくこと。
スキーマ: 似た記憶同士の関連のネットワーク(?)
スキーマを効率よく更新する
- 定期的に情報に触れる
- 複数の感覚を生かして記憶する(視覚聴覚触覚…)
長期記憶に関する二つの強度
- 保存強度
- 検索強度 (検索の速さ)
スキーマの強化
- 積極的なチャンク化
- 深掘りする( -> 関連情報の検索速度が上がる)
負荷の分類
- 課題内在性負荷
問題そのものの難しさ
e.x. 難易度の高い数学の問題
工夫:難しさは分散させる。状態遷移図を作るのも良い - 課題外在性負荷
問題の外部に存在する環境構築などの難しさ
map -> for in にするなどの工夫 - 学習関連負荷
長期記憶に保存する負荷
施策
- コードを読んでいて意図がつかめない場合。
学習の入り口を作る。
変数の役割を考える - 余計な情報を遮断する
騒音を避ける
複数のタスクの同時進行を避ける
割り込みを避ける
割り込みを避けるにはチャットの通知を一定期間オフにする。
カレンダーに集中タイムを作成
一人で集中できる環境を構築
何をしていたか思い出せる仕組み
中断せざるを得ない時はわざとコンパイルエラーを起こしておくと気持ち悪いから思い出しやすい。
脳の状態を知って負荷を軽減しよう。
じゃあいいコードを書くには?
- 余計な負荷を排除する。
- 独自の演算子を使わない。臭いコードを書かない。
一貫性を意識する - 一貫した命名規則
- 一貫したコーディングスタイル
- 一貫したフォーマット
- デザインパターン
テストを書く
見えるところにドキュメントを追加する
読みやすさは人によって異なるため、チーム開発の場合は共通のルールを構築する。
SwiftUIに適した新アーキテクチャの導入に挑む
SVVSアーキテクチャ
Store-View-ViewState
Store:状態を管理
View: UI
ViewState: Presentationロジックを保持
Store:
GlobalState(App前半で)をプロパティとして保持する。取得変更するメソッドも。
@Publicshed private(set) var
変更はStore内のみ
ViewStateはViewModelではない!Viewの状態管理のみする!
- Viewの状態を管理する
StoreのデータをViewで表示しやすいように加工、保持。 - Viewのアクションをメソッドで実装する。
Viewは必ずViewStateを@StateObjectで保持する
ロジックは基本持たない。
イベント処理はViewStateのメソッドを呼ぶ。
Viewは@Stateは持たない?簡単な処理も全てViewStateを介する?
View->ViewStateは1:1で依存する
ViewState->StoreはN:Nで依存する
SVVSの特徴
-
リアクティブ:
各レイヤー間のデータが同期されている。
コードが減りバグが減る。 -
シンプル:
バグに強い
変更しやすい
開発スピード上がる