概要
- Swiftでコードを書き始めるのに十分な情報を提供
- 様々なプログラミングタスクをどのように達成するかを紹介
- 参考
HelloWorld
print("Hello, world!")
// Prints "Hello, world!"
- 入出力や文字列処理のような機能
- 別のライブラリをインポートする必要はない
- main関数は必要ない
- グローバルスコープで書かれたコードは、プログラムのエントリポイントとして使用
- すべての文の最後にセミコロンを書く必要はない
単純な値
- 定数
- let
- コンパイル時に知っている必要はない
- 正確に一度だけ値を代入する必要がある
- イミュータブル(immutable)
- 一度決めた値を様々な場所で使用可能
- 変数
- var
- ミュータブル(mutable)
var myVariable = 42
myVariable = 50
let myConstant = 42
- 定数や変数
- 代入した値と同じ型を持たなければならない
- 型を明示的に記述する必要はない
- 作成するときに値を与える
- コンパイラーがその型を推測(型推論)
- 変数の後にコロンで区切って型を明示的に記述
- 初期値で十分な情報が得られない場合
- 初期値がない場合
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
練習問題
- 明示的な型がFloatで、値が4の定数を作成
let value: Float = 4
- 値が暗黙のうちに別の型に変換されることはない
- 値を別の型に変換する必要がある場合
- 明示的に目的の型のインスタンスを作成
- 値を別の型に変換する必要がある場合
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
練習問題
- 最後の行からStringへの変換を削除してみてください。どんなエラーが出ますか?
let label = "The width is "
let width = 94
let widthLabel = label + width
error: Practice.playground:3:24: error: binary operator '+' cannot be applied to operands of type 'String' and 'Int'
let widthLabel = label + width
~~~~~ ^ ~~~~~
Practice.playground:3:24: note: overloads for '+' exist with these partially matching parameter lists: (Int, Int), (String, String)
let widthLabel = label + width
- 文字列に値を含める方法
- 値を括弧で囲み、括弧の前にバックスラッシュ(\)を書く
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
練習問題
- 文字列の中に浮動小数点計算を入れたり、挨拶文の中に誰かの名前を入れたりする
let weight = 98.5
let name = "Snorlax"
let result = "\(name)の体重は\(weight)です。"
- 複数行に渡る文字列には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.
"""
print(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 8 pieces of fruit.
- 配列や辞書
- 括弧([])を使用して作成
- 括弧の中にインデックスやキーを記述して要素にアクセス
- 最後の要素の後にカンマを入れてもよい
var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"
// fruits -> ["strawberries", "grapes", "tangerines"]
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
// occupations -> ["Malcolm": "Captain", "Kaylee": "Mechanic", "Jayne": "Public Relations"]
- 配列は、要素を追加すると自動的に大きくなる
fruits.append("blueberries")
print(fruits)
// ["strawberries", "grapes", "tangerines", "blueberries"]
- 空の配列や辞書を書くときにも括弧を使用
- 配列の場合は[]
- 辞書の場合は[:]
fruits = []
occupations = [:]
- 新しい変数に空の配列や辞書を代入する場合
- 型情報がない場所には代入できない
- 型を指定する必要がある
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
制御フロー
- 条件分岐
- 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
- 暗黙のゼロ比較はエラーとなる
- NG例
- if score {…}
- OK例
- if score == 0 {…}
- NG例
- 条件に基づいて値を選択可能
- ifやswitchを代入の等号(=)の後やreturnの後に書く
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)"
}
練習問題
- optionalNameをnilに変更
- else句を追加して、optionalNameがnilの場合に別の挨拶を設定する
var optionalName: String? = nil
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}else{
greeting = "hoge"
}
print(greeting)
// hoge
- オプション値がnilの場合、条件式は偽となり、中括弧内のコードはスキップ
- そうでない場合は、オプションの値はラップされず、letの後の定数に代入
- オプション値を扱うもうひとつの方法
- ?オプション値がない場合
- デフォルト値が代わりに使用される
- ?オプション値がない場合
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
print(informalGreeting)
// Hi John Appleseed
if let nickname {
print("Hey, \(nickname)")
}
// ニックネームが0なので何も表示されない
- 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.")
}
// Prints "Is it a spicy red pepper?"
練習問題
- デフォルトのケースを削除した場合は、どんなエラー?
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)?")
}
error: Practice.playground:2:1: error: switch must be exhaustive
switch vegetable {
^
Practice.playground:2:1: note: do you want to add a default clause?
switch vegetable {
^
- letがパターン内でどのように使われるかに注目
- パターンにマッチした値を定数に代入するために
- マッチしたswitchケース内のコードを実行した後
- プログラムはswitchステートメントから抜ける
- 実行は次のケースに続かない
- 各ケースのコードの最後で明示的にswitchから抜け出す必要はない
- 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
練習問題
- _ を変数名に置き換えて、どの種類の数字が一番大きかったかを記録
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
var result = ""
for (key, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
result = key
}
}
}
print("\(result):\(largest)")
// Square:25
- 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
練習問題
- 条件をm < 100からm < 0に変更
- ループ条件がすでに真である場合にwhileとrepeat-whileがどのように異なる動作をするかを確認
var n = 2
while n < 0 {
n *= 2
}
print(n)
// 2
var m = 2
repeat {
m *= 2
} while m < 0
print(m)
// 4
- インデックスの範囲
- ..< を使用
- ループの中でインデックスを保持
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// 6
- 両方の値を含む範囲を作るには ... を使う
var total = 0
for i in 0...4 {
total += i
}
print(total)
// 10
関数とクロージャ
- 関数の宣言
- funcを使用
- 関数の呼び出し
- 関数の名前の後に引数のリストを括弧で囲む
- 関数の戻り値の型から引数の名前と型を分離するには -> を使用
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
let result = greet(person: "Bob", day: "Tuesday")
print(result)
// Hello Bob, today is Tuesday.
練習問題
- dayパラメータを削除
- 挨拶文に本日のランチスペシャルを含めるパラメーターを追加
func greet(person: String, specialLunch: String) -> String {
return "Hello \(person), special lunch is \(specialLunch)."
}
let result = greet(person: "Bob", specialLunch: "meat")
print(result)
// Hello Bob, special lunch is meat.
- デフォルトでは、関数はパラメータ名を引数のラベルとして使用
- パラメータ名の前にカスタム引数ラベルを記述
- 引数ラベルを使用しない場合は_を記述
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
let result = greet("John", on: "Wednesday")
print(result)
// Hello John, today is Wednesday.
- 関数から複数の値を返す場合
- タプルの要素は、名前または数値で参照可能
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
}
var result = returnFifteen()
print(result)
// 15
- 関数はファーストクラス型
- 関数はその値として別の関数を返すことが可能
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
var result = increment(7)
print(result)
// 8
- 関数は他の関数を引数の1つとして受け取ることが可能
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]
var result = hasAnyMatches(list: numbers, condition: lessThanTen)
print(result)
// true
- 関数はクロージャの特殊なケース
- 後で呼び出すことができるコードのブロック
- クロージャの中のコード
- 実行時にクロージャが別のスコープでも、クロージャが作成されたスコープで使用可能だった変数や関数などにアクセス可能
- 中括弧({})でコードを囲む
- 名前なしでクロージャを書くことが可能
- 引数や戻り値の型と本体を分けるには in を使用
var numbers = [20, 19, 7, 12]
var result = numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
print(result)
// [60, 57, 21, 36]
練習問題
- すべての奇数に対してゼロを返すようにクロージャを書き直す。
var numbers = [20, 19, 7, 12]
var result = numbers.map({(number: Int) -> Int in
if number % 2 == 1{
return 0
}
return number
})
print(result)
// [20, 0, 0, 12]
- クロージャのオプション
- より簡潔に書くために
- クロージャの型がすでに分かっている場合
- デリゲートのコールバックなど
- 省略可能
- パラメータの型
- 戻り値の型
- 両方
- シングル・ステートメント・クロージャ
- 唯一のステートメントの値を暗黙的に返す
var numbers = [20, 19, 7, 12]
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// [60, 57, 21, 36]
- 非常に短いクロージャで特に便利
- 関数の最後の引数として渡されるクロージャ
- 括弧の直後に書くことが可能
- クロージャが関数の唯一の引数である場合
- 括弧を完全に省略可能
var numbers = [20, 19, 7, 12]
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// [20, 19, 12, 7]
オブジェクトとクラス
- クラスの作成
- classの後にクラス名を続ける
- クラス内のプロパティ宣言
- 定数や変数の宣言と同じように記述
- クラスのコンテキストにあることを除く
- メソッドや関数の宣言も同じように記述
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
var shape = Shape()
print(shape.simpleDescription())
// A shape with 0 sides.
練習問題
- letで定数プロパティを追加し、引数を取る別のメソッドを追加
class Shape {
var numberOfSides = 0
let name = "shape"
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
func greetingName(_ greeting: String) -> String {
return "\(greeting) \(name)"
}
}
var shape = Shape()
print(shape.greetingName("Hello"))
// Hello shape
- クラス名の後に括弧を付けてクラスのインスタンスを作成
- インスタンスのプロパティやメソッドにアクセスする方法
- ドット構文(.)を使用
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
print(shapeDescription)
// A shape with 7 sides.
- イニシャライザ
- インスタンス生成時にクラスをセットアップするもの
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
var nameShape = NamedShape(name: "hoge")
var result = nameShape.simpleDescription()
print(result)
// A shape with 0 sides.
- self
- nameプロパティとイニシャライザーのname引数を区別するために使用
- イニシャライザへの引数
- クラスのインスタンスを作成するときに関数呼び出しのように渡される
- プロパティは値を代入する必要がある
- 宣言
- numberOfSides
- イニシャライザ
- name
- 宣言
- オブジェクトが解放される前に何らかのクリーンアップを行う必要がある場合
- deinit を使用して初期化解除を実施
- サブクラス
- クラス名の後にコロンで区切ってスーパークラス名を記述
- クラスが標準的なルート・クラスをサブクラス化する必要はない
- 必要に応じてスーパークラスを含めることも省略することも可能
- オーバーライドのマーク
- スーパークラスの実装をオーバーライドするサブクラスのメソッドに付けられる
- オーバーライドせずに誤ってメソッドをオーバーライド
- コンパイラはエラーとして検出
- スーパークラスのメソッドをオーバーライドしていないオーバーライド付きメソッドも検出
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")
print(test.area())
// 27.040000000000003
print(test.simpleDescription())
// A square with sides of length 5.2.
練習問題
- CircleというNamedShapeの別のサブクラスを作成
- イニシャライザの引数として半径と名前を取る
- area() メソッド
- simpleDescription() メソッド
import Foundation
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
class Circle: NamedShape{
var radius: Double
init(radius: Double, name: String) {
self.radius = radius
super.init(name: name)
}
func area() -> Double{
return pow(self.radius, 2) * Double.pi
}
override func simpleDescription() -> String{
return "A circle with radius of length \(self.radius)."
}
}
var circle = Circle(radius:10, name:"my test cricle")
print(circle.area())
// 314.1592653589793
print(circle.simpleDescription())
// A circle with radius of length 10.0.
- 保存される単純なプロパティに加えて、プロパティはゲッターとセッターを持つことが可能
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のセッター
- 新しい値は暗黙的な名前を持っている
- newValue
- setの後の括弧で明示的な名前を指定可能
- 新しい値は暗黙的な名前を持っている
- EquilateralTriangleクラスのイニシャライザ
- 3つのステップ
- サブクラスが宣言するプロパティの値を設定
- スーパークラスのイニシャライザーを呼び出す
- スーパークラスが定義したプロパティの値を変更
追加のセットアップ作業もこの時点で可能- メソッド
- ゲッター
- セッター
- 3つのステップ
- willSetとdidSet
- プロパティを計算する必要はない
- 新しい値を設定する前と後に実行されるコードを提供する必要がある場合
- イニシャライザ以外で値が変更されるたびに実行
- 以下のクラスは三角形の辺の長さが常に正方形の辺の長さと同じであることを保証
// 前回までのコード
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
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)."
}
}
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)."
}
}
// ここから
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
print(triangleAndSquare.square.sideLength)
// 50.0
- オプショナルな値を扱う場合
- メソッドやプロパティ、添え字などの操作の前
- ?の前の値が nil の場合
- ? の後の値はすべて無視され、式全体の値は nil
- それ以外の場合
- オプションの値がアンラップされ、?どちらの場合も、式全体の値はオプショナル値
- ?の前の値が nil の場合
- メソッドやプロパティ、添え字などの操作の前
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength: Double? = optionalSquare?.sideLength
if let optionalSquare {
print(optionalSquare.sideLength)
// 2.5
}
列挙と構造体
- 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
print(ace)
// ace
let aceRawValue = ace.rawValue
print(aceRawValue)
// 1
print(ace.simpleDescription())
// ace
練習問題
- 2つのRank値を比較する関数
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)
}
}
}
func > (left: Rank, right: Rank) -> Bool{
return left.rawValue > right.rawValue
}
print(Rank.ace > Rank.jack)
// false
print(Rank.jack > Rank.ace)
// true
- デフォルト
- ゼロから始まる
- 毎回1ずつ増加する値を割り当てる
- 明示的に値を指定することも可能
- 指定可能な型
- 整数
- 文字列
- 浮動小数点数
- アクセス方法
- rawValueプロパティを使用
- init?(rawValue:)
- 値から列挙型のインスタンスを作成
- 値にマッチする列挙型のケースを返却
- マッチしない場合はnilを返却
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)
}
}
}
// ここから
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
print(threeDescription)
// 3
}
- 列挙型のケース値は実際の値
- 値を別の書き方で表したものではない
- 意味のある値でない場合、その値を提供する必要はないため
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
print(hearts)
// hearts
let heartsDescription = hearts.simpleDescription()
print(heartsDescription)
// hearts
練習問題
- Suitにcolor()メソッドを追加
- スペードとクラブには "black" を返却
- ハートとダイアモンドには "red" を返却
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"
}
}
func color() -> String {
switch self {
case .spades, .clubs:
return "black"
case .hearts, .diamonds:
return "red"
}
}
}
print(Suit.spades.color())
// black
print(Suit.clubs.color())
// black
print(Suit.hearts.color())
// red
print(Suit.diamonds.color())
// red
- 列挙型の値にたいする2つの参照方法
- 定数heartsに値を代入するときの参照方法
- Suit.hearts
- 定数に明示的な方が指定さていないので、フルネームで参照
- switchの内部
- .hearts
- selfの値が既にSuitであることがわかっているので、省略形で参照
- 省略形は、値の型が既知であればいつでも使用可能
- 定数heartsに値を代入するときの参照方法
- 列挙型に値をもつ場合
- それらの値は宣言の一部として決定
- 特定の列挙型のケースのすべてのインスタンスは、常に同じ値をもつ
- もう一つの選択肢
- ケースに関連する値をもつこと
- これらの値はインスタンスを作成するときに決定
- 列挙型のケースのインスタンスごとに異なる可能性がある
- 列挙型のケース・インスタンスの保存されたプロパティのように動作
- 日の出と日の入りの時刻をサーバーに要求する場合
- サーバーは要求された情報で応答
- 何が間違っていたかの説明で応答
- 日の出と日の入りの時刻をサーバーに要求する場合
- ケースに関連する値をもつこと
- それらの値は宣言の一部として決定
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).")
// Sunrise is at 6:00 am and sunset is at 8:09 pm.
case let .failure(message):
print("Failure... \(message)")
}
- 構造体
- structを使用
- クラスと同じ動作の多くをサポート
- メソッド
- イニシャライザー
- クラスとの重要な違い
- コード内で渡されるときの挙動
- 構造体
- 常にコピー
- クラス
- 参照渡し
- 構造体
- コード内で渡されるときの挙動
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)
}
}
}
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"
}
}
}
// ここから
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()
print(threeOfSpadesDescription)
// The 3 of spades
練習問題
- ランクとスートの各組み合わせを1枚ずつ含む、山札をすべて含む配列を返す関数
enum Rank: Int, CaseIterable {
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)
}
}
}
enum Suit: CaseIterable {
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"
}
}
}
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
func deck() -> [Card]{
var cards: [Card] = []
for rank in Rank.allCases{
for suit in Suit.allCases{
cards.append(Card(rank: rank, suit: suit))
}
}
return cards
}
var cardDeck = deck()
for card in cardDeck{
print(card.rank, card.suit)
}
並行性
// 非同期に実行される関数をマークするには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 {
await connectUser(to: "primary")
}
// 以下はエラーが発生しているので一旦コメントアウト
// わかり次第コードを修正します
// タスク・グループを使って並行コードを構造化
//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
// }
//}
// アクターのメソッドを呼び出したり、そのプロパティにアクセスしたりするとき
// ・コードにawaitをつける
// ・アクター上ですでに実行されている他のコードが終了するまで待つ必要があることを示す
//let server = ServerConnection()
//let userID = await server.connect()
プロトコルと拡張機能
// プロトコルの宣言には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()
print(a.simpleDescription)
// A very simple class. Now 100% adjusted.
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
print(b.simpleDescription)
// A simple structure (adjusted)
練習問題
- ExampleProtocolに別の要件を追加
- SimpleClassとSimpleStructureがプロトコルに適合するように修正
protocol ExampleProtocol {
var simpleDescription: String { get }
var value: String { get }
mutating func adjust()
}
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
var value: String = "SimpleClass"
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
print(a.value)
// SimpleClass
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
var value: String = "SimpleStructure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
print(b.value)
// SimpleStructure
- 構造を変更するメソッドをマーク
- SimpleStructure の宣言で mutating キーワードが使われている
- クラスのメソッドは常にクラスを変更することが可能
- SimpleClass の宣言は mutating としてマークされたメソッドを必要としない
- 既存の型に機能を追加するには拡張機能を使用
- 新しいメソッド
- 計算プロパティ
- 拡張機能を使用してプロトコル整合性を追加可能
- 他の場所で宣言されている型
- インポートした型
- ライブラリ
- フレームワーク
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// The number 7
練習問題
- AbsoluteValue プロパティを追加する Double タイプの拡張を記述
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
// AbsoluteValue プロパティを追加する Double タイプの拡張を記述
protocol AbsoluteProtocol {
var absoluteValue: String { get }
}
extension Double: AbsoluteProtocol {
var absoluteValue: String {
return "絶対値:\(abs(self))"
}
}
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print((-4.25).absoluteValue)
// 絶対値:4.25
- プロトコル名は、他の名前付き型と同じように使用可能
- 型は異なるけれども、すべてが単一のプロトコルに準拠するオブジェクトのコレクションを作成する場合など
- 型がボックス化されたプロトコル型の値を扱う場合
- プロトコル定義外のメソッドは使用できない
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 protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// A very simple class. Now 100% adjusted.
// print(protocolValue.anotherProperty)
// エラーとなります。
// 変数 protocolValue の実行時型が SimpleClass であっても、コンパイラはそれを ExampleProtocol の指定された型として扱う
// これは、プロトコルの適合性に加えてクラスが実装しているメソッドやプロパティに誤ってアクセスできないことを意味
エラー処理
// Errorプロトコルを採用する任意の型を使ってエラーを表現
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
// エラーをスローできる関数をマークするにはthrowsを使用
// 関数内でエラーをスローした場合、関数はすぐに戻り、関数を呼び出したコードがエラーを処理
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
// エラーを処理する方法
// do-catchを使う
// ・doブロックの中では、tryを前に書くことで、エラーを投げる可能性のあるコードをマーク
// ・catchブロックの中では、別の名前をつけない限り、エラーには自動的にerrorという名前がつけられる
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
// Job sent
} catch {
print(error)
}
練習問題
- プリンタ名を "Never Has Toner "に変更
- send(job:toPrinter:)関数がエラーを投げるよう変更
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
do {
let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
print(printerResponse)
} catch {
print(error)
// noToner
}
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
// 特定のエラーを処理するcatchブロックを複数用意することが可能
// switchのcaseの後に書くのと同じように、catchの後にパターンを書く
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
// Job sent
} 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)
}
- エラーを処理するもう一つの方法
- try?関数
- エラーをスローした場合
- エラーは破壊され結果はnilとなる
- エラーでない場合
- 結果は関数が返した値を含むオプショナル
- エラーをスローした場合
- try?関数
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
// ここから
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
if let printerSuccess {
print(printerSuccess)
// Job sent
}
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
if let printerFailure {
print(printerFailure)
}
- defer
- 関数内の他のすべてのコードの後で、関数が戻る直前に実行されるコード・ブロックを書くことが可能
- このコードは、関数がエラーを投げるかどうかに関係なく実行
- セットアップとクリーンアップのコードを隣り合わせに書くことが可能
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
print(fridgeIsOpen)
// true
let result = fridgeContent.contains(food)
return result
}
if fridgeContains("banana") {
print("Found a banana")
}
print(fridgeIsOpen)
// false
総称型
- 山括弧(<>)の中に名前を書くと、ジェネリクスな関数や型となる
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result: [Item] = []
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
print(makeArray(repeating: "knock", numberOfTimes: 4))
// ["knock", "knock", "knock", "knock"]
print(makeArray(repeating: 10, numberOfTimes: 4))
// [10, 10, 10, 10]
- クラス、列挙、構造体だけでなく、関数やメソッドのジェネリック形式も作成可能
// Swift標準ライブラリのオプション型
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
print(possibleInteger)
// none
possibleInteger = .some(100)
print(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
}
print(anyCommonElements([1, 2, 3], [3]))
// true
print(anyCommonElements([1, 2, 3], [4]))
// false
練習問題
- anyCommonElements(::)関数を修正
- 任意の2つのシーケンスが共通に持つ要素の配列を返す関数を作成
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> [T.Element]
where T.Element: Equatable, T.Element == U.Element
{
var result: [T.Element] = []
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
result.append(lhsItem)
}
}
}
return result
}
print(anyCommonElements([1, 2, 3], [3, 1]))
// [1, 3]
print(anyCommonElements([1, 2, 3], [4]))
// []