Swiftツアー(A Swift Tour)
最終更新日: 2024/11/7 原文: https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html
Swift の特徴とシンタックスを探る。
伝統的に、新しい言語の最初のプログラミングは "Hello world!" を画面に表示することを勧めます。これを Swift ではたった 1 行で達成できます。
print("Hello, world!")
// Hello, world!他の言語を知っているなら、この構文は馴染み深いものでしょうが、Swift ではこの 1 行のコードでプログラミングは完成しています。テキストの出力や文字列を扱うために他のライブラリを import する必要もありません。グローバル領域に書かれたコードはプログラムのエントリポイントとして使用されます。main() 関数は必要ありません。全てのステートメントの末尾にセミコロンをつける必要もありません。
このツアーでは、様々なプログラム上のタスクを遂行する方法を示すことで、Swift でコードを書き始めるために必要十分な情報を提供します。もし理解できなくても心配しないでください。このツアーで紹介することは全て、この本で後々詳細に説明しています。
シンプルな値(Simple Values)
定数を作成するのに let、変数を作成するのに var を使います。定数の値はコンパイル時に知る必要はありませんが、正確に一度だけ値を設定しなければなりません。つまり、値の指定をたった一度だけ行い、他のあらゆる場所からその定数を利用できます。
var myVariable = 42
myVariable = 50
let myConstant = 42定数や変数は設定したい値と同じ型でなければなりません。しかし、明示的に型を記載する必要はありません。定数や変数を作成する時に値を与えることで、コンパイラが型を推論します。例えば、コンパイラは myVariable の値が整数ということから、型を整数と推論します。
もし、最初に設定する値が十分な情報を提供しなかった場合(もしくは最初に設定する値ではなかった場合)、変数の後にコロン(:)を付けて型を書くことで特定することができます。
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70Experiment 値が
4でFloatを明示的に指定した定数を作成してみましょう。
値は暗黙的に他の型に変換されることはありません。他の型に変換したい場合は、変換したい型のインスタンスを明示的に作成してください。
Experiment 最後の行から
Stringへの変換を消してみてください。どんなエラーが起きるでしょうか?
もっと簡単な方法で文字列の中に値を含めることができます。値を括弧で囲み、括弧の前にバックスラッシュをつけます。
Experiment 文字列に浮動小数点の計算を含めたり、誰かの名前を挨拶に含めるために、
\()を使用してみましょう。
複数行の文字列に対しては、3 つのダブルクォーテーション(""")を使いましょう。末尾のクォーテーションに到達するまで、各行の最初のインデントは除外されます。
配列や辞書の作成には角括弧([])を使い、それらの要素へアクセスする際は角括弧内にインデックスやキーを書きます。最後の要素の後ろにもカンマ(,)を付けることができます。
配列は要素を追加すると自動でサイズが大きくなります。
また、括弧を使用して、空の配列または辞書を記述します。配列の場合は []、辞書の場合は [:] と書きます。
空の配列または辞書を新しい変数、または型情報がない別の場所に割り当てる場合は、型を指定する必要があります。
制御フロー(Control Flow)
条件分岐を作成するには、 if と switch を使います。ループを作成するには for-in while repeat-while を使います。条件やループ変数やオプショナルに丸括弧(())を付けるかどうかは任意です。本文の周りの中括弧({})は必須です。
if 文の中で、条件はブール式でなければなりません。つまり、if score { ... } などのコードはエラーで、暗黙的な 0 との比較は行われません。
条件に基づいた値を選ぶために、代入時の等号(=)の後ろや return の後に、if または switch を書くことができます。
if と let を一緒に使用して、存在しないかもしれない値を扱うことができます。これらの値はオプショナルで表現されます。オプショナルは値が含まれているか、値が存在しないことを示す nil を含んでいます。値をオプショナルにするには、値の型の後ろにクエスチョンマーク(?)を書きましょう。
Experiment
optionalNameをnilに変えてみましょう。どんな挨拶が出力されるでしょうか?optionalNameがnilの場合に別の挨拶が出力されるようにelseを追加してみましょう。
もしオプショナル値が nil の場合、条件は false になり、中括弧内のコードはスキップされます。nil でなければ、オプショナル値はアンラップされて let の後の定数に代入され、中括弧内のブロック内でその値を利用できます。
オプショナル値を扱うもう 1 つの方法として、?? 演算子を使用してデフォルトの値を提供する方法があります。もしオプショナル値が nil の場合、代わりにデフォルトの値が使われます。
アンラップ後の変数に同名を用いる場合、右辺を省略することができます。
switch は、あらゆる種類のデータと比較のための演算子を扱うことができます。(整数や等価チェックだけに限定されません)
Experiment
defaultのケースを削除してみましょう。どんなエラーが起きるでしょうか?
パターンに合った値を定数に設定するために let がどう使われるかに注目してください。
パターンに合った switch case 内のコードが実行された後、プログラムは switch 文から抜け出します。次の case は実行されないので、各 case の最後に明示的に break を書く必要はありません。
for-in を使用することで辞書のそれぞれの要素のキーバリューペアを受け取って、辞書内のアイテムに反復処理をすることができます。辞書は順序のないコレクションなので、受け取るキーバリューペアの順序は決まっていません。
Experiment
_を変数の名前に置き換えてみて、どの種類の値が最大だったかを追ってみましょう。
while を使用することで、条件が変わるまでブロック内のコードを反復して実行できます。ループの条件を最後に置くことで、ループ内のブロックが少なくとも 1 回実行されるようにすることができます。
Experiment 条件を
m < 100からm < 0に変更し、ループ条件がすでに真である場合のwhileとrepeat-whileの動作の違いを確認しましょう。
..< を使用するとインデックスの範囲を生成でき、ループのインデックスを追うことができます。
..< では後ろの値は除外され、... は両方の値を含みます。
関数とクロージャ(Functions and Closures)
関数の定義には func を使います。括弧(())の中に引数のリスト、その前に関数の名前を付けることで関数を呼び出します。また、-> の後ろに戻り値の型を指定して、パラメータ名や型と区別します。
Experiment
dayパラメータを削除して、greetに今日のランチスペシャルを指定するためのパラメータを追加してみましょう。
デフォルトで、関数はパラメータ名をそのままラベルとして使用します。独自の引数ラベルを設定したい場合は、パラメータ名の前に引数ラベルを記載してください。引数にラベルが不要な場合は、_ を書きましょう。
関数はネストすることができます。ネストした関数は、外側の関数で定義された変数にアクセスすることができます。ネストした関数を使用することで、長くて複雑な関数を整理することができます。
関数は第一級オブジェクトです。つまり、関数は値として他の関数を戻り値にすることができます。
引数に他の関数を受け取ることもできます。
実の所、関数はクロージャの特殊なケースです。クロージャは、後で実行される可能性があるコードブロックのことを指します。クロージャ内のコードは、そのクロージャが作成されたスコープで利用可能な変数や関数へアクセスすることができます。これは、実際に実行されるのが別のスコープ(タイミング)の場合でも当てはまります。上記のネストした関数の例でも同じことが見られます。中括弧({})で囲むことで、名前なしのクロージャを作成することもできます。in を使用することで、コードの本文から引数と戻り値を分離することができます。
Experiment 上記のクロージャを、全ての奇数で 0 を返すように書き換えてみましょう。
より簡潔にクロージャを書く複数の方法があります。クロージャの型が既にわかっている場合(デリゲートのコールバックなど)、パラメータと戻り値の型を省略できます。単一の文のみのクロージャの場合、その文の値が戻り値として暗黙的に返されます。
名前ではなく番号でパラメータを参照できます。このアプローチは、非常に短いクロージャで特に役立ちます。関数の最後の引数として渡されたクロージャは、丸括弧の直後に記述できます。 クロージャが関数の唯一の引数の場合は、丸括弧を完全に省略できます。
オブジェクトとクラス(Objects and Classes)
class をクラス名の前に付けることでクラスを作成することができます。クラス内のプロパティの宣言は、クラス内にあるということを除いて、定数や変数の宣言方法と同じです。同様に、メソッドや関数の宣言も同じように書くことができます。
Experiment
letを使用して定数プロパティを追加してください。また、引数を受け取る別のメソッドも追加してみましょう。
クラスのインスタンスは、クラス名の後に丸括弧(())を付けて生成します。そのインスタンスのプロパティやメソッドにアクセスするには、ドット(.)構文を使います。
このバージョンの Shape クラスは重要なことが抜けています。それは、インスタンスを生成するときにクラスを構築するためのイニシャライザです。init を使ってイニシャライザを作成します。
イニシャライザ内で self を使用して name プロパティと name 引数を区別していることに注目してください。インスタンスの作成時、イニシャライザの引数は関数の呼び出しと同じように渡されます。全てのプロパティは宣言時に値を設定するか(numberOfSides 参照)、イニシャライザ内で値を設定する必要があります。(name 参照)
インスタンスが開放される前に何かクリーンアップ作業が必要な場合、deinit を使ってデイニシャライザを作成します。
サブクラスは、そのクラス名の後に、スーパークラスの名前をコロン(:)で区切って指定します。標準で全てのクラスのルートクラスが定義されていますが、そのサブクラスにする必要はありませんので、必要に応じて指定も省略もできます。
スーパークラスのメソッドをサブクラスでオーバーライドする場合は override を付けます。override を付けずにオーバーライドしようとしている場合は、コンパイラがエラーにします。override を付けたメソッドが実際にはスーパークラスのメソッドをオーバーライドしていない場合も、コンパイラはエラーにします。
Experiment
radiusとnameをイニシャライザの引数に受けとるCircleという名前のNameShapeの別のサブクラスを作ってみましょう。そして、Circleクラスにarea()、simpleDescription()メソッドを実装してみましょう。
単純に値を保持するプロパティ以外に、プロパティが get と set を持つこともできます。
perimeter の set の中で、新しい値は暗黙的に newValue という名前になります。set の後に丸括弧(())で囲んで明示的に指定することもできます。
EquilateralTriangle クラスのイニシャライザは 3 つの異なるステップがあります。
サブクラスで宣言されたプロパティに値を設定します
スーパークラスのイニシャライザを呼びます
スーパークラスで定義されたプロパティの値を変更。この時点で get や set やメソッドを使用して他のセットアップ処理を実行できます
プロパティを計算する必要はないけれども、新しい値を設定する前後で何かコードを実行したい場合、willSet、didSet を使います。このコードは、イニシャライザ以外で値が変更された時に毎回実行されます。例えば、下のクラスは三角形の辺の長さが常に四角形の辺の長さと同じになります。
オプショナル値を扱う場合、? をメソッド、プロパティ、サブスクリプトのような操作の前に書きます。もし ? の前の値が nil の場合、? の後の処理は全て無視され、その式全体の値は nil になります。それ以外、オプショナル値はアンラップされて、? の後は全てアンラップされた値として実行されます。どちらの場合も、式全体はオプショナル値です。
列挙型と構造体(Enumerations and Structures)
列挙型を作成するには enum を使います。クラスやその他の名前の付いた型と同様に、列挙型もメソッドを持つことができます。
Experiment 2つの
Rankの raw value を比較してRankを比較するメソッドを追加してみましょう。
デフォルトで、Swift は 0 から 1 つずつ増加する値を raw value に割り当てますが、明示的に値を指定してこの挙動を変更することもできます。上記の例では、Ace は明示的に 1 を raw value に指定しており、残りは、1 から順番に raw value が指定されます。文字列や浮動小数点も使用することができます。列挙型の個々のケースの raw value には、rawValue プロパティからアクセスすることができます。
Raw value から列挙型のインスタンスを生成するためには init?(rawValue:) イニシャライザを使います。これは raw value に合致したケースを返すか、合致するものがない場合は nil を返します。
列挙型のケースの値は実在の値で、raw value を書くための別の方法ではありません。事実、意味のある raw value が存在しない場合は、raw value を提供する必要はありません。
Experiment
color()メソッドをSuitに追加してみましょう。spadesとclubsは "black" を返し、heartsとdiamindsは "red" を返します。
hearts ケースを参照するために 2 つの方法があることに注目してください。hearts 定数に値を設定する時、定数の型が明示的に指定されていないため、列挙ケースの Suits.hearts を完全な名前で参照しています。一方、switch 文の中では、self が既に Suit だとわかっているため、列挙ケースは .hearts と省略記法で参照しています。型が既にわかっている場合はいつでも省略記法を使用することができます。
もし列挙型が raw value を持つ場合、これらの値は宣言の一部として決定されます。これはつまり、特定の列挙ケースの全てのインスタンスは、必ず同じ raw value を持つということです。もう 1 つの選択肢は、列挙ケースに関連値(Associated value)を持たせることです。関連値はインスタンス生成時に決定され、列挙ケースのインスタンスごとに異なる値を持つことができます。関連値は列挙ケースのインスタンスの格納プロパティのように振る舞います。例えば、サーバから日の出時刻と日の入り時刻をリクエストする場合を考えてみてください。サーバはリクエスト情報に応じたレスポンスを返すか、リクエストが間違っている場合は間違っているという内容を返します。
Experiment
ServerResponseに3つ目のケースを追加して、switch 文を変更してください。
日の出時刻と日の入り時刻が、switch に合致したケースの一部として、ServerResponse から抽出されていることに注目してください。
構造体を作成するには struct を使います。構造体はメソッドやイニシャライザなど多くのクラスと同じ挙動をサポートしています。最も重要な違いは、構造体はコードの中で渡される時に必ずコピーされる、ということです。クラスは参照を渡します。
Experiment 全種(ランクとスートの全組み合わせ)のカードからなる配列を返すメソッドを書いてみよう。
並行処理(Concurrency)
async を使うことで非同期に実行される関数を定義できます。
非同期関数を呼び出す際には、その前に await を付けます。
非同期関数の呼び出しに async let を使うことで、他の非同期関数と並列に実行することができます。戻り値を使う箇所には await を書きます。
同期的なコードから非同期関数を呼び出す場合は Task を使います。Task は非同期関数の終了を待ちません。
並行コードを構造化するために、タスクグループ(task group)を使います。
アクターはクラスと似ていますが、異なる非同期関数が同時に同じアクターのインスタンスに安全にアクセスできる点が異なります。
アクターのメソッドを呼び出す、もしくはそのプロパティの 1 つにアクセスする時に、 そのアクター上で実行している他のコードが完了するのを待つことを示すために await をつける必要があります。
プロトコルと拡張(Protocols and Extensions)
プロトコルを宣言するために protocol を使います。
クラス、列挙型、構造体は全てプロトコルに準拠することができます。
Experiment
ExampleProtocolrankにもう1つの要件を追加してみましょう。SimpleClassとSimpleStructureにどういう変更が必要になるでしょうか?
構造体 SimpleStructure を変更するメソッドに mutating キーワードが付いていることに注目してください。クラスのメソッドはクラスを変更することが許されているため、mutating を付ける必要はありません。
既存の型に新しい機能(メソッドや計算プロパティ)を追加する場合に、extension を使います。他のファイルやモジュールで宣言された型や、ライブラリやフレームワークからインポートした型にプロトコルを準拠させる場合にも extension を使います。
Experiment
absoluteValueプロパティをDoubleに追加するextensionを書いてみましょう。
プロトコルの名前は、他の名前が付いた型と同じように使用することができます。例えば、同じ 1 つのプロトコルに準拠した異なる型のオブジェクトのコレクションを作成することができます。Box プロトコル型の値をそのまま扱っている場合、プロトコルの外側で定義されたメソッドを使用することはできません。
変数 protocolValue が実行時に SimpleClass になることはわかるものの、コンパイラはこれを、ExampleProtocol として扱います。つまり、プロトコルで定義されたメソッドやプロパティ以外へアクセスすることはできません。
エラーハンドリング(Error Handling)
Error プロトコルに準拠することで、任意の型でエラーを表現することができます。
関数に throws を付け、エラーをスローする箇所に throw を使用することでエラーをスローすることができます。関数内でエラーをスローすると、関数がすぐにリターンして、その関数を呼び出したコードでエラーを処理します。
エラーを処理する方法は様々あります。1 つは do-catch を使用することです。do ブロックの中では、エラーをスローする可能性がある箇所の前に try を付けます。catch ブロックの中では、明示的に異なる名前を設定しない限り、error という名前で自動的にエラー情報が与えられます。
Experiment プリンタの名前を
"Never Has Toner"に変えてみましょう。send(job:toPrinter:)はエラーをスローします。
特定のエラーを処理するために、複数の catch ブロックを書くこともできます。switch 文の case のように catch の後ろにパターンを書きます。
Experiment
doブロックの中でエラーをスローするコードを追加してみましょう。最初のcatchブロックでエラーを処理するためにはどの種類のエラーをスローする必要がありますでしょうか? 2つ目、3つ目の場合はどうでしょうか?
エラーを処理するもう 1 つの方法は、try? を付けて結果をオプショナルに変換することです。もしその関数がエラーをスローする場合、特定のエラーは破棄されて、結果が nil になります。そうでなければ、結果は、関数が返す値を内包したオプショナル値になります。
関数内の全ての処理の実行後、関数が結果を返す直前にコードを実行したい場合、defer を使います。このブロックは関数がエラーをスローしても実行されます。違うタイミングで実行される必要はありますが、defer を使用してセットアップ用のコードの次にクリーンアップ用のコードを書くことができます。
ジェネリクス(Generics)
ジェネリックな関数や型を作成するには、山括弧(<>)の中に名前を書きます。
クラス、列挙型、構造体と同じように、ジェネリックな関数やメソッドも作成することができます。
要件の一覧を指定するために、本文の直前に where を使います。例えば、プロトコルを実装するために型が必要な場合や、2 つの型が一致している必要がある場合、クラスが特定のスーパークラスを継承している必要がある場合などに使います。
Experiment
anyCommonElements(_:_:)を2つのシーケンスの共通の要素が含まれる配列を返すように修正してみましょう。
<T: Equatable> は <T> ... where T: Equatable と同じです。
最終更新