こんにちは、西 @jrsaruo_tech です。
iOSDC Japan 2025 Day1 前半戦です!
アンドパッドのブースでは本日も新しいクイズを出題しています。
- Day0
- Day1 午前 ← 本記事
- Day1 午後
- Day2 午前
- Day2 午後
本記事ではDay1 午前に出題したクイズ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
Q1 ★☆☆☆☆
問題
変数valueの値を文字列に挿入するコードとして正しいものを選んでください。
-
A
"number is $value" -
B
"number is ${value}" -
C
"number is \value" -
D
"number is \(value)"
解答
D
解説
Swiftで変数valueの値を文字列に挿入するにはDの"number is \(value)"という記法を用います。
let value = 10 print("number is \(value)")
Q2 ★★☆☆☆
問題
/* ? */に当てはめてコンパイルが通るものを選んでください。
struct FooError: Error {} func foo() throws {} do { try foo() } { print("FooError") } catch { print(error) }
-
A
catch FooError -
B
catch FooError() -
C
catch is FooError -
D
catch as FooError
解答
C
解説
FooError型のエラーを個別に捕捉するための書き方を問うクイズです。
catch句にはパターンを書きます。パターンとは値の構造を表したもので、値のマッチや構造の分解に用いられます。小難しいことを言いましたが簡単に言えばswitch文のcaseに書けるようなやつです。
パターンは色々あるので、興味がある方は以下のページをご覧ください。
The Swift Programming Language – Patterns
switch value { case is Foo: break case 1...5: break case _: break }
選択肢のなかでコンパイルが通るのはCです。is FooErrorはtype-castingパターンと呼ばれ、対象の値(ここでは投げられたエラー)がFooError型であればマッチします。
選択肢AのFooError(型名のみ書いたもの)や選択肢Dのas FooErrorのようなパターンは存在せず、コンパイルが通りません。asを使ったtype-castingパターンは正しくはcatch let error as FooErrorのような形です。この場合は対象の値がFooError型であればマッチし、FooError型にキャストされた値が変数errorにバインドされます。
選択肢Bは少し特殊です。FooError()はexpressionパターンとして解釈できますが、expressionパターンとのマッチングではそれに対応する~=演算子(パターンマッチング演算子)のオーバーロードが必要となります。ここでは対象の値any ErrorとexpressionパターンFooError()とのパターンマッチングを担う以下のような~=演算子が必要です。
func ~= (pattern: FooError, value: any Error) -> Bool
このようなオーバーロードはデフォルトでは提供されていないため、自前で実装しない限りcatch FooError()はコンパイルが通りません。
ここで、enumで定義したエラー型なら特に何もしなくてもcatch EnumError.fooのように書けるのでは?と思った方もいるかもしれません。確かにこれはコンパイルが通るのですが、実はこれはenumeration caseパターンという別のパターンとして解釈されています。そちらでは上記のような~=演算子は必要とされません。
Q3 ★★★☆☆
問題
次のコードのコンパイルを通すために、MyIntが最低限適合しなければならないプロトコル(必要最低限の実装だけ要求されるもの)を選んでください。
struct MyInt { var value: Int } let five: MyInt = 5
- A:
BinaryInteger - B:
Numeric - C:
IntegerLiteralType - D:
ExpressibleByIntegerLiteral
解答
D
解説
5や0.5、"abc"、true、nilなど、ソースコードに直接書かれた値そのものをリテラルと呼びます。
このクイズは、独自の型MyIntを5のような整数リテラルで初期化できるようにするための方法を問う問題です。
実はSwiftのリテラル自体には型がありません。5と書いたからといってInt型であるとは限らず、"a"と書いたからといってString型であるとは限りません。
let a = 5と書いたときにaがInt型と推論されるのは、整数リテラルがデフォルトでInt型として推論されるようになっているからです。以下のコードも、5というIntをInt8型に変換しているわけではなく、5というリテラルから直接Int8型の値を作っています。
let a: Int8 = 5
そしてこの機能を実現しているのは各リテラルに対応するExpressibleByXXLiteralというプロトコルです。例えば整数リテラルにはExpressibleByIntegerLiteral、文字列リテラルにはExpressibleByStringLiteralが対応します。
ExpressibleByIntegerLiteralに適合させた型は整数リテラルで値を初期化することができるようになります。というわけで答えはDです。
extension MyInt: ExpressibleByIntegerLiteral { init(integerLiteral value: IntegerLiteralType) { self.value = value } } let five: MyInt = 5
選択肢AのBinaryIntegerは2進数で表現される整数を表すプロトコル、選択肢BのNumericは掛け算(と足し算)ができる値を表すプロトコル、選択肢CのIntegerLiteralTypeはプロトコルではなくIntの型エイリアスです。
ちなみにBinaryIntegerはNumericを、NumericはExpressibleByIntegerLiteralを継承しているため、実はMyIntをこれらのいずれかに適合させればlet five: MyInt = 5はコンパイルが通ります。とはいえ問題文には「最低限適合しなければならないプロトコル」とあるので、正解はやはりExpressibleByIntegerLiteralです。
IntegerLiteralTypeは上書きすると面白いことが起きるのですが、それはまた別の機会に。
Q4 ★★★★☆
問題
次のなかでコンパイルが通るコードをすべて選んでください。
ただし、独自の==関数は定義されていないものとします。
-
A
func checkEquality(_ lhs: AnyHashable, _ rhs: AnyHashable) -> Bool { lhs == rhs } -
B
func checkEquality(_ lhs: any Equatable, _ rhs: any Equatable) -> Bool { lhs == rhs } -
C
func checkEquality(_ lhs: some Equatable, _ rhs: some Equatable) -> Bool { lhs == rhs } -
D
func checkEquality(_ lhs: T, _ rhs: T) -> Bool where T: Equatable { lhs == rhs }
解答
A、D
解説
各選択肢を検討する前に、==演算子について確認しておきましょう。
ある型Aのインスタンスa、型Bのインスタンスbについて考えます。これらの間でa == bのように==演算子を利用できるかどうかは、それに対応する==演算子のオーバーロード実装があるかどうかで決まります。
func == (lhs: A, rhs: B) -> Bool { ... } extension A { static func == (lhs: A, rhs: B) -> Bool { ... } } (==)(a, b)
そして、Equatableプロトコルに適合した型は後に示す==演算子のオーバーロード実装を持っています。
したがって、lhs == rhsが通るかどうかは
Equatable由来の==実装が利用できるか- または
Equatable由来でない==実装が提供されているか
の2点を確認すれば良いです。そして今回の選択肢において後者は提供されていないので、前者の「Equatable由来の==実装が利用できるか」だけをチェックすればOKです。
ではその条件を調べるためにEquatableの定義を見てみましょう。
public protocol Equatable { static func == (lhs: Self, rhs: Self) -> Bool }
Equatableが要求する==メソッドには、lhsとrhsがいずれも同じSelf型であるという制約があります。SelfとはEquatableに適合した型自身です。重要なのは両辺が同じ型であることです。
extension Foo: Equatable { static func == (lhs: Foo, rhs: Foo) -> Bool { ... } }
この制約により、例えばInt同士やString同士の比較はできる一方、IntとString間での比較はできないようになっています。
10 == 10 "a" == "a" 10 == "a"
以上を踏まえると、lhs == rhsという式において
- ①
lhsの型TがEquatableに適合している - ②
rhsの型もTである
という条件を共に満たしていれば、以下のようなEquatable由来の==実装が利用できるためコンパイルが通ります。
static func == (lhs: T, rhs: T) -> Bool { ... }
それではこれらの条件①②をふまえて選択肢を見ていきましょう。
A:OK
以下のように条件①②を共に満たしているためコンパイルが通ります。
- ①
lhsの型AnyHashableはEquatableに適合している - ②
rhsの型もAnyHashableである
繰り返しになりますがAnyHashableはEquatableに適合しているため必ず以下の==メソッドを持っており、これを利用できるわけですね。
static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool
B:NG
lhsの型any EquatableはEquatableに適合していないため、条件①を満たせずコンパイルが通りません。
なぜany EquatableがEquatableに適合していないのかの説明は割愛しますが、気になる方は”self-conformance”というワードで調べてみてください。
C:NG
lhsの型some EquatableはEquatableに適合しているため、条件①は満たされます。
そして一見lhsとrhsはどちらも同じsome Equatable型に見えるので条件②も満たされそうですが、some Equatableとはあくまで「Equatableに適合した何か」でしかなく、lhsとrhsが同じ型であるとは限りません。例えばlhsにはInt型、rhsにはString型の値が渡ってくるかもしれません。
func checkEquality(_ lhs: some Equatable, _ rhs: some Equatable) -> Bool { ... } func checkEqualityT1, T2>(_ lhs: T1, _ rhs: T2) -> Bool where T1: Equatable, T2: Equatable { ... } checkEquality(10, "a")
したがって条件②を満たしておらずコンパイルが通りません。「lhsとrhsは同じ型である」という制約を課すためには、次の選択肢Dのように型パラメータを導入する必要があります。
D:OK
以下のように条件①②を共に満たしているためコンパイルが通ります。
- ①
lhsの型TはEquatableに適合している - ②
rhsの型もTである
今回も長くなってしまいましたが、ここまで読んでくださりありがとうございました。
感想等お待ちしております。
午後も新しいクイズが出題されるのでぜひまたアンドパッドブースまで遊びにきてください!
Day2にはTrack Bにて11:25より『ネイティブ製ガントチャートUIを作って学ぶUICollectionViewLayoutの威力』というタイトルで登壇しますので、そちらもぜひお越しください。
さらにLTにも弊社やまひろ @yamahiro248 が登壇します。こちらもお楽しみに!
引き続きiOSDC楽しんでいきましょう!