Swift Macros:メタプログラミングの実践例
Swift Macros の完全ガイドです。Swift 6 の body マクロ(SE-0415)を含む freestanding と attached マクロの作成、swift-syntax を用いた AST 操作、ボイラープレートを排除する実践的なコード例をまとめています。

Swift Macros はコードの記述と保守の方法を根本から変えます。Swift 5.9 で導入され、Swift 6 で大幅に拡張されたマクロは、静的型安全性を維持しながらコンパイル時にコードを生成できます。C のプリプロセッサマクロとは異なり、Swift Macros は型安全であり、コンパイラに統合され、Xcode の「Expand Macro」機能を含む開発ツールから完全にサポートされています。
本ガイドでは Swift Macros の作成を基礎概念から高度な実装まで取り上げます。Swift 6 で導入された body マクロ(SE-0415)も含め、すべての例で Swift 6 と swift-syntax 600.0.0 以降を使用します。
Swift Macros の種類を理解する
Swift にはマクロのカテゴリが大きく 2 つあり、それぞれ異なるユースケースに対応します。freestanding マクロは式や宣言として単独で機能し、attached マクロは既存の宣言に紐づき、それを修正・拡張します。
freestanding マクロ:式と宣言
freestanding マクロは # 記号で始まり、値を返す(式)か新しい宣言を生成します。次は具体的な式マクロの例です。
// Freestanding expression macro - generates a value
let buildInfo = #buildDate
// Expansion → "2026-08-23 10:30:45"
// Freestanding macro with arguments
let message = #stringify(1 + 2)
// Expansion → "1 + 2 = 3"
// Freestanding declaration macro - creates declarations
#makeCase("success", "failure", "pending")
// Expansion →
// case success
// case failure
// case pending式と宣言の根本的な違いは結果にあります。式は値を返し、宣言は構造的なコード(型、関数、変数)を生成します。
attached マクロ:6 つのロール
attached マクロは @ で始まり、宣言の前に配置します。Swift 6 ではこのマクロに対して 6 つのロールを定義しており、関数の実装を生成する新しい body ロールも含まれます。
// @attached(peer) - adds declarations at the same level
@AddAsync
func fetchUser(id: Int) -> User { ... }
// Expansion → adds func fetchUserAsync(id: Int) async -> User
// @attached(accessor) - adds getters/setters
@UserDefault("theme")
var currentTheme: String
// Expansion → adds get { UserDefaults.standard.string(...) }
// @attached(member) - adds members to a type
@AutoEquatable
struct Point {
var x: Int
var y: Int
}
// Expansion → adds static func == (lhs: Point, rhs: Point) -> Bool
// @attached(memberAttribute) - applies attributes to members
@CodableKeys
struct Config {
var apiUrl: String
var timeout: Int
}
// Expansion → adds @CodingKey("api_url") before apiUrl
// @attached(extension) - adds protocol conformances
@Hashable
struct User {
var id: Int
var name: String
}
// Expansion → adds extension User: Hashable { ... }
// @attached(body) - NEW in Swift 6: synthesizes function bodies
@Remote
func fetchData(endpoint: String) -> Data
// Expansion → generates the entire function implementationこれらのロールを組み合わせることで、コードを複数の側面で同時に変換する強力なマクロを構築できます。
Swift 6 Body マクロ:SE-0415
Swift 6 では SE-0415 によって関数本体マクロが導入されました。これにより、マクロが関数の実装を合成または置き換えることが可能になります。従来のマクロロールでは対応できなかったユースケースに対応しています。
BodyMacro プロトコル
BodyMacro プロトコルは関数本体の完全な置き換えを可能にします。
import SwiftSyntax
import SwiftSyntaxMacros
public protocol BodyMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingBodyFor declaration: some DeclSyntaxProtocol &
WithOptionalCodeBlockSyntax,
in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax]
}マクロは元の宣言を受け取り、関数本体となるコードブロックアイテムを返します。既存の本体がある場合は置き換えられます。
リモートプロシージャコールマクロの実装
body マクロは関数シグネチャからネットワークレイヤーのコードを生成する場合に特に便利です。
import SwiftSyntax
import SwiftSyntaxMacros
public struct RemoteMacro: BodyMacro {
public static func expansion(
of node: AttributeSyntax,
providingBodyFor declaration: some DeclSyntaxProtocol &
WithOptionalCodeBlockSyntax,
in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax] {
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw MacroError.invalidSyntax("@Remote requires a function")
}
let functionName = funcDecl.name.text
let parameters = funcDecl.signature.parameterClause.parameters
// Generate parameter encoding
let paramDict = parameters.map { param in
let name = param.firstName.text
return ""(name)": (name)"
}.joined(separator: ", ")
// Generate the RPC body
return [
"""
let params: [String: Any] = [(raw: paramDict)]
return try await RPCClient.shared.call(
method: "(raw: functionName)",
params: params
)
"""
]
}
}宣言と使用方法
/// Generates a remote procedure call implementation
@attached(body)
public macro Remote() = #externalMacro(
module: "MyMacrosPlugin",
type: "RemoteMacro"
)class APIService {
@Remote
func createUser(name: String, email: String) async throws -> User
@Remote
func deleteUser(id: UUID) async throws -> Bool
// Each function body is generated at compile time:
// func createUser(name: String, email: String) async throws -> User {
// let params: [String: Any] = ["name": name, "email": email]
// return try await RPCClient.shared.call(
// method: "createUser",
// params: params
// )
// }
}1 つの関数に適用できる body マクロは最大 1 つです。ロギングやトレースなどの合成可能なプリアンブル注入については、今後の Swift バージョンで PreambleMacro プロトコルが検討されています。
マクロを作るためのプロジェクト構成
Swift Macros の作成には、特定の構成を持つ Swift Package が必要です。このパッケージは Swift コードを抽象構文木(AST)として扱う公式ライブラリ swift-syntax に依存します。
Package.swift の構造
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyMacros",
platforms: [.macOS(.v10_15), .iOS(.v13)],
products: [
// Library exposing macros to the main project
.library(
name: "MyMacros",
targets: ["MyMacros"]
),
// Executable for testing macros
.executable(
name: "MyMacrosClient",
targets: ["MyMacrosClient"]
)
],
dependencies: [
// Required dependency for macros - use 600.0.0+ for Swift 6
.package(
url: "https://github.com/swiftlang/swift-syntax.git",
from: "600.0.0"
)
],
targets: [
// Compiler plugin containing implementation
.macro(
name: "MyMacrosPlugin",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
// Target exposing macro declarations
.target(
name: "MyMacros",
dependencies: ["MyMacrosPlugin"]
),
// Test client
.executableTarget(
name: "MyMacrosClient",
dependencies: ["MyMacros"]
),
// Unit tests
.testTarget(
name: "MyMacrosTests",
dependencies: [
"MyMacrosPlugin",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
]
)
]
)この構成では、マクロの宣言(クライアントコードから見える部分)と実装(コンパイル時に動作する部分)が明確に分離されています。
最低でも 3 つのファイルが必要です。宣言用の MyMacros.swift、実装用の MyMacrosPlugin.swift、テスト用の MyMacrosTests.swift です。この分離により保守性が向上します。
式マクロの作成
式マクロはコード内でそのまま使える値を生成します。次は、変数名を含むカスタムエラーメッセージ付きで Optional をアンラップする #unwrap マクロの作成例です。Swift のエラー処理パターンについては、Swift structured concurrency ガイドも参照してください。
マクロの宣言
import Foundation
/// Macro that unwraps an optional with an explicit error message
/// Usage: let value = #unwrap(optionalValue)
/// Expansion: guard let optionalValue else { fatalError("...") }; optionalValue
@freestanding(expression)
public macro unwrap<T>(_ value: T?) -> T = #externalMacro(
module: "MyMacrosPlugin",
type: "UnwrapMacro"
)このシグネチャは、マクロが Optional を受け取り、非 Optional の値を返すことを示しています。#externalMacro はプラグイン内の実装を指します。
swift-syntax を用いた実装
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftCompilerPlugin
public struct UnwrapMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// Get the first argument passed to the macro
guard let argument = node.argumentList.first?.expression else {
throw MacroError.missingArgument
}
// Extract the variable name for the error message
let variableName = argument.description.trimmingCharacters(
in: .whitespacesAndNewlines
)
// Generate the expansion code
// Uses an immediately-invoked closure to encapsulate the guard
return """
{
guard let value = (argument) else {
fatalError("Failed to unwrap '\((literal: variableName))' - value was nil")
}
return value
}()
"""
}
}
// Custom errors for macros
enum MacroError: Error, CustomStringConvertible {
case missingArgument
case invalidSyntax(String)
var description: String {
switch self {
case .missingArgument:
return "The macro requires an argument"
case .invalidSyntax(let message):
return "Invalid syntax: (message)"
}
}
}expansion メソッドはマクロ呼び出しを表す AST ノードとコンパイルコンテキストを受け取り、生成されたコードを含む ExprSyntax を返します。
プラグインの登録
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct MyMacrosPlugin: CompilerPlugin {
// List all macros provided by this plugin
let providingMacros: [Macro.Type] = [
UnwrapMacro.self,
RemoteMacro.self,
AutoInitMacro.self,
// Add other macros here
]
}このエントリーポイントにより、プラグインで提供されるマクロをコンパイラに伝えます。
iOSの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
attached の member マクロの作成
member マクロは既存の型にメンバー(プロパティ、メソッド、ネスト型)を追加します。次は、ストアドプロパティすべてを含むイニシャライザを自動生成する @AutoInit マクロです。
宣言と実装
/// Automatically generates an initializer with all stored properties
@attached(member, names: named(init))
public macro AutoInit() = #externalMacro(
module: "MyMacrosPlugin",
type: "AutoInitMacro"
)import SwiftSyntax
import SwiftSyntaxMacros
public struct AutoInitMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Verify the macro is applied to a struct or class
guard declaration.is(StructDeclSyntax.self) ||
declaration.is(ClassDeclSyntax.self) else {
throw MacroError.invalidSyntax(
"@AutoInit can only be applied to structs and classes"
)
}
// Collect stored properties
let properties = declaration.memberBlock.members
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
.filter { isStoredProperty($0) }
// Generate initializer parameters
let parameters = properties.compactMap { property -> String? in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self),
let type = binding.typeAnnotation?.type else {
return nil
}
let name = identifier.identifier.text
let typeName = type.description.trimmingCharacters(in: .whitespaces)
// Check if the property has a default value
if binding.initializer != nil {
return "(name): (typeName) = (binding.initializer!.value)"
}
return "(name): (typeName)"
}
// Generate assignments in the init body
let assignments = properties.compactMap { property -> String? in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else {
return nil
}
let name = identifier.identifier.text
return "self.(name) = (name)"
}
// Build the complete initializer
let initDecl: DeclSyntax = """
public init((raw: parameters.joined(separator: ", "))) {
(raw: assignments.joined(separator: "
"))
}
"""
return [initDecl]
}
// Check if a variable is a stored property (not computed)
private static func isStoredProperty(_ variable: VariableDeclSyntax) -> Bool {
guard let binding = variable.bindings.first else { return false }
// A computed property has an accessor block with get/set
if let accessor = binding.accessorBlock {
// If it's a block with explicit accessors, it's computed
if accessor.accessors.is(AccessorDeclListSyntax.self) {
return false
}
}
// let or var without accessor = stored property
return true
}
}AutoInit マクロの使用例
@AutoInit
struct User {
let id: UUID
var name: String
var email: String
var isActive: Bool = true
}
// Automatically generated code:
// public init(id: UUID, name: String, email: String, isActive: Bool = true) {
// self.id = id
// self.name = name
// self.email = email
// self.isActive = isActive
// }
// Usage
let user = User(id: UUID(), name: "Alice", email: "alice@example.com")
// isActive uses the default valueこのマクロはイニシャライザのボイラープレートを取り除いてくれるため、プロパティが多いデータモデルで特に効果を発揮します。
async を生成するための attached peer マクロ
peer マクロは注釈された宣言と同じ階層に新しい宣言を追加します。次は、completion handler を使う関数の async 版を生成する @AddAsync マクロです。このパターンは、レガシーなネットワークコードを移行する際に特に有用であり、Combine vs async/await マイグレーションガイドで詳しく扱っています。
/// Automatically generates an async version of a function with completion handler
@attached(peer, names: suffixed(Async))
public macro AddAsync() = #externalMacro(
module: "MyMacrosPlugin",
type: "AddAsyncMacro"
)import SwiftSyntax
import SwiftSyntaxMacros
public struct AddAsyncMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Verify it's a function
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw MacroError.invalidSyntax(
"@AddAsync requires a function"
)
}
let functionName = funcDecl.name.text
let asyncFunctionName = "(functionName)Async"
// Analyze parameters to find the completion handler
let parameters = funcDecl.signature.parameterClause.parameters
// Filter parameters (exclude completion handler)
var regularParams: [String] = []
var completionType: String? = nil
for param in parameters {
let paramType = param.type.description
// Detect a completion handler (closure with Result or simple value)
if paramType.contains("->") && paramType.contains("Void") {
// Extract the return type from completion
completionType = extractCompletionReturnType(from: paramType)
} else {
let paramName = param.firstName.text
let paramSecondName = param.secondName?.text
let label = paramSecondName ?? paramName
regularParams.append("(paramName): (paramType)")
}
}
guard let returnType = completionType else {
throw MacroError.invalidSyntax(
"No completion handler found"
)
}
// Generate arguments for internal call
let callArgs = parameters.dropLast().map { param in
let name = param.firstName.text
return "(name): (name)"
}.joined(separator: ", ")
// Generate the async function
let asyncFunc: DeclSyntax = """
func (raw: asyncFunctionName)((raw: regularParams.joined(separator: ", "))) async throws -> (raw: returnType) {
try await withCheckedThrowingContinuation { continuation in
(raw: functionName)((raw: callArgs.isEmpty ? "" : callArgs + ", ")completion: { result in
switch result {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
})
}
}
"""
return [asyncFunc]
}
// Extract return type from a Result type
private static func extractCompletionReturnType(from type: String) -> String {
// Simplified pattern - in production, use the AST
if let match = type.range(of: #"Result<([^,]+)"#, options: .regularExpression) {
var result = String(type[match])
result = result.replacingOccurrences(of: "Result<", with: "")
return result.trimmingCharacters(in: .whitespaces)
}
return "Void"
}
}AddAsync マクロのデモ
class NetworkService {
@AddAsync
func fetchUser(
id: Int,
completion: @escaping (Result<User, Error>) -> Void
) {
// Implementation with callback
URLSession.shared.dataTask(with: URL(string: "/users/(id)")!) { data, _, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
let user = try? JSONDecoder().decode(User.self, from: data)
completion(.success(user!))
}
}.resume()
}
// Automatically generates:
// func fetchUserAsync(id: Int) async throws -> User {
// try await withCheckedThrowingContinuation { continuation in
// fetchUser(id: id, completion: { result in
// switch result {
// case .success(let value):
// continuation.resume(returning: value)
// case .failure(let error):
// continuation.resume(throwing: error)
// }
// })
// }
// }
}
// Modern usage with async/await
let user = try await networkService.fetchUserAsync(id: 42)生成される関数名は @attached 属性の names: で宣言する必要があります。ここでは suffixed(Async) を指定しており、生成される関数は元の関数名に「Async」が付与された名前になります。
マクロの単体テスト
マクロは生成したコードがコンパイルされるため、テストが欠かせません。Swift では SwiftSyntaxMacrosTestSupport がテストを支援します。swift-syntax 603.0.0 以降では、XCTest に加えて Swift Testing もサポートされています。
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
@testable import MyMacrosPlugin
final class MyMacrosTests: XCTestCase {
// Dictionary of macros to test
let testMacros: [String: Macro.Type] = [
"unwrap": UnwrapMacro.self,
"AutoInit": AutoInitMacro.self,
"AddAsync": AddAsyncMacro.self,
"Remote": RemoteMacro.self
]
func testUnwrapMacroExpansion() throws {
assertMacroExpansion(
"""
let value = #unwrap(optionalString)
""",
expandedSource: """
let value = {
guard let value = optionalString else {
fatalError("Failed to unwrap 'optionalString' - value was nil")
}
return value
}()
""",
macros: testMacros
)
}
func testAutoInitMacroWithStruct() throws {
assertMacroExpansion(
"""
@AutoInit
struct Point {
let x: Int
var y: Int
}
""",
expandedSource: """
struct Point {
let x: Int
var y: Int
public init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
""",
macros: testMacros
)
}
func testAutoInitWithDefaultValues() throws {
assertMacroExpansion(
"""
@AutoInit
struct Config {
var timeout: Int = 30
var retryCount: Int
}
""",
expandedSource: """
struct Config {
var timeout: Int = 30
var retryCount: Int
public init(timeout: Int = 30, retryCount: Int) {
self.timeout = timeout
self.retryCount = retryCount
}
}
""",
macros: testMacros
)
}
func testAutoInitFailsOnEnum() throws {
assertMacroExpansion(
"""
@AutoInit
enum Status {
case active
}
""",
expandedSource: """
enum Status {
case active
}
""",
diagnostics: [
DiagnosticSpec(
message: "@AutoInit can only be applied to structs and classes",
line: 1,
column: 1
)
],
macros: testMacros
)
}
func testRemoteMacroExpansion() throws {
assertMacroExpansion(
"""
@Remote
func fetchUser(id: Int) async throws -> User
""",
expandedSource: """
func fetchUser(id: Int) async throws -> User {
let params: [String: Any] = ["id": id]
return try await RPCClient.shared.call(
method: "fetchUser",
params: params
)
}
""",
macros: testMacros
)
}
}このテストでは正しいコード展開と、不適切な使用時の適切なエラーメッセージの両方を確認します。
マクロのデバッグと検査
Xcode にはマクロをデバッグし、生成されたコードを把握するための複数のツールが用意されています。
Xcode での展開
// Right-click on macro call → "Expand Macro"
// Displays generated code inline
@AutoInit
struct Product {
let id: UUID
var name: String
var price: Decimal
}
// To see the expansion:
// 1. Right-click on @AutoInit
// 2. Select "Expand Macro"
// 3. Generated code displays inline for inspection and debugging開発中のロギング
public struct DebugMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// Print the node's AST to understand the structure
print("=== DEBUG MACRO ===")
print("Node: (node)")
print("Arguments: (node.argumentList)")
// Complete dump of the syntax tree
dump(node)
// Continue with normal expansion
return "42"
}
}swift-ast-explorer で AST を観察する
オンラインツール swift-ast-explorer.com では、任意の Swift コードの構文木を視覚的に確認できます。マクロ実装時に AST のノードをどのように辿るかを理解するうえで欠かせません。Swift Testing フレームワークガイドで解説している Swift 組み込みのテストマクロを確認する際にも役立ちます。
Swift Macros のベストプラクティス
保守しやすいマクロを作るには、いくつかの規約を守り、よくある落とし穴を避ける必要があります。
バリデーションとエラーメッセージ
public struct ValidatedMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Validate usage context
guard declaration.is(StructDeclSyntax.self) else {
// Clear error messages with possible localization
context.diagnose(
Diagnostic(
node: node,
message: MacroDiagnosticMessage(
id: "invalid-target",
message: "This macro can only be applied to structs",
severity: .error
)
)
)
return []
}
// Check required arguments
guard let arguments = node.arguments else {
context.diagnose(
Diagnostic(
node: node,
message: MacroDiagnosticMessage(
id: "missing-args",
message: "Required arguments missing",
severity: .error
)
)
)
return []
}
// Implementation...
return []
}
}
// Structure for diagnostic messages
struct MacroDiagnosticMessage: DiagnosticMessage {
let id: String
let message: String
let severity: DiagnosticSeverity
var diagnosticID: MessageID {
MessageID(domain: "MyMacros", id: id)
}
}読みやすいコードを生成する
// Avoid: hard-to-read generated code
let badCode: DeclSyntax = "public init(a:Int,b:String,c:Bool){self.a=a;self.b=b;self.c=c}"
// Preferred: properly formatted generated code
let goodCode: DeclSyntax = """
public init(
a: Int,
b: String,
c: Bool
) {
self.a = a
self.b = b
self.c = c
}
"""生成されるコードは「Expand Macro」で開発者が確認するため、手書きのコードと同じくらい読みやすくする必要があります。
ソース
- SE-0415: Function Body Macros、Swift 6.0 で実装
- swift-syntax releases、Swift 6 向けに 600.0.0 以降を使用
- Swift.org Blog、Swift 6.3 リリースノート
Swift Macros で実現できること
Swift Macros は静的型安全性を保ったままボイラープレートを取り除く強力な手段です。この技術により次のことが実現できます。
- 2 つのカテゴリ:freestanding(
#)と attached(@) - Swift 6 では 6 つの attached ロール:peer、accessor、member、memberAttribute、extension、そして新しい body ロール
- swift-syntax 600.0.0 以降と AST 操作による実装
SwiftSyntaxMacrosTestSupportを用いた必須のテスト(Swift Testing にも対応)- 実装のための専用パッケージが必要
- Xcode の「Expand Macro」によるデバッグ
- 開発者体験に欠かせない明確なエラーメッセージ
Swift Macros は Equatable や Codable などの準拠の生成、高度なプロパティラッパーの作成、シグネチャから RPC 実装を合成する処理、コールバックベースの API を async/await へモダナイズする際に特に役立ちます。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
iOS のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年8月23日 更新
タグ
共有
関連記事

Swift における Combine vs async/await:段階的な移行パターン
Swift で Combine から async/await への移行を進めるための完全ガイド:段階的な戦略、ブリッジングパターン、iOS コードベースにおけるパラダイムの共存。

2026年のiOSアクセシビリティ面接質問: VoiceOverとDynamic Type
iOS面接に向け、VoiceOver、Dynamic Type、セマンティックtraits、監査などアクセシビリティの重要質問を解説します。

StoreKit 2面接対策:サブスクリプション管理とレシート検証
StoreKit 2、サブスクリプション管理、レシート検証、アプリ内課金実装に関するiOS面接の質問を、実用的なSwiftコード例とともにマスターしましょう。