Xiaomi iOS開発者面接4ラウンドの全記録:SwiftとiOS原理の深い評価
3年経験iOS開発者のXiaomi面接完全振り返り。技術3ラウンド+HR面接の実際の問題を網羅。Swift高度機能、Runtime、RunLoop、メモリ管理、UI最適化等の問題まとめと対策アドバイス付き
背景紹介
まず私の状況から説明します:iOS開発3年の経験、現在は中規模アプリ会社でソーシャル製品を開発しており、DAUは約200万です。日常の仕事は主にSwiftでビジネスロジックを書き、一部のレガシーObjective-Cモジュールも保守しています。正直に言うと、現在の会社で2年以上働いて、技術的な壁にぶつかっていました——ビジネスの反復は速いものの、低レイヤーのことに触れる機会がどんどん減り、自分がますます「API呼び出しエンジニア」になっているように感じていました。
今年の3月から転職活動を始め、目標は明確でした:大手のシステムレベルチームに入り、より深いiOS開発に携わりたい。XiaomiのMIUI/iOSチームは以前から目標リストに入っていました——Xiaomiエコシステムの幅広さもそうですし、チームがiOSの原理を深く掘り下げて評価すると聞いていたので、ちょうど良い刺激になると思ったのです。
応募プロセスは比較的スムーズでした。Boss直聘でHRと話した後、すぐに面接が手配されました。一次面接からオファーをいただくまで、全体で約2週間半かかりました。以下、4ラウンドの面接の完全な振り返りをまとめました。Xiaomi iOS面接やiOS開発面接を準備中の方の参考になれば幸いです。
第1ラウンド 技術面接1回目(ビデオ面接、約60分)
一次面接は月曜日の午後2時に予定されていました。面接官は若手エンジニアで、XiaomiのiOS基盤アーキテクチャチーム所属と自己紹介しました。全体的なペースはかなりタイトで、Swiftの基礎からiOSの内部原理まで幅広くカバーされていました。
1. Swiftのstructとclassの違いは?なぜSwiftはstructを推奨するのか?
これは比較的スムーズに答えられました:structは値型、classは参照型;structは継承をサポートしない、classはサポートする;structにはmemberwise initializerがある。structを推奨する理由は、値セマンティクスがより安全、共有状態による副作用がない、スタック割り当てでパフォーマンスが良い、本質的にスレッドセーフなどです。面接官はcopy-on-writeメカニズムについて掘り下げましたが、Swift標準ライブラリのArrayやDictionaryなどがCOWでパフォーマンスを最適化する仕組みも説明できました。
2. Swiftのジェネリクスでwhereキーワードの役割は?実際の使用例を挙げられる?
whereはジェネリック型に制約を追加するために使用すると説明しました。例えば、ジェネリックパラメータが特定のプロトコルに準拠するか、特定のクラスを継承することを要求するなどです。実際の例として、要素がHashableとComparableの両方に準拠することを要求する関数を挙げました。面接官は頷いただけで、深く掘り下げませんでした。
3. SwiftのprotocolとObjective-Cのprotocolの違いは?
Swiftのprotocolはデフォルト実装をサポートしている(extension経由)、ジェネリック制約として使用できる、associatedtype関連型をサポートしていると説明しました。OCのprotocolはデフォルト実装も関連型もサポートしていません。面接官はprotocolで格納プロパティを宣言できるかと聞きました——できないと答え、計算プロパティの要件のみ宣言可能で、格納プロパティは関連オブジェクトや具象型で実装する必要があると説明しました。
4. Swiftのasync/awaitと従来のGCD/Completion Handlerの違いは?
コードの可読性、エラー処理、タスクキャンセルの3つの観点から比較しました。async/awaitは非同期コードを同期的に見せ、コールバック地獄を回避;try-catchとの組み合わせでエラー処理がより自然;TaskとTaskGroupで構造化並行処理が可能です。面接官はactorについて掘り下げました——actorはSwiftの並行処理モデルにおける分離メカニズムで、内部状態へのスレッドセーフなアクセスを保証し、データ競合を防止すると説明しました。正直なところ、actorについては基本的な概念しか話せず、深掘りできませんでした。面接官はさらに追及しませんでしたが、もっと準備すべきだったと感じています。
5. iOSのweakとunownedの違いは?それぞれどのような場面で使う?
weakはオプショナル型で、オブジェクト解放後に自動的にnilになる;unownedは非オプショナル型で、オブジェクトが常に存在すると仮定し、解放後にアクセスするとクラッシュする。使用場面:weakはクロージャでselfをキャプチャする際に最も一般的;unownedはオブジェクトのライフサイクルがクロージャより長いことが確実な場面、例えばlazyプロパティなど。面接官はクロージャでの[weak self]と[unowned self]の選択について聞きました——一般的にはweakの方が安全で推奨されると答えました。
6. SwiftUIとUIKitの違いは?SwiftUIの未来についてどう思う?
SwiftUIは宣言型UIフレームワーク、UIKitは命令型;SwiftUIは状態駆動でビューを更新するが、UIKitは手動でビュー階層を管理する必要があると説明しました。SwiftUIの利点はコードが簡潔、ライブプレビュー、クロスプラットフォーム;欠点はエコシステムが未成熟、複雑なカスタマイズが困難、パフォーマンスデバッグ手段が少ないこと。将来については、SwiftUIが徐々にUIKitに取って代わると考えていますが、短期的には特に複雑なビジネスシナリオでUIKitが主力であり続けると述べました。面接官はこの回答に満足したようでした。
7. iOSのイベントレスポンダチェーン(Responder Chain)の仕組みは?
hit-testingで第一レスポンダを決定し、イベントがレスポンダチェーンに沿って子ビューから親ビューへ渡され、処理されるまで続くと説明しました。最終的に誰も処理しない場合、イベントはUIWindowとUIApplicationに到達します。面接官はタップ領域を拡大する方法について聞きました——point(inside:with:)をオーバーライドするか、pointInsideの拡張を使用できると答えました。
8. iOSのMethod Swizzlingについて知っているか?リスクは?
Method SwizzlingはRuntimeを通じて2つのメソッドの実装を交換するもので、分析トラッキングなどのAOPプログラミングでよく使われると説明しました。リスクには、交換順序の依存による問題、複数回の交換による無限ループ、スレッドセーフティの問題、システム動作への予測不能な影響などがあります。使用時は+loadで実行すること、dispatch_onceで1回だけ交換すること、適切なログ記録を行うことを推奨しました。面接官はこの回答に頷いていました。
9. iOSのメモリ管理機構ARCの仕組みを簡単に説明して
ARCはコンパイラがコンパイル時に自動的にretain/release/autoreleaseの呼び出しを挿入し、参照カウントでメモリを管理します。強参照はカウントを増やし、弱参照は影響しません。カウントが0になるとオブジェクトが解放されます。autoreleasepoolの役割にも触れ——プールがドレインされる時にまとめてreleaseメッセージを送信し、メモリピークを防ぐと説明しました。面接官はautoreleasepoolとRunLoopの関係について掘り下げました——各RunLoopイテレーションでautoreleasepoolが作成され、イテレーション終了時にドレインされると簡単に説明しました。これは2回目の面接のRunLoopの問題と繋がっていました。
第2ラウンド 技術面接2回目(ビデオ面接、約65分)
2回目の面接は木曜日の午前10時に予定されていました。面接官はチームのテックリードで、1回目よりも明らかに深く、答えられなくなるまで掘り下げるスタイルでした。このラウンドはプレッシャーが大きく、2つの問題でうまく答えられませんでした。
1. Objective-C Runtimeのメッセージディスパッチメカニズムについて詳しく説明して
objc_msgSendから説明を始めました:メソッド呼び出しはコンパイル時にobjc_msgSend(receiver, selector, ...)に変換され、その後:キャッシュの検索→クラスのメソッドリストの検索→継承チェーンを上に検索、という順で進みます。見つからない場合、動的メソッド解決プロセスに入ります:resolveInstanceMethod → forwardingTargetForSelector → forwardInvocation。面接官はmethodSignatureForSelectorとforwardInvocationの関係について聞きました——methodSignatureForSelectorがメソッドシグネチャを返し、forwardInvocationがそのシグネチャに基づいて転送を実行すると答えました。その後、すべての転送が失敗したらどうなるかと聞かれ、doesNotRecognizeSelector例外がスローされてクラッシュすると答えました。全体的にはまずまずでしたが、動的メソッド解決の詳細、特にresolveInstanceMethodがYESを返した後にキャッシュを再検索する理由については、少し曖昧でした。
2. RunLoopの基盤メカニズムは何か?iOSでの実際の応用は?
RunLoopは本質的にイベントループで、mach_portを通じてイベントを受信し、スレッドを存続させると説明しました。内部では複数のModeを維持し、各ModeにはSource0、Source1、Timer、Observerが含まれます。実際の応用には、NSTimerの実装、AutoreleasePoolの管理、イベントレスポンス、GCDコールバックの実行などがあります。面接官はRunLoopとスレッドの関係について聞きました——すべてのスレッドに対応するRunLoopがあるが、メインスレッドのRunLoopのみデフォルトで起動し、サブスレッドでは手動で取得して実行する必要があると答えました。その後、私を困らせた質問が来ました:「RunLoopがスリープしている時、スレッドはどのような状態にあるか?どのように起こされるのか?」 スレッドがサスペンドされることは知っていましたし、「mach_msgでスレッドをスリープさせ、ポートメッセージがある時に起こす」という詳細は言えましたが、カーネル空間とユーザー空間の切り替えの詳細については正直よく分かりませんでした。「この部分は深く理解していません」と正直に答えました。面接官は大丈夫だと言って次に進みました。
3. iOSで循環参照(Retain Cycle)を検出・解決する方法は?
一般的なシナリオを挙げました:クロージャでのselfキャプチャ、delegateのstrong宣言、Timerのinvalidate忘れ。検出方法:InstrumentsのLeaksとAllocationsツール、XcodeのMemory Graph Debugger、FBRetainCycleDetector。解決方法:クロージャで[weak self]を使用、delegateをweakで宣言、Timerはdeinitでinvalidate。面接官は予想外の質問をしました:「NSTimerの循環参照は具体的にどのように形成されるのか?なぜtargetをweakにしても解決しないのか?」 考えた後、NSTimerのtargetはRunLoopに強く保持されているため、targetがweakでもRunLoopがtimerを保持し、timerがtargetを保持し、RunLoop→Timer→Targetの循環が形成されると答えました。weakは参照がカウントを増やさないようにするだけですが、RunLoopの保持はstrongです。正しいアプローチはGCD TimerやBlockベースのTimer APIを使用することです。面接官は「基本的に正しい」と言いましたが、説明が十分に明確ではなかったと感じました。
4. GCDとOperationQueueの違いと使用場面は?
GCDはC言語APIで、キューとクロージャベース、軽量で効率的;OperationQueueはオブジェクト指向で、キャンセル、依存関係、優先度、最大同時実行数の制御をサポート。使用場面:単純な非同期タスクはGCD、複雑なタスク編成はOperationQueue。面接官はGCDのキュータイプとQoSレベルについて掘り下げました——main queue、global queue、カスタムシリアルキュー、カスタム並行キュー、そしてuserInteractive、userInitiated、default、utility、backgroundの5つのQoSレベルを挙げました。
5. iOSのCategoryとExtensionの違いについて説明して。Categoryに格納プロパティを追加できる?
Categoryはランタイムで有効になり、既存のクラスにメソッドを追加できるが、格納プロパティは直接追加できない;Extensionはコンパイル時に有効になり、プロパティとメソッドを追加できるが、メインクラスの実装ファイルになければならない。Categoryで格納プロパティを追加するには、関連オブジェクト(objc_setAssociatedObject/objc_getAssociatedObject)を使用する必要があります。面接官は関連オブジェクトの基盤実装について聞きました——関連オブジェクトはグローバルなAssociationsManagerハッシュテーブルに格納され、オブジェクトのアドレスとkeyでインデックスされると答えました。オブジェクト解放時にはobjc_destructInstanceで関連オブジェクトがクリーンアップされます。
6. iOSのKVOの基盤実装原理は?
KVOはRuntimeを通じて観察対象オブジェクトのサブクラス(NSKVONotifying_XXX)を動的に作成し、setterメソッドをオーバーライドし、setter内でwillChangeValueForKeyとdidChangeValueForKeyを呼び出して観察者に通知すると説明しました。面接官はこのメカニズムを証明する方法について聞きました——object_getClassでオブジェクトの実際のクラスを出力して確認できると答えました。また、KVOの落とし穴にも触れました:setter外でインスタンス変数を直接変更するとKVOがトリガーされず、willChange/didChangeを手動で呼び出す必要があります。
第3ラウンド 技術面接3回目(ビデオ面接、約55分)
3回目の面接は翌週の火曜日午後3時に予定されていました。面接官はシニアアーキテクトで、質問はアーキテクチャ設計とシナリオ分析に偏っていました。このラウンドのペースは比較的快適で、面接官は単に試すというより議論しているような感じでした。
1. プロジェクトで遭遇したパフォーマンス問題は?どのように最適化したか?
2つの実際の事例を挙げました。1つ目はTableViewのスクロールカクつき:原因はcellForRowで画像デコードと角丸クリッピングを行っていたことで、最適化として画像デコードをバックグラウンドスレッドに移行、リアルタイムクリッピングの代わりに事前レンダリング角丸を使用、行の高さを事前計算しました。2つ目はアプリ起動時間の長さ:環境変数DYLD_PRINT_STATISTICSを追加してpre-mainフェーズの所要時間を特定し、動的ライブラリが多すぎることが判明したため、いくつかの内部ライブラリを統合し、起動時間を2.3秒から1.5秒に短縮しました。面接官は他の起動最適化手法について聞きました——バイナリリオーダー、初画面以外のモジュールの遅延ロード、+load内のロジック削減を追加しました。
2. 画像キャッシュフレームワークを設計するとしたら、どう設計する?
3層キャッシュアーキテクチャで設計しました:メモリキャッシュ(NSCache、自動退避戦略)→ディスクキャッシュ(ファイルシステム、時間とサイズでクリーンアップ)→ネットワークリクエスト(URLSession、並行性と優先度をサポート)。主要な設計ポイント:メモリ警告時のメモリキャッシュクリア、ディスクキャッシュのLRU戦略、バックグラウンドスレッドでの画像デコード、プログレッシブJPEGロードのサポート。面接官はNSCacheとNSDictionaryの違いについて聞きました——NSCacheはスレッドセーフ、メモリ圧力時に自動的にオブジェクトを解放、keyをコピーしないと答えました。面接官はディスクキャッシュの効率的な検索方法についても聞きました——ファイルパスのMD5をファイル名として使用し、ファイルシステムから直接読み取ると答えました。
3. アプリでOOMが発生した場合、どう調査するか?
まずメモリリークかメモリピークかを区別すると説明しました。メモリリークはInstrumentsのLeaksとMemory Graphで調査;メモリピークは大きなオブジェクトのライフサイクルを分析する必要があります。一般的な原因:大きな画像を圧縮せずにロード、リストキャッシュのサイズ制限なし、循環参照。調査手順:まずMemory Graphで現在のメモリ内のオブジェクト分布を確認、次にAllocationsでメモリ増加傾向を追跡、最後にLeaksでリークを検出。面試官はJetsamメカニズムについて聞きました——iOSはJetsamを使用してメモリ使用量が高く優先度の低いプロセスを優先的に強制終了し、ログでJetsamイベントを確認できると答えました。
4. コンポーネント化についてどう理解しているか?プロジェクトではどうやっているか?
コンポーネント化の核心は疎結合で、各モジュールを独立して開発・テストできるようにすることだと説明しました。私たちのプロジェクトではプロトコルベースの登録方式(BeeHiveに似たもの)を使用し、Protocol-Class登録でサービスディスカバリを実現し、モジュール間は直接依存ではなくプロトコルで通信しています。面接官はコンポーネント化の過程で直面した困難について聞きました——最大の課題はレガシーコードの疎結合化で、多くのクロスモジュール呼び出しあちこちに散らばっており、段階的に抽出する必要がありました。もう一つの問題はコンポーネントの粒度で、細かすぎると保守コストが高く、粗すぎると疎結合が不十分になることでした。
5. シナリオ問題:A/Bテストをサポートする設定可能なホームフィードを設計する場合、アーキテクチャをどう設計するか?
階層化アーキテクチャで設計しました:設定層(サーバーからAB実験設定を取得し、どのカードをどの順序で表示するかを決定)→データ層(設定に基づいて対応するデータソースをリクエスト)→プレゼンテーション層(カードタイプに基づいて対応するCellを動的に作成、ファクトリパターンを使用)。重要ポイント:設定はJSON形式で配信され、クライアントが解析してカードモデル配列を生成;Cell登録はReuseIdentifierマッピングを使用;データソースはプロトコルで抽象化し、異なるカードがそれぞれのデータプロトコルを実装。面接官は新しいカードタイプを追加するためにアプリのアップデートが必要な場合どうするかと聞きました——汎用カードタイプを予約し、サーバー設定で動的にレンダリングする、WebViewやテンプレートレンダリングに似たアプローチができると答えました。
第4ラウンド HR面接(約35分)
HR面接は3回目の面接の3日後に予定されていました。面接官はとても親切なHRの方でした。全体的な雰囲気はリラックスしていましたが、いくつかの質問にはやはり慎重に考える必要がありました。
1. 自己紹介
3年のiOS開発経験、現在の製品と技術スタック、Xiaomiに入りたい理由を簡単に説明しました。
2. なぜXiaomiを選んだのか?
3つの理由を挙げました:Xiaomiのエコシステムは幅広く、スマホからIoT、自動車まで、iOS開発はアプリだけでなくシステムレベルの仕事もある;Xiaomiは技術的な文化が厚く、オープンソース文化が豊か;MIUIチームの技術への追求と私のキャリアプランが一致している。
3. 現在の会社での最大の成果は?
アプリのOCからSwiftへの移行プロジェクトを主導したこと——半年かけてコードの80%を移行し、本番でのゼロインシデントを達成しました。この過程で、混合言語プロジェクトの経験を積み、OCとSwiftの相互運用メカニズムを深く理解しました。
4. 期待する給与は?
現在の給与と期待する増加幅を述べました。HRはその場では回答せず、総合的に評価してから提案するとのことでした。
5. 何か質問はありますか?
チームの技術スタックと今後の方向性について聞きました。HRは、チームは現在クロスプラットフォームソリューションの研究をしており、Xiaomi自動車の車載iOS適配にも参加していると答えました。この回答でさらに期待が高まりました。
面接問題まとめ
以下は4ラウンドの全問題を、ラウンド別に整理したものです:
技術1回目(9問):
1. structとclassの違い、なぜstructを優先するか → Swift基礎 → ⭐⭐
2. ジェネリクスのwhereキーワードの役割と実際の例 → Swiftジェネリクス → ⭐⭐
3. Swift protocolとOC protocolの違い → 言語比較 → ⭐⭐⭐
4. async/awaitとGCD/Completion Handlerの違い → Swift並行処理 → ⭐⭐⭐
5. weakとunownedの違いと使用場面 → メモリ管理 → ⭐⭐
6. SwiftUIとUIKitの違い → 技術視野 → ⭐⭐
7. イベントレスポンダチェーンの仕組み → UI内部原理 → ⭐⭐⭐
8. Method Swizzlingの原理とリスク → Runtime → ⭐⭐⭐
9. ARCの仕組み → メモリ管理 → ⭐⭐
技術2回目(6問):
1. Runtimeメッセージディスパッチメカニズム → Runtime内部 → ⭐⭐⭐⭐
2. RunLoopの基盤メカニズムと実際の応用 → RunLoop → ⭐⭐⭐⭐
3. 循環参照の検出と解決 → メモリ管理 → ⭐⭐⭐
4. GCDとOperationQueueの違い → マルチスレッド → ⭐⭐⭐
5. CategoryとExtensionの違い → OC機能 → ⭐⭐⭐
6. KVOの基盤実装原理 → Runtime → ⭐⭐⭐⭐
技術3回目(5問):
1. プロジェクトでのパフォーマンス最適化経験 → 実践能力 → ⭐⭐⭐
2. 画像キャッシュフレームワークの設計 → アーキテクチャ設計 → ⭐⭐⭐⭐
3. OOM調査アプローチ → パフォーマンスチューニング → ⭐⭐⭐⭐
4. コンポーネント化の理解と実践 → アーキテクチャ能力 → ⭐⭐⭐
5. 設定可能なホームフィードアーキテクチャ設計 → システム設計 → ⭐⭐⭐⭐⭐
HR面接(5問):
1. 自己紹介 → コミュニケーション能力 → ⭐
2. なぜXiaomiか → 志望動機 → ⭐⭐
3. 最大の成果 → 自己認識 → ⭐⭐
4. 給与期待 → 市場認識 → ⭐⭐
5. 逆質問 → 積極性 → ⭐
感想とアドバイス
最終的に、面接後5日目にXiaomiからオファーをいただきました。給与は約30%の増加で、全体的に満足しています。面接プロセス全体を振り返って、いくつかアドバイスを共有します:
1. iOSの原理は深く理解する必要がある。表面的な知識だけでは不十分。XiaomiのRuntime、RunLoop、メモリ管理に関する評価は非常に徹底しています。いくつかの概念を暗記するだけでは対応できません。RunLoopのスリープ/ウェイクメカニズムやRuntimeメッセージ転送の完全なフローなど、基盤実装を真に理解する必要があります。結論を覚えるのではなく、「Pro Objective-C」やAppleの公式ドキュメントを読み、ソースコードと合わせて学習することをお勧めします。
2. Swiftの高度な機能はプラス評価。async/awaitとactorは必ず準備する。1回目の面接でのasync/awaitとactorの評価で、Swiftの並行処理モデルがすでに面接の高頻度トピックになっていることを実感しました。まだGCDしか使っていない場合は、できるだけ早くSwift Concurrencyを学習することをお勧めします——これは面接のためだけでなく、iOS開発の将来の方向性でもあります。
3. アーキテクチャ設計とシナリオ問題は日常の蓄積が必要。直前の詰め込みでは効果が限定的。3回目の画像キャッシュフレームワーク設計とホームフィードアーキテクチャ設計は、実際のプロジェクト経験と設計思考が必要です。日常の仕事で「なぜこのように設計されているのか」をより多く考え、「どう実装するか」だけでなく、デザインパターンの応用シナリオを蓄積することをお勧めします。
4. 面接で分からない問題は正直に言う。でっち上げない。2回目の面接でRunLoopのカーネル空間切り替えについては本当に理解しておらず、「この部分は深く理解していません」と正直に答えました。面接官はそれで減点しませんでした。逆に、答えをでっち上げて追及されると、評価に深刻な影響を与えます。分からないことは分からないと言い、学習意欲を表現する方が良い戦略です。
FAQ
Q1:Xiaomi iOS面接でOCの基礎はどの程度求められる?
A:非常に高く求められます。日常の開発ではSwiftを使っていても、Runtime、RunLoop、KVOなどはすべてOCレベルの基盤メカニズムであり、面接で必ず出題されます。日常的にSwiftを使っていても、OCのRuntimeとメモリ管理を体系的に学習することをお勧めします。
Q2:Xiaomi iOS面接でアルゴリズムは出題される?
A:私の4ラウンドでは単独のアルゴリズム問題はありませんでしたが、3回目のシナリオ設計問題ではデータ構造とアルゴリズムの基礎が必要です。少なくともLeetCode Hot 100を完了し、リンクリスト、ツリー、動的プログラミングに重点を置くことをお勧めします。
Q3:SwiftUIは面接でどの程度の比重か?
A:1回目でSwiftUIとUIKitの比較問題が1問あり、深くはありませんでした。ただし、履歴書にSwiftUIプロジェクトを記載している場合、面接官は必ず深く掘り下げます。実際のプロジェクト経験がない場合は、履歴書でSwiftUIを過度に強調しないことをお勧めします。
Q4:Xiaomi面接の難易度は他の大手と比べてどう?
A:個人的には、Xiaomi iOS面接の難易度は中程度よりやや上で、ByteDanceのiOS面接より少し簡単で、Baiduとほぼ同じと感じました。特徴は原理の評価が深いことですが、故意に困らせることはなく、面接官の態度は皆良いです。
Q5:面接前にどのようなプロジェクト紹介を準備すべきか?
A:深く話せるプロジェクトを1〜2個準備し、解決した技術的課題と最適化の成果を強調することをお勧めします。STARメソッドで構成します:Situation(背景)→Task(タスク)→Action(行動)→Result(結果)。特にResultは定量化できるように——「起動時間を2.3秒から1.5秒に短縮」は「起動速度を最適化」よりも説得力があります。

