こんにちは、アンドパッドでiOSアプリ開発を担当している西 @jrsaruo_tech です。
今年もいよいよiOSDC Japan 2025が始まりましたね!私はDay2に初登壇を控えており気が気ではありません。
今年のアンドパッドブースではSwiftクイズを出題しています。以下の5回に分けてそれぞれ4題ずつ、合計20題のクイズを出題します。
本記事ではDay0に出題したクイズ4題を解説します。
それぞれの解答と解説はセクションを閉じているので、まだ解かれていない方はぜひチャレンジしてみてください。
※ クイズで使用しているSwiftのバージョンは6.1.2、言語モードは6です。
% swift --version swift-driver version: 1.120.5 Apple Swift version 6.1.2 (swiftlang-6.1.2.1.2 clang-1700.0.13.5) Target: arm64-apple-macosx15.0
クイズ Day0
Q1 ★★☆☆☆
問題
/* ? */に当てはまるコードとして正しいものを選んでください。
func sorting(_ numbers: [Int]) -> [Int] { /* ? */ }
A
numbers.sorted()B
numbers.sort()
解答
A
解説
Array型のsorted()、sort()メソッドのシグネチャはそれぞれ以下の通りです。
// 自身を変更せず、自身をソートした配列を新たに作って返す func sorted() -> [Self.Element] // 自身の中身をソートし何も返さない mutating func sort()
問題のsorting(_:)関数は返り値として[Int]を返すので、/* ? */には[Int]を返すsorted()が当てはまります。したがって答えはAです。
なおSwiftのAPI Design Guidelinesでは、メソッドの役割が動詞で表現される場合以下のルールで命名することが推奨されています。
- 自身を加工するなら:動詞の命令形
- 例)
sort()
- 例)
- 自身を加工しないなら:
- 目的語を取らないなら:動詞の過去分詞形(
-ed)- 例)
sorted()
- 例)
- 目的語を取るなら:動詞の現在分詞形(
-ing)- 例)
sorting(_:)
- 例)
- 目的語を取らないなら:動詞の過去分詞形(
When the operation is naturally described by a verb, use the verb’s imperative for the mutating method and apply the “ed” or “ing” suffix to name its nonmutating counterpart.
Q2 ★★☆☆☆
問題
次のなかで、/* ? */に当てはめるとコンパイルが通らないものを選んでください。
struct FooError: Error {} func foo(isFoo: Bool) throws { /* ? */ print("is foo") }
A
guard isFoo else { return }B
guard isFoo else { throw FooError() }C
guard isFoo else { assertionFailure() }D
guard isFoo else { fatalError() }
解答
C
解説
Swiftのguard構文は以下のようなものです。主に早期脱出に用いられます。
guard shouldBeTrue else { /* shouldBeTrueがfalseならここに入る。guard以降の処理に進まないようなコードを書くことが強制される */ } assert(shouldBeTrue, "ここに到達するなら必ずshouldBeTrueが成り立っている")
guard構文は「条件を満たした場合に限り後続の処理を実行できる」という挙動を保証します。
そのため、guardの条件式がfalseとなりelse節に入った場合、guardの後に続く処理が実行されないよう何らかの手段を取ることがコンパイラによって強制されます。
処理を続行させない手段としては以下が挙げられます。
returnで値を返すthrowでエラーを投げるbreakで分岐や繰り返し処理から抜けるcontinueで次の繰り返し項目の処理に飛ぶNever型(あるいはそれと同じ構造の独自型)を返すプロパティや関数を呼ぶpreconditionFailure、fatalError、exitなどNever型のインスタンスは作れないので、上記を呼んだ時点で後続の処理は確実に実行されない(Q3で詳しく解説します)
while true {}で無限ループさせる(これもOKなのは面白いですね)
したがって選択肢A、B、Dはコンパイルが通ります。
一方、選択肢CのassertionFailureは返り値型がVoidです。
func assertionFailure( _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line )
assertionFailureは最適化の効かない環境(-Onone)ではクラッシュしますが、そうでなければ処理が継続してしまいます。というわけでこのクイズの答えはCです。
余談ですが、guardの前ですでに上記の手段で脱出or続行不可にしていた場合はelseの中で何もしなくてもコンパイルが通ります。guard以降の処理は必ず実行されないためです。これも面白いですね。
return guard shouldBeTrue else { // NOP } assertionFailure("shouldBeTrueがtrueかどうかにかかわらずここには到達しない")
Q3 ★★★★☆
問題
fatalError関数の返り値型として正しいものを選んでください。
- A:
Void - B:
Int - C:
String - D:
Never
解答
D
解説
fatalError関数の定義は以下の通りです。
func fatalError( _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line ) -> Never
返り値型がNeverとなっていますね。答えはDです。
…だけでは面白くないので、Never型についてもう少し掘り下げてみます。
まずは実際の定義を見てみましょう。
public enum Never {}
なんとただの空のenumです。
たったこれだけの定義ですが、「Never型のインスタンスは作ることができない」という重要で興味深い性質を持っています。enumのインスタンスはcaseを指定して作りますが、その肝心のcaseが存在しないからです。
この性質を利用すると様々な面白い仕組みを実現できます。その一例が今回出題したfatalErrorです。
インスタンスの存在しないNever型を関数の返り値とすることで、その関数が値を返せない、つまり処理が継続できなくなることを型で表現できるのです(Voidとは区別してください。返り値がVoid型の関数は値を返さないのではなく、Void型唯一のインスタンスである()を返しています)。
処理が継続できないというのは以下のような状況です。
- 処理が永遠に終了しない(無限ループなど)
- プログラムの実行が終了する
これの何が嬉しいかと言うと、Never型を返す関数を呼べばそれ以降の処理が実行されないことがコンパイル時点で分かるので、例えば後続の処理にWill never be executedという警告を出したり、Q2の選択肢Dのようにguard文のelse節で呼ぶだけでコンパイルを通したりすることができます。
func returnsNever() -> Never { while true {} } returnsNever() // これ以降処理が継続しないことが保証されている print("ここは絶対に実行されない") // Warning: Will never be executed
fatalErrorはプログラムの実行を終了させるのでNever型を返す関数として定義されているわけです。
個人的には関数のシグネチャが"fatalError returns never"と読めるのも綺麗だなと思います。
Q4 ★★★★☆
問題
コンパイルが通る組み合わせとして正しいものを選んでください。
protocol FooProtocol {} struct FooA: FooProtocol {} struct FooB: FooProtocol {} func makeSomeFoo() -> some FooProtocol { FooA() } func makeAnyFoo() -> any FooProtocol { FooB() } func useSomeFoo(_ foo: some FooProtocol) {} func useAnyFoo(_ foo: any FooProtocol) {} // 1 useSomeFoo(makeSomeFoo()) // 2 useSomeFoo(makeAnyFoo()) // 3 useAnyFoo(makeSomeFoo()) // 4 useAnyFoo(makeAnyFoo())
- A: 1, 4
- B: 3, 4
- C: 1, 3, 4
- D: 1, 2, 3, 4
解答
D
解説の前に
クイズの解説に入る前に、まずはsome FooProtocolとany FooProtocolについて整理します。
それぞれ理解されている方は「解説」まで読み飛ばしてください。
some FooProtocol
some FooProtocolは関数シグネチャのどこに現れるかによって異なる意味を持ちますが、いずれも本質的な特徴は以下の通り共通しています。
FooProtocolに適合した何らかの型であること- 具体的な型が何であるかは利用者には分からないこと
- 具体的な型はコンパイル時点で定まること
some FooProtocolは単に型名を隠しているだけで、具体的なFooAなどに置き換えても支障がない、と考えると分かりやすいかもしれません。
これを念頭に
- 返り値に現れる
some FooProtocol - 引数に現れる
some FooProtocol
について見ていきましょう。
返り値に現れるsome FooProtocol
func makeSomeFoo() -> some FooProtocol
これはOpaque Result Typesと呼ばれる機能です。
makeSomeFoo()はFooProtocolに適合した何らかの型の値を返します。
呼び出し側には「FooProtocolに適合した何かが返される」ことしか分からず、具体的にFooAが返されるのかFooBが返されるのか(あるいは他の型か)は分かりません。
具体的な型はコンパイル時点でただ1つに決まります。すなわち、呼び出し方によって返り値がFooAになったりFooBになったりとコロコロ変わることはありません。
もしFooAを返す関数なのだとすれば、some FooProtocolをFooAに置き換えても支障がありません。このFooAを隠しているだけです。
func makeSomeFoo() -> FooA { FooA() }
引数に現れるsome FooProtocol
func useSomeFoo(_ foo: some FooProtocol)
これはジェネリック関数をより簡易的に書けるようにしたシンタックスシュガーで、以下と等価です。
func useSomeFoo<Foo>(_ foo: Foo) where Foo: FooProtocol
useSomeFoo(_:)はFooProtocolに適合した何らかの型の値を受け取ります。
useSomeFoo(_:)にとっては「fooがFooProtocolに適合した何かである」ことしか分からず、具体的にFooAが渡されるのかFooBが渡されるのかは分かりません。
コンパイラは具体的にどんな型の値が渡されたのかを知っているので、(特殊化と呼ばれる最適化が効けば)fooをFooA型だと分かったうえで扱うことができます。
useSomeFoo(FooA()) // fooの型はFooAに定まる useSomeFoo(FooB()) // fooの型はFooBに定まる
FooAを受け取るようなコードにおいては、some FooProtocolをFooAと書き換えても支障がありません(コンパイラによる最適化が効けば実際にこのような関数が生成されます)。
func useSomeFoo(_ foo: FooA) {} useSomeFoo(FooA())
any FooProtocol
これはExistential Typesと呼ばれる型です。
「FooProtocolに適合した型の値なら何でも入れられる箱」だと考えてください。「箱」の中に何が入っているかはコンパイル時点では分かりませんが、中身がFooProtocolに適合していることだけは確かです。
/* any FooProtocolという「箱」を返す。 「箱」の中にはFooProtocolに適合したものなら何でも入り得る。 呼び出すたびに中身がFooAだったりFooBだったり変化するかもしれない(コンパイル時点で確定しない)。 */ func makeAnyFoo() -> any FooProtocol /* any FooProtocolという「箱」を受け取る。 「箱」の中にはFooAが入っているかもしれないしFooBが入っているかもしれない(コンパイル時点で確定しない)。 */ func useAnyFoo(_ foo: any FooProtocol)
何でも入れることができて便利な反面、メモリ利用やメソッド呼び出しなどにおいてオーバーヘッドがあります。
any FooProtocolをFooAやFooBに置き換えることはできません。any FooProtocolはそれらを入れられる「箱」であり、FooAやFooBそのものとは異なる型だからです。
let foos: [any FooProtocol] = [FooA(), FooB()] let foos: [FooA] = [FooA(), FooB()] // NG
普段使いにおいてはざっくり
some FooProtocolはパフォーマンスが良いが制限が強めany FooProtocolはパフォーマンスが落ちる分自由度が高くなる
くらいの理解でもOKです。
解説
前提がかなり長くなりましたが、以上を踏まえて1〜4のコードを見てみましょう。
1. useSomeFoo(makeSomeFoo()):OK
makeSomeFoo()はFooProtocolに適合した何かを返します。
useSomeFoo(_:)はFooProtocolに適合した何かを受け取れます。
useSomeFoo(_:)の引数にmakeSomeFoo()の返り値を渡しても型は破綻しないので、コンパイルが通ります。
以下2つの関数のFooAが隠されただけ、と考えると分かりやすいです。
func makeSomeFoo() -> FooA func useSomeFoo(_ foo: FooA)
2. useSomeFoo(makeAnyFoo()):OK
結論から言うと、これもコンパイルが通ります。
実は古いバージョンのSwiftでは通らなかったのですが、以下のプロポーザルが採用されて通るようになりました。
SE-0352: Implicitly Opened Existentials
useSomeFoo(_:)はFooProtocolに適合した何かを受け取ります。makeAnyFoo()の返り値はany FooProtocolという「箱」であり、この「箱」自体はFooProtocolには適合していません。そのためかつてはany FooProtocolな値をuseSomeFoo(_:)に渡すことはできませんでした。
しかし、Swift 5.7で「any FooProtocolという『箱』から中身を取り出して、some FooProtocolを受け取る関数に渡せるようにする」という機能が導入されました(厳密にはSwift 5系ではupcoming feature flagで機能を有効化する必要があります)。
any FooProtocolという「箱」の中にはFooProtocolに適合した何かが入っているので、取り出してしまえばsome FooProtocolとして扱えるわけです。
概念を擬似コードで示すとこんな感じです:
// makeAnyFoo()からanyFooという「箱」を受け取る。これはFooProtocolには適合していない let anyFoo: any FooProtocol = makeAnyFoo() // 「箱」の中から具体的な値を取り出す(open)。これはFooProtocolに適合している let concreteFoo: some FooProtocol = open(anyFoo) // 注)あくまでイメージで、実際にopenという関数があるわけではない // FooProtocolに適合した何かをuseSomeFoo(_:)に渡す useSomeFoo(concreteFoo)
3. useAnyFoo(makeSomeFoo()):OK
前提として、FooProtocolに適合した型の値はany FooProtocol型の変数に直接代入可能です。自動で「箱」に包んでくれるようになっています。関数の引数に渡すときも同様です。
let anyFoo: any FooProtocol = FooA() // OK useAnyFoo(FooA()) // OK
makeSomeFoo()はFooProtocolに適合した何かを返すので、その返り値はuseAnyFoo(_:)に直接渡すことができます。
以下のmakeSomeFoo()のFooAが隠されただけ、と考えると分かりやすいです。
func makeSomeFoo() -> FooA func useAnyFoo(_ foo: any FooProtocol)
4. useAnyFoo(makeAnyFoo()):OK
これはもうそのままですね。makeAnyFoo()はany FooProtocolという「箱」を返し、useAnyFoo(_:)はその「箱」をそのまま受け取れるので、これもコンパイルが通ります。
以上からこのクイズの答えはDの1, 2, 3, 4、つまりすべてのコンパイルが通る、でした。
おわりに
長くなってしまいましたが、ここまで読んでくださりありがとうございました。
Day0のクイズは楽しんでいただけたでしょうか?感想等もお待ちしております!
明日以降もDay1午前、Day1午後、…とクイズが更新されていくのでぜひまたアンドパッドブースまで遊びにきてください。
Day2にはTrack Bにて11:25より『ネイティブ製ガントチャートUIを作って学ぶUICollectionViewLayoutの威力』というタイトルで登壇しますので、そちらもぜひお越しください。
さらにLTにも弊社やまひろ @yamahiro248 が登壇します。こちらもお楽しみに!
それでは3日間楽しんでいきましょう!