12月 21, 2025
27 分で読める

ジュニアiOS開発者の面接質問:Swift・UIKit・SwiftUI

interview
career-advice
job-search
entry-level
ジュニアiOS開発者の面接質問:Swift・UIKit・SwiftUI
Milad Bonakdar

Milad Bonakdar

著者

Swift、UIKit、SwiftUI、ネットワーク、永続化、非同期処理について、初級iOS面接で聞かれやすい質問と答え方を練習できます。


はじめに

ジュニア iOS 開発者の面接では、Swift の基礎を説明できるか、UIKit または SwiftUI でシンプルな UI を作れるか、データ取得とデコードができるか、基本的な永続化を選べるか、メモリやスレッドまわりの典型的なミスを避けられるかが見られます。良い回答は短く具体的で、「小さなアプリならどう実装するか」につながっています。

このガイドでは、初級 iOS 面接で聞かれやすい質問について、概念の意味、使う場面、ジュニアが注意すべきリスクを練習できます。


Swiftの基礎 (6つの質問)

1. Swiftにおける varlet の違いは何ですか?

回答:

  • var: 可変変数を宣言します。初期化後に値を変更できます。
  • let: 不変定数を宣言します。一度設定すると、その値を変更できません。
  • ベストプラクティス: 安全性と明確さのために、デフォルトで let を使用します。値が変更されることがわかっている場合にのみ var を使用します。
let name = "John"  // 変更不可
var age = 25       // 変更可能
age = 26           // 有効
// name = "Jane"   // エラー: 値に割り当てることはできません

希少度: 非常に一般的 難易度: 簡単


2. Swiftのオプショナルについて説明してください。オプショナルバインディングとは何ですか?

回答: オプショナルは、nil(値がないこと)である可能性のある値を表します。

  • 宣言: 型の後に ? を使用します: var name: String?
  • アンラップの方法:
    • 強制アンラップ: name! (危険、nilの場合クラッシュ)
    • オプショナルバインディング: if let または guard let を使用して安全にアンラップ
    • Nil合体演算子: name ?? "Default"
    • オプショナルチェイニング: user?.address?.city
var email: String? = "[email protected]"

// オプショナルバインディング
if let unwrappedEmail = email {
    print("Email: \(unwrappedEmail)")
} else {
    print("メールアドレスがありません")
}

// Guardステートメント
guard let email = email else { return }
print(email)

希少度: 非常に一般的 難易度: 簡単


3. Swiftにおける classstruct の違いは何ですか?

回答:

  • Class: 参照型(ヒープに格納)。継承をサポート。参照渡し。
  • Struct: 値型(スタックに格納)。継承なし。コピー渡し。
  • 使用する場面:
    • Struct: 単純なデータモデルの場合、値セマンティクス(独立したコピー)が必要な場合
    • Class: 継承、参照セマンティクス、またはデイニシャライザが必要な場合
struct Point {
    var x: Int
    var y: Int
}

class Person {
    var name: String
    init(name: String) { self.name = name }
}

var p1 = Point(x: 0, y: 0)
var p2 = p1  // コピーが作成される
p2.x = 10    // p1.x はまだ 0

var person1 = Person(name: "John")
var person2 = person1  // 同じ参照
person2.name = "Jane"  // person1.name も "Jane"

希少度: 非常に一般的 難易度: 普通


4. Swiftのクロージャとは何ですか?

回答: クロージャは、コード内で受け渡しや使用ができる自己完結型の機能ブロックです。他の言語のラムダや匿名関数に似ています。

  • 構文: { (parameters) -> ReturnType in statements }
  • 値のキャプチャ: クロージャは、周囲のコンテキストから変数への参照をキャプチャして格納できます。
  • 一般的な用途: 完了ハンドラ、コールバック、配列操作。
// 簡単なクロージャ
let greet = { (name: String) -> String in
    return "こんにちは、\(name)!"
}
print(greet("Alice"))

// トレーリングクロージャ構文
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)  // [2, 4, 6, 8, 10]

希少度: 非常に一般的 難易度: 普通


5. weakunowned 参照の違いについて説明してください。

回答: どちらも、参照型における循環参照(メモリリーク)を防ぐために使用されます。

  • weak: 参照先のオブジェクトが解放されると自動的に nil になるオプショナル参照。参照がオブジェクトよりも長く存続する可能性がある場合に使用します。
  • unowned: オブジェクトが常に存在することを前提とする非オプショナル参照。解放後にアクセスするとクラッシュします。参照がオブジェクトよりも長く存続しないことが確実な場合に使用します。
class Person {
    var name: String
    weak var apartment: Apartment?  // 循環参照を防ぐために weak
    init(name: String) { self.name = name }
}

class Apartment {
    var unit: String
    unowned let tenant: Person  // unowned - アパートには常にテナントがいる
    init(unit: String, tenant: Person) {
        self.unit = unit
        self.tenant = tenant
    }
}

希少度: 一般的 難易度: 普通


6. Swiftのプロトコルとは何ですか?

回答: プロトコルは、特定のタスクまたは機能に適したメソッド、プロパティ、およびその他の要件のブループリントを定義します。クラス、構造体、および列挙型はプロトコルを採用できます。

  • 類似点: 他の言語のインターフェース
  • プロトコルエクステンション: デフォルトの実装を提供できます
  • プロトコル指向プログラミング: Swiftの推奨パラダイム
protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    var radius: Double
    
    func draw() {
        print("半径\(radius)の円を描画")
    }
}

struct Rectangle: Drawable {
    var width: Double
    var height: Double
    
    func draw() {
        print("\(width)x\(height)の長方形を描画")
    }
}

希少度: 非常に一般的 難易度: 簡単


UIKitの基礎 (5つの質問)

7. UIViewUIViewController の違いは何ですか?

回答:

  • UIView: 画面上の長方形の領域を表します。描画とイベント処理を処理します。例: UILabelUIButtonUIImageView
  • UIViewController: ビュー階層を管理します。ビューのライフサイクル、ユーザーインタラクション、およびナビゲーションを処理します。1つのルートビューを含み、そのサブビューを管理します。
  • 関係: ビューコントローラはビューを管理します。1つのビューコントローラは多数のビューを持つことができます。

希少度: 非常に一般的 難易度: 簡単


8. ビューコントローラのライフサイクルメソッドについて説明してください。

回答: ビューコントローラには、特定の順序で呼び出される特定のライフサイクルメソッドがあります。

  1. viewDidLoad(): ビューがメモリにロードされたときに一度だけ呼び出されます。一度だけ行う必要がある設定。
  2. viewWillAppear(_:): ビューが画面に表示される前に呼び出されます。複数回呼び出すことができます。
  3. viewDidAppear(_:): ビューが画面に表示された後に呼び出されます。ここでアニメーションを開始します。
  4. viewWillDisappear(_:): ビューが非表示になる前に呼び出されます。
  5. viewDidDisappear(_:): ビューが非表示になった後に呼び出されます。リソースをクリーンアップします。
override func viewDidLoad() {
    super.viewDidLoad()
    // 1回限りの設定: UIの構成、データのロード
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // データの更新、通知のリスニングの開始
}

希少度: 非常に一般的 難易度: 簡単


9. Auto Layoutとは何ですか?制約をどのように使用しますか?

回答: Auto Layoutは、画面サイズや向きが異なる場合でも動作する適応性のあるUIを作成できる制約ベースのレイアウトシステムです。

  • 制約: ビュー間の関係を定義します(幅、高さ、間隔、配置)
  • 方法:
    • Interface Builder: Xcodeのビジュアルエディタ
    • プログラムによる: NSLayoutConstraintまたはNSLayoutAnchor API
    • Visual Format Language: 文字列ベース(現在はあまり一般的ではありません)
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)

NSLayoutConstraint.activate([
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    button.widthAnchor.constraint(equalToConstant: 200),
    button.heightAnchor.constraint(equalToConstant: 50)
])

希少度: 非常に一般的 難易度: 普通


10. framebounds の違いは何ですか?

回答:

  • frame: スーパービューの座標系におけるビューの位置とサイズ。ビューの配置に使用されます。
  • bounds: 独自の座標系におけるビューの位置とサイズ。通常、原点は(0、0)です。ビュー内のコンテンツの描画に使用されます。
  • 重要な違い: frame は親に対する相対的な位置、bounds はそれ自体に対する相対的な位置です。
let view = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 150))
print(view.frame)   // (50, 100, 200, 150) - スーパービュー内の位置
print(view.bounds)  // (0, 0, 200, 150) - 独自の座標系

希少度: 一般的 難易度: 普通


11. UITextFieldからのユーザー入力をどのように処理しますか?

回答: 複数のアプローチがあります。

  • Target-Action: .editingChanged イベントのターゲットを追加
  • Delegate: UITextFieldDelegate プロトコルに準拠
  • Combine: パブリッシャーを使用(最新のアプローチ)
class ViewController: UIViewController, UITextFieldDelegate {
    let textField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Delegateアプローチ
        textField.delegate = self
        
        // Target-Actionアプローチ
        textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
    }
    
    @objc func textChanged(_ textField: UITextField) {
        print("Text: \(textField.text ?? "")")
    }
    
    // Delegateメソッド
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

希少度: 非常に一般的 難易度: 簡単


SwiftUIの基礎 (4つの質問)

12. SwiftUI とは何ですか。UIKit とはどう違いますか?

回答: SwiftUI は Apple の宣言的 UI フレームワークです。状態から UI を記述し、その状態が変わると SwiftUI が表示を更新します。UIKit は命令的で、UIViewUILabelUIViewController などのオブジェクトを直接作成・変更します。

ジュニア面接では、「SwiftUI が常に UIKit を置き換える」と言い切らないほうが安全です。多くの本番アプリでは両方が使われます。強い答え方は、SwiftUI は新しい画面や状態駆動の UI に向き、UIKit は既存コード、カスタムコントロール、SwiftUI がまだ十分にカバーしていない API で重要、という説明です。

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

希少度: 非常に一般的 難易度: 簡単


13. SwiftUI の @State@Binding@ObservedObject を説明してください。

回答: これらは、状態がどこにあり、誰が変更できるかを表す property wrapper です。

  • @State: 1 つの View が所有するプライベートな状態。小さなローカル UI 状態に使います。
  • @Binding: 親 View が所有する状態への双方向接続。子 View は所有せずに読み書きできます。
  • @ObservedObject: 外部の監視可能なモデルデータへの参照。別のオブジェクトがデータを所有し、変更時に View を更新したいときに使います。新しい SwiftUI では Observation framework や @Observable も見かけますが、面接では今でもこれらの wrapper がよく問われます。
class UserData: ObservableObject {
    @Published var name = ""
}

struct ParentView: View {
    @State private var isOn = false
    @ObservedObject var userData = UserData()

    var body: some View {
        VStack {
            ChildView(isOn: $isOn)
            Text(userData.name)
        }
    }
}

struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

希少度: 一般的 難易度: 普通


14. SwiftUIでリストを作成するにはどうすればよいですか?

回答: List ビューを使用して、スクロール可能なデータのコレクションを表示します。

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange", "Grape"]
    
    var body: some View {
        List(fruits, id: \.self) { fruit in
            Text(fruit)
        }
    }
}

// カスタムモデルを使用
struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TaskListView: View {
    let tasks = [
        Task(title: "Buy groceries"),
        Task(title: "Walk the dog")
    ]
    
    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

希少度: 一般的 難易度: 簡単


15. SwiftUIのビューモディファイアとは何ですか?

回答: ビューモディファイアは、変更されたプロパティを持つ新しいビューを作成するメソッドです。チェーン可能です。

  • 一般的なモディファイア: .padding().background().foregroundColor().font().frame()
  • 順序が重要: モディファイアは順番に適用されます
Text("Hello, World!")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.yellow)
    .cornerRadius(10)
    .shadow(radius: 5)

// カスタムモディファイア
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

extension View {
    func primaryButton() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

希少度: 一般的 難易度: 簡単


データとネットワーク (5つの質問)

16. iOS でネットワークリクエストを行うにはどうしますか?

回答: URLSession を使います。現在の Swift なら、ジュニア向けの回答でも async/await を示し、古いコードベースでは completion handler も使われると補足できます。実務に近い回答では、HTTP レスポンスの検証、エラー処理、Codable モデルへのデコード、UI 更新を main actor で行う点も説明します。

struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode(User.self, from: data)
}

希少度: 非常に一般的 難易度: 普通


17. SwiftのCodableとは何ですか?

回答: Codable は、Encodable & Decodable プロトコルの型エイリアスです。これにより、Swiftの型とJSONのような外部表現との間の変換が容易になります。

  • 自動合成: すべてのプロパティがCodableの場合、Swiftはエンコード/デコードコードを自動的に生成します。
  • カスタムキー: 異なるプロパティ名をマッピングするには、 CodingKeys 列挙型を使用します。
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

// JSONのデコード
let json = """
{
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json)

// JSONへのエンコード
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(user)

希少度: 非常に一般的 難易度: 簡単


18. iOSでローカルにデータを永続化するにはどうすればよいですか?

回答: ローカルデータ永続化には複数のオプションがあります。

  • UserDefaults: 少量のデータ(設定、プリファレンス)用のシンプルなキーと値のストレージ
  • ファイルシステム: ファイルをドキュメントまたはキャッシュディレクトリに保存
  • Core Data: オブジェクトグラフの管理と永続化のためのAppleのフレームワーク
  • SQLite: リレーショナルデータベース
  • Keychain: 機密データ(パスワード、トークン)の安全なストレージ
// UserDefaults
UserDefaults.standard.set("John", forKey: "username")
let username = UserDefaults.standard.string(forKey: "username")

// ファイルシステム
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = documentsPath.appendingPathComponent("data.json")

// 書き込み
try data.write(to: filePath)

// 読み取り
let loadedData = try Data(contentsOf: filePath)

希少度: 非常に一般的 難易度: 簡単


19. 同期処理と非同期処理の違いは何ですか?

回答: 同期処理は、完了するまで現在のスレッドをブロックします。main thread で行うとアプリが反応しなくなるため危険です。非同期処理は作業を開始して現在のスレッドを継続させ、結果は async/await、completion handler、delegate、publisher などで後から返ります。

iOS 面接では UI の応答性と結びつけて説明します。ネットワーク、ファイル処理、重い計算で main thread をブロックせず、UI 変更は main actor で行います。

func loadProfile() async {
    do {
        let user = try await fetchUser(id: 42)
        await MainActor.run {
            self.nameLabel.text = user.name
        }
    } catch {
        await MainActor.run {
            self.errorLabel.text = "Could not load profile"
        }
    }
}

希少度: 一般的 難易度: 簡単


20. Grand Central Dispatch (GCD) とは何ですか?

回答: GCD は、dispatch queue に処理をスケジュールする Apple の低レベル API です。main queue は UI 処理に使い、global queue は異なる QoS でバックグラウンド処理を実行できます。

新しい Swift では、非同期コードを読みやすくするために Swift concurrency(async/awaitTask、actor)を先に使うことが多いです。それでも UIKit プロジェクト、古いコードベース、callback ベースの API では DispatchQueue.main.async や background queue が使われるため、GCD の理解は重要です。

DispatchQueue.global(qos: .userInitiated).async {
    let image = renderThumbnail()

    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

希少度: 一般的 難易度: 普通

Newsletter subscription

実際に機能する週次のキャリアのヒント

最新の洞察をメールボックスに直接お届けします

採用担当者に目立ち、夢の仕事を手に入れよう

ATSを通過し、採用担当者を感動させるAI搭載の履歴書でキャリアを変えた数千人の仲間に加わりましょう。

今すぐ作成を開始

この投稿を共有

6秒を最大限に活用

採用担当者は平均わずか6〜7秒しか履歴書をスキャンしません。当社の実績のあるテンプレートは、即座に注目を集め、読み続けてもらえるように設計されています。