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 = 70
Experiment 値が
4
でFloat
を明示的に指定した定数を作成してみましょう。
値は暗黙的に他の型に変換されることはありません。他の型に変換したい場合は、変換したい型のインスタンスを明示的に作成してください。
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
Experiment 最後の行から
String
への変換を消してみてください。どんなエラーが起きるでしょうか?
もっと簡単な方法で文字列の中に値を含めることができます。値を括弧で囲み、括弧の前にバックスラッシュをつけます。
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
Experiment 文字列に浮動小数点の計算を含めたり、誰かの名前を挨拶に含めるために、
\()
を使用してみましょう。
複数行の文字列に対しては、3 つのダブルクォーテーション("""
)を使いましょう。末尾のクォーテーションに到達するまで、各行の最初のインデントは除外されます。
let quotation = """
Even though there's whitespace to the left,
the actual lines aren't indented.
Except for this line.
Double quotes (") can appear without being escaped.
I still have \(apples + oranges) pieces of fruit.
"""
配列や辞書の作成には角括弧([]
)を使い、それらの要素へアクセスする際は角括弧内にインデックスやキーを書きます。最後の要素の後ろにもカンマ(,
)を付けることができます。
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
配列は要素を追加すると自動でサイズが大きくなります。
shoppingList.append("blue paint")
print(shoppingList)
また、括弧を使用して、空の配列または辞書を記述します。配列の場合は []
、辞書の場合は [:]
と書きます。
fruits = []
occupations = [:]
空の配列または辞書を新しい変数、または型情報がない別の場所に割り当てる場合は、型を指定する必要があります。
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
制御フロー(Control Flow)
条件分岐を作成するには、 if
と switch
を使います。ループを作成するには for-in
while
repeat-while
を使います。条件やループ変数やオプショナルに丸括弧(()
)を付けるかどうかは任意です。本文の周りの中括弧({}
)は必須です。
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// 11
if
文の中で、条件はブール式でなければなりません。つまり、if score { ... }
などのコードはエラーで、暗黙的な 0 との比較は行われません。
条件に基づいた値を選ぶために、代入時の等号(=
)の後ろや return
の後に、if
または switch
を書くことができます。
let scoreDecoration = if teamScore > 10 {
"🎉"
} else {
""
}
print("Score:", teamScore, scoreDecoration)
// "Score: 11 🎉"を出力
if
と let
を一緒に使用して、存在しないかもしれない値を扱うことができます。これらの値はオプショナルで表現されます。オプショナルは値が含まれているか、値が存在しないことを示す nil
を含んでいます。値をオプショナルにするには、値の型の後ろにクエスチョンマーク(?
)を書きましょう。
var optionalString: String? = "Hello"
print(optionalString == nil)
// false
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
Experiment
optionalName
をnil
に変えてみましょう。どんな挨拶が出力されるでしょうか?optionalName
がnil
の場合に別の挨拶が出力されるようにelse
を追加してみましょう。
もしオプショナル値が nil
の場合、条件は false
になり、中括弧内のコードはスキップされます。nil
でなければ、オプショナル値はアンラップされて let
の後の定数に代入され、中括弧内のブロック内でその値を利用できます。
オプショナル値を扱うもう 1 つの方法として、??
演算子を使用してデフォルトの値を提供する方法があります。もしオプショナル値が nil
の場合、代わりにデフォルトの値が使われます。
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
アンラップ後の変数に同名を用いる場合、右辺を省略することができます。
if let nickname {
print("Hey, \(nickname)")
}
switch
は、あらゆる種類のデータと比較のための演算子を扱うことができます。(整数や等価チェックだけに限定されません)
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
// Is it a spicy red pepper?
Experiment
default
のケースを削除してみましょう。どんなエラーが起きるでしょうか?
パターンに合った値を定数に設定するために let
がどう使われるかに注目してください。
パターンに合った switch case 内のコードが実行された後、プログラムは switch 文から抜け出します。次の case は実行されないので、各 case の最後に明示的に break を書く必要はありません。
for-in
を使用することで辞書のそれぞれの要素のキーバリューペアを受け取って、辞書内のアイテムに反復処理をすることができます。辞書は順序のないコレクションなので、受け取るキーバリューペアの順序は決まっていません。
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
// 25
Experiment
_
を変数の名前に置き換えてみて、どの種類の値が最大だったかを追ってみましょう。
while
を使用することで、条件が変わるまでブロック内のコードを反復して実行できます。ループの条件を最後に置くことで、ループ内のブロックが少なくとも 1 回実行されるようにすることができます。
var n = 2
while n < 100 {
n *= 2
}
print(n)
// 128
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
// 128
Experiment 条件を
m < 100
からm < 0
に変更し、ループ条件がすでに真である場合のwhile
とrepeat-while
の動作の違いを確認しましょう。
..<
を使用するとインデックスの範囲を生成でき、ループのインデックスを追うことができます。
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// 6
..<
では後ろの値は除外され、...
は両方の値を含みます。
関数とクロージャ(Functions and Closures)
関数の定義には func
を使います。括弧(()
)の中に引数のリスト、その前に関数の名前を付けることで関数を呼び出します。また、->
の後ろに戻り値の型を指定して、パラメータ名や型と区別します。
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
Experiment
day
パラメータを削除して、greet
に今日のランチスペシャルを指定するためのパラメータを追加してみましょう。
デフォルトで、関数はパラメータ名をそのままラベルとして使用します。独自の引数ラベルを設定したい場合は、パラメータ名の前に引数ラベルを記載してください。引数にラベルが不要な場合は、_
を書きましょう。
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// 120
print(statistics.2)
// 120
関数はネストすることができます。ネストした関数は、外側の関数で定義された変数にアクセスすることができます。ネストした関数を使用することで、長くて複雑な関数を整理することができます。
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
関数は第一級オブジェクトです。つまり、関数は値として他の関数を戻り値にすることができます。
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
引数に他の関数を受け取ることもできます。
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
実の所、関数はクロージャの特殊なケースです。クロージャは、後で実行される可能性があるコードブロックのことを指します。クロージャ内のコードは、そのクロージャが作成されたスコープで利用可能な変数や関数へアクセスすることができます。これは、実際に実行されるのが別のスコープ(タイミング)の場合でも当てはまります。上記のネストした関数の例でも同じことが見られます。中括弧({}
)で囲むことで、名前なしのクロージャを作成することもできます。in
を使用することで、コードの本文から引数と戻り値を分離することができます。
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
Experiment 上記のクロージャを、全ての奇数で 0 を返すように書き換えてみましょう。
より簡潔にクロージャを書く複数の方法があります。クロージャの型が既にわかっている場合(デリゲートのコールバックなど)、パラメータと戻り値の型を省略できます。単一の文のみのクロージャの場合、その文の値が戻り値として暗黙的に返されます。
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// [60, 57, 21, 36]
名前ではなく番号でパラメータを参照できます。このアプローチは、非常に短いクロージャで特に役立ちます。関数の最後の引数として渡されたクロージャは、丸括弧の直後に記述できます。 クロージャが関数の唯一の引数の場合は、丸括弧を完全に省略できます。
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// [20, 19, 12, 7]
オブジェクトとクラス(Objects and Classes)
class
をクラス名の前に付けることでクラスを作成することができます。クラス内のプロパティの宣言は、クラス内にあるということを除いて、定数や変数の宣言方法と同じです。同様に、メソッドや関数の宣言も同じように書くことができます。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Experiment
let
を使用して定数プロパティを追加してください。また、引数を受け取る別のメソッドも追加してみましょう。
クラスのインスタンスは、クラス名の後に丸括弧(()
)を付けて生成します。そのインスタンスのプロパティやメソッドにアクセスするには、ドット(.
)構文を使います。
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
このバージョンの Shape
クラスは重要なことが抜けています。それは、インスタンスを生成するときにクラスを構築するためのイニシャライザです。init
を使ってイニシャライザを作成します。
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
イニシャライザ内で self
を使用して name
プロパティと name
引数を区別していることに注目してください。インスタンスの作成時、イニシャライザの引数は関数の呼び出しと同じように渡されます。全てのプロパティは宣言時に値を設定するか(numberOfSides
参照)、イニシャライザ内で値を設定する必要があります。(name
参照)
インスタンスが開放される前に何かクリーンアップ作業が必要な場合、deinit
を使ってデイニシャライザを作成します。
サブクラスは、そのクラス名の後に、スーパークラスの名前をコロン(:
)で区切って指定します。標準で全てのクラスのルートクラスが定義されていますが、そのサブクラスにする必要はありませんので、必要に応じて指定も省略もできます。
スーパークラスのメソッドをサブクラスでオーバーライドする場合は override
を付けます。override
を付けずにオーバーライドしようとしている場合は、コンパイラがエラーにします。override
を付けたメソッドが実際にはスーパークラスのメソッドをオーバーライドしていない場合も、コンパイラはエラーにします。
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
Experiment
radius
とname
をイニシャライザの引数に受けとるCircle
という名前のNameShape
の別のサブクラスを作ってみましょう。そして、Circle
クラスにarea()
、simpleDescription()
メソッドを実装してみましょう。
単純に値を保持するプロパティ以外に、プロパティが get と set を持つこともできます。
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// 9.3
triangle.perimeter = 9.9
print(triangle.sideLength)
// 3.3000000000000003
perimeter
の set の中で、新しい値は暗黙的に newValue
という名前になります。set
の後に丸括弧(()
)で囲んで明示的に指定することもできます。
EquilateralTriangle
クラスのイニシャライザは 3 つの異なるステップがあります。
- サブクラスで宣言されたプロパティに値を設定します
- スーパークラスのイニシャライザを呼びます
- スーパークラスで定義されたプロパティの値を変更。この時点で get や set やメソッドを使用して他のセットアップ処理を実行できます
プロパティを計算する必要はないけれども、新しい値を設定する前後で何かコードを実行したい場合、willSet
、didSet
を使います。このコードは、イニシャライザ以外で値が変更された時に毎回実行されます。例えば、下のクラスは三角形の辺の長さが常に四角形の辺の長さと同じになります。
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// 10.0
print(triangleAndSquare.triangle.sideLength)
// 10.0
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// 50.0
オプショナル値を扱う場合、?
をメソッド、プロパティ、サブスクリプトのような操作の前に書きます。もし ?
の前の値が nil
の場合、?
の後の処理は全て無視され、その式全体の値は nil
になります。それ以外、オプショナル値はアンラップされて、?
の後は全てアンラップされた値として実行されます。どちらの場合も、式全体はオプショナル値です。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
列挙型と構造体(Enumerations and Structures)
列挙型を作成するには enum
を使います。クラスやその他の名前の付いた型と同様に、列挙型もメソッドを持つことができます。
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
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
を返します。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
列挙型のケースの値は実在の値で、raw value を書くための別の方法ではありません。事実、意味のある raw value が存在しない場合は、raw value を提供する必要はありません。
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
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)を持たせることです。関連値はインスタンス生成時に決定され、列挙ケースのインスタンスごとに異なる値を持つことができます。関連値は列挙ケースのインスタンスの格納プロパティのように振る舞います。例えば、サーバから日の出時刻と日の入り時刻をリクエストする場合を考えてみてください。サーバはリクエスト情報に応じたレスポンスを返すか、リクエストが間違っている場合は間違っているという内容を返します。
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
// Sunrise is at 6:00 am and sunset is at 8:09 pm.
Experiment
ServerResponse
に3つ目のケースを追加して、switch 文を変更してください。
日の出時刻と日の入り時刻が、switch に合致したケースの一部として、ServerResponse
から抽出されていることに注目してください。
構造体を作成するには struct
を使います。構造体はメソッドやイニシャライザなど多くのクラスと同じ挙動をサポートしています。最も重要な違いは、構造体はコードの中で渡される時に必ずコピーされる、ということです。クラスは参照を渡します。
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
Experiment 全種(ランクとスートの全組み合わせ)のカードからなる配列を返すメソッドを書いてみよう。
並行処理(Concurrency)
async
を使うことで非同期に実行される関数を定義できます。
func fetchUserID(from server: String) async -> Int {
if server == "primary" {
return 97
}
return 501
}
非同期関数を呼び出す際には、その前に await
を付けます。
func fetchUsername(from server: String) async -> String {
let userID = await fetchUserID(from: server)
if userID == 501 {
return "John Appleseed"
}
return "Guest"
}
非同期関数の呼び出しに async let
を使うことで、他の非同期関数と並列に実行することができます。戻り値を使う箇所には await
を書きます。
func connectUser(to server: String) async {
async let userID = fetchUserID(from: server)
async let username = fetchUsername(from: server)
let greeting = await "Hello \(username), user ID \(userID)"
print(greeting)
}
同期的なコードから非同期関数を呼び出す場合は Task
を使います。Task
は非同期関数の終了を待ちません。
Task {
await connectUser(to: "primary")
}
// Hello Guest, user ID 97
並行コードを構造化するために、タスクグループ(task group)を使います。
let userIDs = await withTaskGroup(of: Int.self) { group in
for server in ["primary", "secondary", "development"] {
group.addTask {
return await fetchUserID(from: server)
}
}
var results: [Int] = []
for await result in group {
results.append(result)
}
return results
}
アクターはクラスと似ていますが、異なる非同期関数が同時に同じアクターのインスタンスに安全にアクセスできる点が異なります。
actor ServerConnection {
var server: String = "primary"
private var activeUsers: [Int] = []
func connect() async -> Int {
let userID = await fetchUserID(from: server)
// ... communicate with server ...
activeUsers.append(userID)
return userID
}
}
アクターのメソッドを呼び出す、もしくはそのプロパティの 1 つにアクセスする時に、
そのアクター上で実行している他のコードが完了するのを待つことを示すために await
をつける必要があります。
let server = ServerConnection()
let userID = await server.connect()
プロトコルと拡張(Protocols and Extensions)
プロトコルを宣言するために protocol
を使います。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
クラス、列挙型、構造体は全てプロトコルに準拠することができます。
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
Experiment
ExampleProtocolrank
にもう1つの要件を追加してみましょう。SimpleClass
とSimpleStructure
にどういう変更が必要になるでしょうか?
構造体 SimpleStructure
を変更するメソッドに mutating
キーワードが付いていることに注目してください。クラスのメソッドはクラスを変更することが許されているため、mutating
を付ける必要はありません。
既存の型に新しい機能(メソッドや計算プロパティ)を追加する場合に、extension
を使います。他のファイルやモジュールで宣言された型や、ライブラリやフレームワークからインポートした型にプロトコルを準拠させる場合にも extension
を使います。
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// The number 7
Experiment
absoluteValue
プロパティをDouble
に追加するextension
を書いてみましょう。
プロトコルの名前は、他の名前が付いた型と同じように使用することができます。例えば、同じ 1 つのプロトコルに準拠した異なる型のオブジェクトのコレクションを作成することができます。Box プロトコル型の値をそのまま扱っている場合、プロトコルの外側で定義されたメソッドを使用することはできません。
let protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// A very simple class. Now 100% adjusted. が出力されます。
// print(protocolValue.anotherProperty) // エラーを確認したい場合はコメントアウトを外しましょう
変数 protocolValue
が実行時に SimpleClass
になることはわかるものの、コンパイラはこれを、ExampleProtocol
として扱います。つまり、プロトコルで定義されたメソッドやプロパティ以外へアクセスすることはできません。
エラーハンドリング(Error Handling)
Error
プロトコルに準拠することで、任意の型でエラーを表現することができます。
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
関数に throws
を付け、エラーをスローする箇所に throw
を使用することでエラーをスローすることができます。関数内でエラーをスローすると、関数がすぐにリターンして、その関数を呼び出したコードでエラーを処理します。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
エラーを処理する方法は様々あります。1 つは do-catch
を使用することです。do
ブロックの中では、エラーをスローする可能性がある箇所の前に try
を付けます。catch
ブロックの中では、明示的に異なる名前を設定しない限り、error
という名前で自動的にエラー情報が与えられます。
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// Job sent
Experiment プリンタの名前を
"Never Has Toner"
に変えてみましょう。send(job:toPrinter:)
はエラーをスローします。
特定のエラーを処理するために、複数の catch
ブロックを書くこともできます。switch 文の case
のように catch
の後ろにパターンを書きます。
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// Job sent
Experiment
do
ブロックの中でエラーをスローするコードを追加してみましょう。最初のcatch
ブロックでエラーを処理するためにはどの種類のエラーをスローする必要がありますでしょうか? 2つ目、3つ目の場合はどうでしょうか?
エラーを処理するもう 1 つの方法は、try?
を付けて結果をオプショナルに変換することです。もしその関数がエラーをスローする場合、特定のエラーは破棄されて、結果が nil
になります。そうでなければ、結果は、関数が返す値を内包したオプショナル値になります。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
関数内の全ての処理の実行後、関数が結果を返す直前にコードを実行したい場合、defer
を使います。このブロックは関数がエラーをスローしても実行されます。違うタイミングで実行される必要はありますが、defer
を使用してセットアップ用のコードの次にクリーンアップ用のコードを書くことができます。
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
if fridgeContains("banana") {
print("Found a banana")
}
print(fridgeIsOpen)
// false
ジェネリクス(Generics)
ジェネリックな関数や型を作成するには、山括弧(<>
)の中に名前を書きます。
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
クラス、列挙型、構造体と同じように、ジェネリックな関数やメソッドも作成することができます。
// Swift 標準ライブラリのオプショナル型の再実装
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
要件の一覧を指定するために、本文の直前に where
を使います。例えば、プロトコルを実装するために型が必要な場合や、2 つの型が一致している必要がある場合、クラスが特定のスーパークラスを継承している必要がある場合などに使います。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
Experiment
anyCommonElements(_:_:)
を2つのシーケンスの共通の要素が含まれる配列を返すように修正してみましょう。
<T: Equatable>
は <T> ... where T: Equatable
と同じです。