
プログラミング言語Swift学習ノート(随時更新中)
macOS/iOSのプログラミング言語Swiftでよく使われる構文を素早く覚えるため、クイックリファレンス的なものをまとめようと書き始めましたが、Swiftならではの記法が多くまとまらなかったので、学習したことを記録するノートにしました。学んだことが増えるたびに記事が増える予定です。
サンプルコードは、XcodeのPlayground、またはターミナルでswift replコマンドを実行して、対話形式で試すことができます。async / awaitを使った並行処理など、Playgroundで正常に動作しない場合は、ファイルに保存してswift <ソースコード名>で実行します。
本記事を作成したときのSwiftのバージョン:
~$ swift --versionswift-driver version: 1.127.14.1 Apple Swift version 6.2.3 (swiftlang-6.2.3.3.21 clang-1700.6.3.2)Target: arm64-apple-macosx15.0Swiftに関する情報:
- Swift | Apple Developer Documentation (英語のドキュメント)
- The Swift Programming Language(日本語版) (日本語のドキュメント)
SwiftUIで難儀した話:

1. 基本的な構文
Section titled “1. 基本的な構文”文の末尾に;(セミコロン)は不要です。1行に複数のステートメント(文)を書くときは;で結びます。ファイルの拡張子はswiftです。
print("Hello, world!")print(2 + 3); print(10 - 4)ソースコードをファイルに保存した場合、ターミナルでswiftコマンドを使って実行できます。
swift hello.swiftHello, world!と5と6が表示されます。
コメントは、1行コメントと複数行コメントがあります。
// 1行コメント
/* 複数行コメント①の始まり /* 複数行コメント② コメントをネストできるのは便利ですね。 */ 複数行コメント①の終わり */2. 変数・定数
Section titled “2. 変数・定数”Swiftは型推論が強力な静的型付け言語です。
宣言と可変性
Section titled “宣言と可変性”var: 変数(変更可能)。let: 定数(変更不可)。コードに格納された値が変化しない場合は、letキーワードで定数として宣言する。
var myVariable = 42 // 42 → Int型と推論されるmyVariable = 50 // 変数は変更可能
var x = 0.0, y = 0.0, z = 0.0 // 1行の中に複数の変数(var)・定数(let)を定義できる
let myConstant = 42myConstant = 50 // エラー: 定数は変更不可
let env = "dev"let maxNum: Int // 後から定数の初期値を代入できる(構造体などの格納プロパティで頻出)if env == "dev" { maxNum = 100} else { maxNum = 10}
// 型注釈(Type Annotation)の書き方let explicitDouble: Double = 70.0 // `70.`はエラー(`.`はドット構文と見なされる)識別子の規則
Section titled “識別子の規則”変数・定数の名前の規則は以下のとおり。変数・定数の名前に限らず、クラス・構造体・列挙型の名前、タイプエイリアスの名前、whileループなど制御フローのラベルなどにも以下の規則が適用される。
- Unicode文字も含めた、ほとんどの文字を使用可能。以下は不可な文字。
- ホワイトスペース、数学記号、矢印、公式ではない Unicode スカラ値、罫線素片、書式文字
- 数字から始めることは出来ない。
- Swiftの予約語を識別子として使用することもできる。予約語の前後にバッククォートを置く。
let π = 3.14159let こんにちは = "こんにちは、世界"let 😊 = "笑顔"let `let` = "定数宣言" // 予約語を定数名として使うlet 123abc = 123 // エラー3. Swiftのデータ型
Section titled “3. Swiftのデータ型”Swiftには値型と参照型があります。クラス、関数(メソッドやクロージャも含む)、アクター以外は値型です。
flowchart LR
dataType[データ型]
valueType((値型))
referenceType((参照型))
struct{{構造体}}
enum{{列挙型}}
tuple{{タプル}}
class_{{クラス}}
function{{関数}}
actor{{アクター}}
integer([整数 Int])
floatingPoint([浮動小数点数 Double/Float])
boolean([ブール値 Bool])
string([文字列 String])
array([配列 Array])
set([セット Set])
dictionary([辞書 Dictionary])
optional([オプショナル Optional])
dataType --> valueType
dataType --> referenceType
subgraph ValueTypes [値がコピーされる]
direction TB
valueType --> struct
valueType --> enum
valueType --> tuple
end
subgraph ReferenceTypes [参照が渡される]
direction TB
referenceType --> class_
referenceType --> function
referenceType --> actor
end
struct --> integer
struct --> floatingPoint
struct --> boolean
struct --> string
struct --> array
struct --> set
struct --> dictionary
enum --> optional
基本的なデータ型とリテラル
Section titled “基本的なデータ型とリテラル”基本的なデータ型
Section titled “基本的なデータ型”| データ型の種類 | データ型 |
|---|---|
| 整数(符号付き) | Int, Int32, Int8, Int16, Int64, Int128 |
| 整数(符号なし) | UInt, UInt32, UInt8, UInt16, UInt64, UInt128 |
| 浮動小数点数 | Double(Float64でも可), Float(Float32でも可), Float16 |
| ブール値 | Bool |
| 文字列 | String (1文字のCharacter型もある) |
IntとUIntはプラットフォーム依存です。
| データ型 | 32bitプラットフォーム | 64bitプラットフォーム |
|---|---|---|
Int | Int32と等しい | Int64と等しい |
UInt | UInt32と等しい | UInt64と等しい |
- Int:
17,0b10001,0o21,0x11,1_000_000 - Double: 倍精度浮動小数点数。例:
12.1875,1.21875e1,0xC.3p0,1_000.000_1 - Float: 単精度浮動小数点数。
Float(3.14)など明示が必要。 - Bool:
trueまたはfalse。 - String: 文字列。ダブルクォーテーション(
")で括る。例:"Some string literal value"- 複数行リテラルは3つのダブルクォーテーション(
""")で括る。行末の\は改行しない。 - Character型(1文字のリテラル)もダブルクォーテーション(
")で括る。
- 複数行リテラルは3つのダブルクォーテーション(
let singleLine = "1行の文字列"let multipleLines = /* 次の行から文字列を書く */ """ この行は空白で始まりません。 この行は4文字の空白で始まります。\ この行は前の行の後ろに続きます。 Unicode文字の例: \u{1f496} """ // 終了クォーテーションマーク前の空白がインデントと見なされるlet singleLineExt1 = #"拡張区切り文字を使うと特殊文字("や\nなど)はただの文字になります。"#let singleLineExt2 = #"ただし、\記号の直後に#を書くと\#n特殊文字のままになります。"#let singleLineExt3 = ###"#記号は\###n2つ以上書くことができます。"###let multipleLinesExt = #"""特殊文字("や\nなど)はただの文字になります。"""#let cat: Character = "🐱" // 1文字のリテラルlet dogs: Character = "🐕🐕🦺" // エラー型名に?が付いているものはオプショナルのデータ型です。値がない状態(nil)を持ちます。
タイプエイリアス
Section titled “タイプエイリアス”既存の型名に別の名前を定義できます。
typealias Byte = UInt8var buffer: [Byte] = [Byte.min, Byte.max] // [0x00, 0xff]と同じ数値の型変換
Section titled “数値の型変換”暗黙の型変換はありません。
let a = 3 // Int型let b = 0.14 // Double型: `.14`という書き方はエラーになる// let c = a + b // エラー: Int + Double は不可let c = Double(a) + b // OK: `a`からDouble型の値を作る文字列へのアクセス
Section titled “文字列へのアクセス”文字列は配列と同じような処理ができます(CollectionプロトコルやRangeReplaceableCollectionプロトコル)が、サブスクリプト構文ではString.Index型を渡す必要があるため、hello[2]のような書き方ができません。
var hello = "こんばんは"hello += "、世界" // "こんばんは、世界"var index = hello.index(hello.startIndex, offsetBy: 2)assert(hello[index] == "ば")
// 全ての文字のインデックスを取得しても、上とあまり変わらないvar indices = hello.indicesindex = indices.index(indices.startIndex, offsetBy: 2)assert(hello[index] == "ば")
// 何度も文字単位でアクセスするような処理では、Character型の配列にしてしまうほうが楽なのでは?var chars: [Character] = Array(hello)assert(chars[2] == "ば")assert(chars[6] == "世")
// insertやremove、範囲指定演算子なども使えるが、String.Index型で指定するvar begin = hello.index(hello.startIndex, offsetBy: 2)var end = hello.index(after: begin)hello.replaceSubrange(begin...end, with: "にち")
index = hello.index(hello.startIndex, offsetBy: 4)let hello2: Substring = hello[hello.startIndex...index] // `Substring`型assert(hello2 == "こんにちは")SwiftのString型は、内部ではUnicodeスカラから構築されています。Unicodeスカラは21bitの文字と修飾子で構成されます。
// Unicode表現let utf8 = Array<UInt8>(hello.utf8)let utf16 = Array<UInt16>(hello.utf16)let unicodeScalars = Array<Unicode.Scalar>(hello.unicodeScalars)数値と文字列の変換
Section titled “数値と文字列の変換”String(format:_:)で指定できるフォーマットの詳細(%@, %d, %x, %%など)は、String Format Specifiersを参照してください。%の直後にn$を書いて引数の位置を指定できます。
let dec: Int? = Int("123") // 10進数→Intlet hex: Int? = Int("7f", radix: 16) // 16進数→Intlet fp: Double? = Double("123.456") // 10進数→Double
let decStr = String(dec!) // Int→10進数let hexStr = String(hex!, radix: 16) // Int→16進数
import Foundation // これをインポートしないとString(format:_:)が使えない💢let fpStr = String(format: "%.5f", fp!)let hexStr2 = String(format: "%08x", hex!)let formattedMessage1 = String(format: "%@ が %d 個あります。", "🍎", 3)let formattedMessage2 = String(format: "%2$d 個の %1$@ があります。", "🍎", 3)4. コレクションとタプル
Section titled “4. コレクションとタプル”Swiftのコレクション(配列, 辞書, セット)はすべてジェネリクスで実装された構造体(値型)です。一方、タプルはコレクションではない点がPythonのタプルと異なります。
コレクションとタプルの可変性は、var宣言 (変数、ミュータブル) / let宣言 (定数、イミュータブル) で決まります。
コレクション
Section titled “コレクション”糖衣構文(シンタックスシュガー)を使って、Array<T>, Dictionary<T1, T2>はそれぞれ[T], [T1: T2]と書けます。
Set<T>のシンタックスシュガーはありません。
配列(Array)
Section titled “配列(Array)”var someInts: [Int] = [] // 空の配列の作成// var someInts = [Int]() // 同上
// count: 配列のアイテムの数print("someIntsは\(someInts.count)個の要素を持ちます。")
// isEmpty: countプロパティが0か否か// 三項演算子の`?`の前に空白を入れないとオプショナルチェーンに見なされてエラーになるprint( someInts.isEmpty ? "someIntsは空です" : "someIntsは空ではありません")
// repeatingはデフォルトの要素の値、countは繰り返し回数var threeInts = Array(repeating: 0, count: 3)
// 要素の追加var fruits = ["Apple", "Banana"] // [String]型の配列fruits.append("Orange")fruits += ["Pineapple", "Mango"]fruits.append(contentsOf: ["Grape", "Melon"])
// サブスクリプト構文fruits[0] = "🍎" // JS風の`fruits[fruits.count] = "🍒"`はインデックス範囲外(実行時エラー)print("2番目から4番目の果物は\(fruits[1...3])です。") // インデックスの範囲指定fruits[1...3] = ["🍌", "🍊", "🍍", "🍑", "🍋"]print(fruits)// ["🍎", "🍌", "🍊", "🍍", "🍑", "🍋", "Mango", "Grape", "Melon"]
// 要素の挿入・削除fruits.insert("🍓", at: 1)let removedFruit = fruits.remove(at: 3) // 🍊let removedFruit2 = fruits.removeLast() // Melonassert(fruits.contains("Melon") == false)print(fruits)// ["🍎", "🍓", "🍌", "🍍", "🍑", "🍋", "Mango", "Grape"]
// 定数let animals = ["Dog", "Cat"]animals.append("Bird") // エラー: letなので変更不可(イミュータブル)多次元配列は配列の要素に配列を入れて作ります。
let matrix: [[Int]] = [ [1, 2, 3], [4, 5, 6], [7, 8, 9],]assert(matrix[1][2] == 6)辞書(Dictionary)
Section titled “辞書(Dictionary)”var namesOfNumbers: [Int: String] = [:] // 空の辞書の作成// var namesOfNumbers = [Int: String]() // 同上
var airports = ["HND": "羽田空港", "KMQ": "小松空港"] // [String: String]型の辞書
// count: 辞書のアイテムの数print("airportsは\(airports.count)個の要素を持ちます。")
// isEmpty: countプロパティが0か否かprint( airports.isEmpty ? "airportsは空(から)です" : "airportsは空(から)ではありません")
// キーと値の追加・変更・削除namesOfNumbers.updateValue("十五", forKey: 15)let previousValue = namesOfNumbers.updateValue("fifteen", forKey: 15) // 十五let removedValue = namesOfNumbers.removeValue(forKey: 15) // fifteen
// サブスクリプト構文airports["NRT"] = "成田空港"if let airportName = airports["ABC"] { // 値はオプショナル値のため、オプショナルバインディングで値を取得する print("空港名は\(airportName)です。")} else { print("該当する空港名はありません。")}airports["KMQ"] = nil // nilが格納されるのではなく、削除される (PythonやJSと異なる動作)print(airports)// ["NRT": "成田空港", "HND": "羽田空港"]セット(Set)
Section titled “セット(Set)”var letters = Set<Character>() // 空のセットの作成
// 型注釈を書かないとArray<String>になるので注意var genres: Set<String> = ["ロック", "クラシック", "J-POP"]
// count: セットのアイテムの数print("genresは\(genres.count)個の要素を持ちます。")
// isEmpty: countプロパティが0か否かprint( genres.isEmpty ? "genresは空です" : "genresは空ではありません")
// 要素の挿入・削除genres.insert("ジャズ")let removedGenre = genres.remove("クラシック") // クラシックlet removedGenre2 = genres.remove("童謡") // nilprint(genres.contains("ロック") ? "含まれます" : "含まれません")
// 集合の演算、letを使っているので以下は全て不変let oddDigits: Set = [1, 3, 5, 7, 9]let evenDigits: Set = [0, 2, 4, 6, 8]let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]let 和集合 = oddDigits.union(evenDigits)let 積集合 = oddDigits.intersection(evenDigits)let 差集合 = oddDigits.subtracting(singleDigitPrimeNumbers)let 対称差 = oddDigits.symmetricDifference(singleDigitPrimeNumbers) // 排他的論理和の集合
// セットの要素と等価性let houseAnimals: Set = ["🐶", "🐱"]let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]let cityAnimals: Set = ["🐦", "🐭"]print(houseAnimals.isSubset(of: farmAnimals) ? "farmAnimalsに含まれる" : "farmAnimalsに含まれない")print(farmAnimals.isSuperset(of: houseAnimals) ? "houseAnimalsを含む" : "houseAnimalsを含まない")print(farmAnimals.isDisjoint(with: cityAnimals) ? "共通要素が無い" : "共通要素がある")タプル (Tuple)
Section titled “タプル (Tuple)”Swiftのタプルは 名前のない軽量な構造体 のようなもので、異なる型を入れることができます。Pythonのタプルと異なり、サブスクリプト構文やループ処理を使ってタプルの各要素にアクセスすることはできません。
// 定義let httpError = (404, "Not Found") // (Int, String) 型のタプル
// 左辺でタプルを展開できるlet (code, message) = httpErrorlet (justCode, _) = httpError // 不要な値は `_` で捨てられる
// インデックスアクセスlet statusCode = httpError.0let statusMessage = httpError.1
// 名前付きタプルvar user = (id: 1, name: "Alice")user.name = "Smith" // 変数の場合、タプルの値を変更できるassert(user == (1, "Smith"))
// タプルの比較: 左から順に値を比較assert((1, "zebra") < (2, "apple"))assert((3, "apple") < (3, "bird"))assert((4, "dog") == (4, "dog"))assert(("blue", false) < ("purple", true)) // エラー: Boolは`<`で比較できない
// 蛇足: Pythonは以下の右辺式でタプルになるが、Swiftではタプルにはならないlet value = (1, )assert(type(of: value) == Int.self) // valueはInt型5. 列挙型
Section titled “5. 列挙型”CやC#などのように、列挙ケース(列挙値)に暗黙の整数(0, 1, 2, ...)は割り当てられません。列挙ケースは、型推論によって型名を省略できます。
enum CompassPoint { case north case south case east, west // カンマ区切りで列挙ケースを複数定義できる}
let direction: CompassPoint = .north // 型推論により型名は省略できる
// `switch`を式として使うこともできるlet japaneseCompassPoint = switch direction {case .north: "北"case .south: "南"case .east: "東"case .west: "西"}print(japaneseCompassPoint)
// `CaseIterable`を継承すると`allCases`プロパティが使えるenum Beverage: CaseIterable { case coffee, tea, juice}for i in Beverage.allCases { // Array<Beverage>型のコレクション print(i)}構造体のように、列挙型にもメソッドなどを定義できます。詳細はオブジェクト指向の章を参照。
enum CompassPoint { case north, south, east, west
// `self`を変更するメソッドは`mutating`で修飾する(self.propertyNameへの変更も含まれる) mutating func turn(_ direction: CompassPoint) { self = direction }}
var direction = CompassPoint.southdirection.turn(.north)print("方角: \(direction)")列挙ケースに関連値と呼ばれる追加情報を与えることができます。関連値は、caseのパターンマッチを使って抽出できます。
enum Barcode { case upc(Int, Int, Int, Int) // UPC形式の1次元バーコード (関連値は4つの整数を持つタプル) case qrCode(String) // QRコード形式の2次元バーコード (関連値は文字列)}
var productBarcodes = [ Barcode.upc(8, 85909, 51226, 3), Barcode.qrCode("ABCDEFGHIJKLMNOP")]for barcode in productBarcodes { switch barcode { case let .upc(numberSystem, manufacturer, product, check): print("UPC : \(numberSystem), \(manufacturer), \(product), \(check)") case let .qrCode(productCode): print("QR code: \(productCode)") }}
// 再帰的列挙型 (indirect)// 以下の例では`case`ごとに`indirect`を書いているが、// `enum`の前に`indirect`と書くこともできる(`case`ごとの`indirect`は不要になる)enum ArithmeticExpression { case number(Int) indirect case addition(ArithmeticExpression, ArithmeticExpression) // 自身の列挙型を関連値として持つ indirect case multiplication(ArithmeticExpression, ArithmeticExpression) // 同上}
let three = ArithmeticExpression.number(3)let seven = ArithmeticExpression.number(7)let five = ArithmeticExpression.number(5)let sum = ArithmeticExpression.addition(three, seven)let product = ArithmeticExpression.multiplication(sum, five)
func eval(_ expression: ArithmeticExpression) -> Int { switch expression { case let .number(value): // numberの関連値(Int型)を抽出している return value case let .addition(left, right): // additionの関連値を抽出している // valueにマッチするまでeval関数を再帰的に呼び出す return eval(left) + eval(right) case let .multiplication(left, right): // multiplicationの関連値を抽出している // valueにマッチするまでeval関数を再帰的に呼び出す return eval(left) * eval(right) }}assert(eval(product) == 50)Raw Value
Section titled “Raw Value”列挙ケースに、全て同じ型の値(Raw Value)を事前に定義できます。Raw Valueは、文字列・文字・整数型・浮動小数点数型のいずれかの型になります。
// enum宣言の後ろに`:`+`型名`を付けるenum Planet: Int, CaseIterable { case mercury, venus, mars = 3, jupiter, saturn, uranus, neptune}print(Planet.allCases.map {$0.rawValue}) // [0, 1, 3, 4, 5, 6, 7]let earth = Planet(rawValue: 2) // rawValueから列挙型の値を得ることもできるassert(earth == nil) // nilを返す場合もある(Planet?型)
enum CompassPoint: String { case north, south, east, west}assert(CompassPoint.north.rawValue == "north")assert(CompassPoint.south.rawValue == "south")assert(CompassPoint.east.rawValue == "east")assert(CompassPoint.west.rawValue == "west")
// 整数や文字列のRaw ValueはSwiftが自動的に値を割り当てるが、// 浮動小数点数型や文字はRaw Valueを明示する必要があるenum ASCIIControlCharacter: Character { case tab = "\t" case lineFeed = "\n" case carriageReturn = "\r"}6. 制御構文
Section titled “6. 制御構文”条件式の括弧 ( ) は不要で、ブロックの中括弧 { } は必要です。
分岐 (switch, if, guard)
Section titled “分岐 (switch, if, guard)”switchやifは式としても使えます。
switch文
Section titled “switch文”暗黙でフォールスルー(次のcaseに通り抜けること)をしないので、末尾のbreakは不要です。途中でbreakは可能。
caseはパターンマッチで値を比較します。最初に一致したパターンのcaseを実行します。カンマ区切りで複合ケースを書くと、その中のいずれか1つにマッチすることを意味します。
caseによるパターンマッチはifやfor-inでも使えます。
let score = 75switch score {case 0: print("Zero")case 1..<50: // Range (1から49) print("Keep trying")case 50...100: // Range (50から100) print("Good")case ..<0: fallthrough // フォールスルーdefault: print("Invalid")}
let letter = "A"let result = switch letter {case "a", "A": // 複合ケース("a"または"A"にマッチ) "Aです"default: "A以外の文字です"}assert(result == "Aです")タプルでは、_はワイルドカードとして任意の値にマッチし、letで値を定数にバインディングします(値バインディング)。列挙型では、マッチした列挙ケースの関連値をバインディングできます。
whereで条件式をパターンに追加できます。
let point = (x: -2, y: -2)switch point { case (0, 0): print("原点")case (_, 0): // `_`は任意の値にマッチする print("X軸上の点")case (0, let y): print("Y軸上のy=\(y)の位置の点")case (-1...1, -1...1): print("-1以上1以下の範囲内の点")case let (x, y) where x > 0 && y > 0: print("第1象限にある点")case let (x, y) where x < 0 && y < 0: print("第3象限にある点")default: break // case・default内のコードは空に出来ないのでbreakを書く}let score = 75
if score >= 80 { print("A判定")} else if score >= 60 { print("B判定")} else { print("C判定")}
// 式としても使えるlet result = if score >= 80 { "A判定" // ブロック内は単一式} else if score >= 60 { "B判定"} else { nil as String?}
// パターンを使うlet point = (x: 10, y: 20)if case (10, let y) = point { // `==`ではない print("y=\(y)")}guard文
Section titled “guard文”条件を満たさない場合は早期リターンするための構文です。
func greet(person: [String: String]) { guard let name = person["name"] else { return // nameがない場合はここで終了 } print("Hello \(name)!") // 以降、nameが使える}
// パターンを使うfunc test(_ point: (x: Int, y: Int)) { guard case (11, let y) = point else { print("x=11ではありません。") return } print("x=11, y=\(y)")}test((10, 20)) // x=11ではありません。test((11, 22)) // x=11, y=22繰り返し (for-in, while)
Section titled “繰り返し (for-in, while)”配列のループ
let names = ["Anna", "Alex", "Brian", "Jack"]for name in names { print("Hello, \(name)!")}
// Pythonのenumerate相当for (index, name) in names.enumerated() { print("No. \(index): \(name)")}
// パターンを使う(`where`の位置は`in`の後ろ)for case let name in names where name.count < 5 { print(name)}セットのループ
let nameSet = Set(names)for (index, name) in nameSet.sorted().enumerated() { print("No. \(index): \(name)")}辞書のループ
var airports = ["HND": "羽田空港", "KMQ": "小松空港"]
// キーと値のペアでループfor (code, name) in airports { print("\(code): \(name)")}
// キーだけでループfor code in airports.keys { print("\(code): \(airports[code]!)") // オプショナルの強制アンラップが必要}文字列から1文字ずつ取り出す
for (index, char) in "こんにちは".enumerated() { print("\(index + 1)文字目: \(char)")}範囲ループ
for i in 0..<5 { // 0, 1, 2, 3, 4 print(i)}
// Pythonのrange(start, stop, step)相当for i in stride(from: 0, to: 60, by: 5) { // 0, 5, 10, ..., 55 print(i)}
// throughを使うと、それ自身の値も含むfor i in stride(from: 0, through: 60, by: 5) { // 0, 5, 10, ..., 60 print(i)}
// strideは浮動小数点数にも使えるfor i in stride(from: 0.1, through: 1.0, by: 0.1) { // 0.1, 0.2, ..., 1.0 print(i)}whileループ
var n = 1while n < 100 { n *= 2}
// Cなどのdo-whileループに相当repeat { n /= 2} while n > 1ループ構文やswitchでは、breakやcontinueにラベルが使えます。
mainLoop: while true { print("何か入力してください(endで終了): ", terminator: "") let line: String? = readLine(strippingNewline: true) if let line { switch line { case "end": print("終了します") break mainLoop case let line where line.count == 0: continue mainLoop default: print("入力値: \(line)") } } else { print("強制終了します") break // `break mainLoop`と同じ }}7. 関数とクロージャ
Section titled “7. 関数とクロージャ”関数の定義はfunc 名前(引数) -> 戻り値の型名です。
引数は引数ラベル パラメータ名: 型名の形式で記述します。
呼び出し側では、引数に引数ラベルを記述します。引数ラベルが_の場合、呼び出し側の引数ラベルは省略します。
// 定義: func 名前(引数ラベル パラメータ名: 型名) -> 戻り値の型名func greet(person name: String, from city: String) -> String { return "Hello \(name), Glad you could visit from \(city)."}greet(person: "Bob", from: "Sendai")
// 引数ラベル省略 (`_` を使う)// 関数が単一式の場合、`return`を省略できる (getプロパティやクロージャも同じ)func addSub(_ a: Int, _ b: Int) -> (Int, Int) { (a + b, a - b)}assert(addSub(1, 2) == (3, -1))
// 引数ラベルを書かない場合は、パラメータ名が引数ラベルになるfunc greet(person: String, dayOfWeek: String) { // 戻り値の`Void`は省略できる(Voidは空タプル) print("こんにちは\(person)さん、今日は\(dayOfWeek)です。")}greet(person: "山田", dayOfWeek: "金曜日") // 引数ラベルが違えばオーバーロードできる
// デフォルトパラメータ値func 消費税込価格(_ price: Int, tax: Double = 0.10) -> Int { Int(Double(price) * (1.0 + tax))}assert(消費税込価格(1000) == 1100)
// 可変長パラメータfunc sum(_ numbers: Double...) -> Double { var total = 0.0 for n in numbers { total += n } return total}assert(sum(1, 2, 3.4) == 6.4)
// In-Outパラメータfunc swapVars(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}var x: Int = 100, y: Int = 200swapVars(&x, &y) // In-Outパラメータに渡す変数の前には`&`を付けるprint("x=\(x), y=\(y)")全ての関数には関数型があります。
var f: (inout Int, inout Int) -> Void = swapVarsf(&x, &y)print("x=\(x), y=\(y)")
// 関数のパラメータの型として関数型を使うfunc printNumber(_ number: Int) { print("値は\(number)です。")}func loop(count: Int, _ callback: (Int) -> Void) { for i in 1...count { callback(i) }}loop(count: 10, printNumber)
// 関数の戻り値として関数型を使うfunc getOffsetFunction(_ offset: Int) -> (Int) -> Int { func offsetFunction(_ number: Int) -> Int { // ネスト関数 (関数の中で関数を定義) number + offset // offsetをキャプチャしている } return offsetFunction}let offset5 = getOffsetFunction(5)assert(offset5(10) == 15)他言語の匿名関数に相当します。
{ (引数) -> 戻り値 in 処理 } の形式で記述しますが、型推論や省略引数名($0, $1, $2, ...)により簡略化できます。
let numbers = [1, 2, 3]let doubled = numbers.map({ (number: Int) -> Int in return number * 2})
// 省略記法 (型推論で引数の型と`( )`と戻り値を省略、さらにreturnも省略)let tripled = numbers.map({number in number * 3})
// 引数は$0, $1, $2, ...で省略でき、中身のない`()`も省略できるlet simpleDoubled = numbers.map({ $0 * 2 })let descending = numbers.sorted(by: {$0 > $1})
// 参考: `>`は`(Self, Self) -> Bool`型の関数なので// {$0 > $1}のようなクロージャを作らなくとも// 直接`by`パラメータに渡すことができるlet shortDescending = numbers.sorted(by: >)assert(descending == shortDescending)
// クロージャを戻り値として返すfunc getOffsetFunction(_ offset: Int) -> (Int) -> Int { return {(_ number: Int) -> Int in // offsetはクロージャの外からキャプチャしている number + offset }}let offset5 = getOffsetFunction(5)assert(offset5(10) == 15)末尾クロージャは、関数引数の括弧( )の後ろに記述します。
// 末尾クロージャを持つ関数の定義func doLoop(count n: Int = 5, body: (_ number: Int) -> Void) { for i in 1...n { body(i) }}
// 末尾クロージャとして記述するdoLoop(count: 3) { number in print("\(number): Hello world")}
// 空の`()`は省略できるdoLoop { number in print("\(number): Hello world")}
// 複数の末尾クロージャを持つ関数func dumpList(_ list: [String], header: () -> String, body: (String) -> String, footer: () -> String) { print("\(header())") for i in list {print("\(body(i))")} print("\(footer())")}
dumpList(["A", "B", "C"]) { // 最初の末尾クロージャは引数ラベルを付けない ">>>>> 開始 >>>>>"} body: { // 2つ目以降の末尾クロージャは引数ラベルを付ける "値は\($0)です"} footer: { "<<<<< 終了 <<<<<"}関数の引数に渡されたクロージャが関数から抜けた後に呼びされるような使い方をするときは、@escapingを付けて関数をエスケープします。
var eventHandlers: Array<() -> Void> = [] // Arrayの部分は[() -> Void]とも書ける
// Playgroundでは以下のエラーが発生するため、`@MainActor`を付ける// `.swift`ファイルに保存して実行するときは`@MainActor`を外すこと// `main actor-isolated var 'eventHandlers' can not be referenced from a nonisolated context`@MainActorfunc registerEventHandler(eventHandler: @escaping () -> Void) { // registerEventHandler関数を抜けた後、 // eventHandlerはeventHandlers経由で呼び出される eventHandlers.append(eventHandler)}registerEventHandler { print("Hello world!") }
@MainActorfunc invokeEventHandlers() { for handler in eventHandlers { handler() }}
invokeEventHandlers()クラスの中でエスケープクロージャを使う場合、self.を明示的に書くか、クロージャのキャプチャリストにselfを入れます。
@MainActorclass SampleCounter { var value = 0 init() { // selfがクラスの場合、 // エスケープクロージャには`self.`を明示的に書く registerEventHandler { self.value += 1 } // あるいはクロージャのキャプチャリストにselfを入れる // registerEventHandler { [self] in value += 1 } } func print() { Swift.print("値は\(value)です") // グローバル関数のprintを呼び出す }}
let sampleCounter = SampleCounter()invokeEventHandlers()sampleCounter.print()エスケープクロージャはselfを変更可能(mutating)でキャプチャするので、構造体や列挙型ではあまり使えそうにありません。
@MainActorstruct SampleCounter { var value = 0 init() { registerEventHandler { Swift.print(value) } // エラー: escaping closure captures mutating 'self' parameter registerEventHandler { Swift.print("init") } // OK registerEventHandler(eventHandler: print) // OK } func print() { Swift.print("値は\(value)です") }}
let sampleCounter = SampleCounter()invokeEventHandlers()自動クロージャは、関数の引数として渡された式をラップして自動的に作成されるクロージャです。
assert(condition:message:file:line:)のconditionとmessageは自動クロージャで引数を受け取ります。
func testCondition(_ condition: @autoclosure () -> Bool, then: () -> String, or: () -> String) ->String { condition() ? then() : or()}
for i in 0..<10 { let result = testCondition(i < 5) { "\(i)は5より小さい" } or: { "\(i)は5以上" } print(result)}async / await
Section titled “async / await”asyncで非同期関数(非同期メソッドも同様)を定義して、呼び出し側ではawaitを書きます。
func listPhotos(album name: String) async throws -> [String] { let library = [ "風景": ["IMG101", "IMG102", "IMG103", ], "人物": ["IMG201", "IMG202", "IMG203", ], "乗り物": ["IMG301", "IMG302", "IMG303", ], ]
// DBなどから写真の一覧を非同期で取得する処理の代替 try await Task.sleep(for: .seconds(1.0))
if let photos = library[name] { return photos } else { return [] // `[String]()`に型推論される }}
do { let data = try await listPhotos(album: "人物") print(data)} catch { print(error)}
// 非同期関数を並行に実行するdo { async let scenery = listPhotos(album: "風景") async let people = listPhotos(album: "人物") async let vehicle = listPhotos(album: "乗り物") // 右辺式は単純に`try await [scenery, people, vehicle]`と書いてもよい let data = try await [ "風景": scenery, "人物": people, "乗り物": vehicle ] print(data)} catch { print(error)}不特定数の非同期関数を並行に実行するのであれば、withTaskGroupやwithThrowingTaskGroupでタスクグループを使います。
async.swiftのdo-catchの部分は、以下のように書き換えできます。
do { let data = try await withThrowingTaskGroup(of: (String, [String]).self) { group in for i in ["風景", "乗り物", "人物"] { group.addTask {try await (i, listPhotos(album: i))} } var photos: [String: [String]] = [:] for try await (i, p) in group { photos[i] = p } return photos } print(data)} catch { print(error)}非同期関数を中断できるようにするには、Task.isCancelledでキャンセル状態を把握するか、Task.checkCancellation()でキャンセル済みのときはエラーをスローする処理を入れます。
import Foundation // fflush用
// 処理時間の長い非同期関数func longTermWorker(minutes: Int) async throws -> String { for i in 1...minutes * 60 { try Task.checkCancellation() print("...", terminator:""); fflush(stdout) try? await Task.sleep(for: .seconds(1.0)) // キャンセル時のスローを抑止 print(Task.isCancelled ? "\(i)秒目で中断" : "\(i)秒経過") } return "longTermWorker completed"}
do { // 処理時間の長い非同期関数の実行 let task = Task { let result = try await longTermWorker(minutes: 10) return result }
// 5.5秒後に中断する try await Task.sleep(for: .seconds(5.5)) task.cancel()
// 処理時間の長い非同期関数の結果待ち let result = await task.value print("処理結果: \(result)")} catch { print("エラー: \(error)")}8. オブジェクト指向
Section titled “8. オブジェクト指向”Swiftには構造体(struct)とクラス(class)があります。共に以下の特徴があります。
- プロパティとメソッドを定義できる(インスタンス変数の概念は無い)
- サブスクリプトを定義できる
- イニシャライザで初期化できる
extensionで機能を拡張できる- プロトコルに準拠できる
一方で、以下の違いがあります。
| 特徴 | class | struct |
|---|---|---|
| 代入 | 参照が渡される | コピーが渡される |
| 同値演算子 | ===, !==が使える | 左記の演算子は使えない |
| 継承 | 別のクラスを継承できる | 継承できない |
| イニシャライザ未定義の場合 | デフォルトイニシャライザ | メンバワイズイニシャライザ |
| convenienceイニシャライザ | 定義できる | 定義できない |
| デイニシャライザ | 定義できる | 定義できない |
| 型キャスト | できる | できない |
Swiftでは継承が必要ない限り、struct(値型) をデフォルトで使います。
以下ではstructとclassを中心にまとめていますが、列挙型のenumにも以下の機能が備わっています。
- 計算プロパティ(格納プロパティは無い)
- 型プロパティ(格納/計算の両方で使える)
- メソッド
- イニシャライザ
- サブスクリプト
格納プロパティ
Section titled “格納プロパティ”varまたはletを使って格納プロパティを定義します。
struct Resolution { var width = 0 var height = 0}
class VideoMode { var resolution = Resolution() var interlaced = false var frameRate = 0.0 let name: String init(name: String) { self.name = name // 初期化時に設定した後は変更不可能 }}
// インスタンス生成var someVideoMode = VideoMode(name: "何らかのビデオモード")someVideoMode.name = "別のビデオモード" // エラー: nameは変更できない
var someResolution = Resolution()(someResolution.width, someResolution.height) = (1920, 1080)someVideoMode.resolution = someResolution // 値のコピーsomeResolution.width = 1280assert(someVideoMode.resolution.width == 1920) // コピーなので、someResolutionとは別の値を持つ
someVideoMode.frameRate = 60.0var anotherVideoMode = someVideoModeanotherVideoMode.frameRate = 30.0assert(someVideoMode.frameRate == 30.0) // anotherVideoModeと同じインスタンスを指すvarの前にlazyを指定すると遅延格納プロパティになり、プロパティにアクセスするまでインスタンスが作成されません。lazyはletでは使えません。
struct A { var value: Int = 0 init() { print("A.init()") } mutating func setValue(_ value: Int) { print("A.setValue()") self.value = value }}struct B { lazy var a = A() var value: Int = 0 init() { print("B.init()") } mutating func setValue(_ value: Int) { print("B.setValue()") self.value = value }}var b = B() // "B.init()"が出力されるb.setValue(5) // "B.setValue()"が出力されるb.a.setValue(10) // "A.init()" "A.setValue()"の順で出力される計算プロパティ (get / set)
Section titled “計算プロパティ (get / set)”getとsetを使って定義します。
struct Square { var sideLength = 0.0 var area: Double { get { sideLength * sideLength } set(value) { sideLength = value.squareRoot() } // あるいは以下のように`newValue`を使って書ける // set {sideLength = newValue.squareRoot()} } var description: String { // 読み取り専用プロパティ(set無し)は`get`の記述が不要 "辺の長さ=\(sideLength) 面積=\(area)" }}var square = Square(sideLength: 10)assert(square.area == 10 * 10)square.area = 21 * 21assert(square.sideLength == 21)print(square.description)プロパティオブザーバ
Section titled “プロパティオブザーバ”willSetはプロパティ値が格納される直前に呼び出され、didSetはプロパティ値が格納された直後に呼び出されます。
class A { var a: Int = 100 { willSet { // `set`と同じく`newValue`が使える print("(2) A.a: \(a) -> \(newValue)") } didSet { // `oldValue`を使うかパラメータ名を指定する print("(3) A.a: \(a) <- \(oldValue)") if a > 500 { // もしプロパティ値を是正してもdidSetは再度呼び出されない a = 500 } } }}
class B: A { override var a: Int { willSet { print("(1) B.a: \(a) -> \(newValue)") } didSet { print("(4) B.a: \(a) <- \(oldValue)") } }}
var b = B()b.a = 999
// 実行結果:// (1) B.a: 100 -> 999// (2) A.a: 100 -> 999// (3) A.a: 999 <- 100// (4) B.a: 500 <- 100プロパティラッパ
Section titled “プロパティラッパ”プロパティをラップしてカスタムコードをプロパティに追加します。
@propertyWrapper属性を付けてプロパティラッパを実装します。wrappedValueプロパティでプロパティ値がラップされます。projectedValueプロパティでプロパティ値から投影された値を保持します。- 投影された値は $プロパティ名 で取得できます。
// projectedValue(プロパティラッパからの投影)用enumの定義enum ProjectionType { case 範囲内, 最小値より小さい, 最大値より大きい}
// 範囲制限付き整数(min〜max)を表すプロパティラッパの定義@propertyWrapperstruct LimitedNumber { private var _value: Int = 0 // プロパティの値を格納する private var _min: Int = Int.min // 最小値 private var _max: Int = Int.max // 最大値 var projectedValue = ProjectionType.範囲内 // プロパティラッパからの投影
// イニシャライザ init() { print("(1) init()") } init(wrappedValue: Int) { print("(2) init(wrappedValue:)") _value = clamp(wrappedValue, min: _min, max: _max) } init(wrappedValue: Int = 0, min minValue: Int = Int.min, max maxValue: Int = Int.max) { print("(3) init(wrappedValue:min:max:)") _value = clamp(wrappedValue, min: minValue, max: maxValue) (_min, _max) = (minValue, maxValue) }
// プロパティの値を設定・取得するwrappedValueメソッドを必ず定義する var wrappedValue: Int { get { _value } set { _value = clamp(newValue, min: _min, max: _max) } }
// サブルーチン private mutating func clamp(_ value: Int, min: Int, max: Int) -> Int { if value < min { projectedValue = .最小値より小さい return min } else if value > max { projectedValue = .最大値より大きい return max } else { projectedValue = .範囲内 return value } }}
// プロパティラッパを使うstruct A { @LimitedNumber var a: Int // (1) @LimitedNumber var b: Int = 100 // (2) @LimitedNumber(min: 0) var c: Int = 200 // (3) @LimitedNumber(min: 0, max: 10) var d: Int = 300 // (3)}
var x = A()assert(x.a == 0)assert(x.b == 100)assert(x.c == 200)assert(x.d == 10)x.c = -200assert(x.c == 0)
// プロパティラッパからの投影print(x.$a) // 範囲内print(x.$b) // 範囲内print(x.$c) // 最小値より小さいprint(x.$d) // 最大値より大きい型プロパティ
Section titled “型プロパティ”型プロパティはC/C++のstaticメンバ変数のように、staticキーワードで静的なプロパティを型に定義します。クラスでオーバーライド可能な型プロパティを定義するときはstaticの代わりにclassを使います。
// Playgroundでは以下のエラーが発生するため、`@MainActor`を付ける// `Static property 'storedTypeProperty' is not concurrency-safe because it is nonisolated global shared mutable state`@MainActorstruct A { static var storedTypeProperty = "struct A" static var computedTypeProperty: Int { return 1 } var value: (String, Int) { (A.storedTypeProperty, A.computedTypeProperty) }}
@MainActorenum B { case b1, b2 static var storedTypeProperty = b2 var value: B { B.storedTypeProperty }}
@MainActorclass C { static var storedTypeProperty = "class C" static var computedTypeProperty: Int { return 3 } class var overridableComputedTypeProperty: Int { // オーバーライド可能な計算プロパティは`class`で定義する return 30 } var value: (String, Int, Int) { (C.storedTypeProperty, C.computedTypeProperty, C.overridableComputedTypeProperty) }}
class D: C { override class var overridableComputedTypeProperty: Int { // オーバーライド可能な計算プロパティは`class`で定義する return 300 } override var value: (String, Int, Int) { (D.storedTypeProperty, D.computedTypeProperty, D.overridableComputedTypeProperty) }}
var a1 = A(), a2 = A()A.storedTypeProperty = "上書きした値"print(a1.value, a2.value) // ("上書きした値", 1) ("上書きした値", 1)var b1 = B.b1, b2 = B.b2B.storedTypeProperty = .b1print(b1.value, b2.value) // b1 b1var c1 = C(), d1 = D()D.storedTypeProperty = "Dに上書きした値"print(c1.value, d1.value) // ("Dに上書きした値", 3, 30) ("Dに上書きした値", 3, 300)インスタンスメソッド
Section titled “インスタンスメソッド”struct、class、enumの定義内に関数と同じ構文で記述します。
- インスタンス自身は
selfで参照可能 (thisではない) structとenumのメソッドがインスタンスの値を変更する場合、mutatingが必要self自体に新しいインスタンスを割り当てることも可能 (self = newValueのように)
enum State: CustomStringConvertible { // `description`プロパティでtoStringや__str__相当を実装できる case ready, running, emergency mutating func change(to newValue: State) { self = newValue }
static let displayTexts: [State: String] = [ .ready: "準備中", .running: "運行中", .emergency: "緊急停止中" ] var description: String { // CustomStringConvertibleプロトコル return State.displayTexts[self]! }}struct A { // `class`にするとメソッド定義の`mutating`は不要 private var state: State = .ready mutating func start() { state.change(to: .running) self.print() } mutating func stop() { state.change(to: .ready) self.print() } mutating func emergencyStop() { state.change(to: .emergency) self.print() } mutating func reset() { self = A() self.print() } func print() { Swift.print("現在の状態: \(state)") }}
var a = A()a.start()a.emergencyStop()a.reset()
let a2 = A()a2.start() // エラー: a2は定数のためmutatingメソッドを呼び出せない型メソッドはC/C++のstaticメンバ関数のように、staticキーワードで静的なメソッドを型に定義します。クラスでオーバーライド可能な型メソッドを定義するときはstaticの代わりにclassを使います。
@MainActorclass LimitedCounter { static var maxCount: Int = 5 var count: Int = 0 static func setMaxCount(_ maxCount: Int) { // 型メソッドからは、`self`または`Self`で型プロパティにアクセスできる self.maxCount = maxCount } class func reset() { // オーバーライド可能な型メソッド maxCount = 5 } func increment() { // インスタンスメソッドからは、型名か`Self`で型プロパティにアクセスできる if count < Self.maxCount { count += 1 } }}
@MainActorclass CustomLimitedCounter: LimitedCounter { override class func reset() { maxCount = 3 }}
var counter = LimitedCounter()LimitedCounter.setMaxCount(7)for i in 1...10 { counter.increment() print(counter.count)}
var customCounter = CustomLimitedCounter()CustomLimitedCounter.reset()for i in 1...10 { customCounter.increment() print(customCounter.count)}サブスクリプト構文
Section titled “サブスクリプト構文”struct、class、enumの定義内にsubscriptを定義すると、コレクション(配列・辞書・セット)のようなサブスクリプト構文が使えます。Pythonの__getitem__風の機能を実装できます。
class Airports { var airports = ["HND": "羽田空港", "KMQ": "小松空港"] subscript(code: String) -> String? { get { airports[code] } set { airports[code] = newValue } } func dump() { print(airports) }}var airports = Airports()assert(airports["HND"] == "羽田空港")airports["HND"] = nilairports["NRT"] = "成田空港"airports.dump() // ["KMQ": "小松空港", "NRT": "成田空港"]サブスクリプトは任意の数のパラメータを渡せます。staticを使って型サブスクリプトも定義できます。
import Foundation
// 8bitのRGBチャンネルを指し示す列挙型enum Channel: Int { case r = 0, g, b static let count: Int = 3 static subscript(channel: Int) -> Channel { Channel(rawValue: channel)! }}
// 8bitのRGBチャンネルを持つ画像データを模倣するクラスclass ImagePixels { let width: Int, height: Int var pixels: [UInt8]
init(_ width: Int, _ height: Int) { (self.width, self.height) = (width, height) pixels = Array(repeating: UInt8(0), count: width * height * 3) } func indexIsValid(x: Int, y: Int) -> Bool { return x >= 0 && x < width && y >= 0 && y < height } subscript (x: Int, y: Int, channel: Channel) -> UInt8 { // 複数の引数を持つことができる get { assert(indexIsValid(x: x, y: y)) let index = (y * width + x) * 3 + channel.rawValue return pixels[index] } set { assert(indexIsValid(x: x, y: y)) let index = (y * width + x) * 3 + channel.rawValue pixels[index] = newValue } } func dump() { // HTMLカラーコード形式で画素値を表示する for y in 0..<height { for x in 0..<width { var colorCode = "#" for i in 0..<Channel.count { colorCode += String(format: "%02x", self[x, y, Channel[i]]) } print(colorCode, terminator: " ") } print() } }}var imagePixels = ImagePixels(8, 8)imagePixels[7, 0, .r] = 255 //右上に赤い点imagePixels[0, 7, .g] = 255 //左下に緑の点imagePixels[7, 7, .b] = 255 //右下に青の点imagePixels.dump()クラス・構造体・列挙型・プロトコルなどの型の中に別の型を定義できます。
class A { // クラスの中に構造体を定義 struct B { // 構造体の中に列挙型を定義 enum C { case x, y, z }
var value: C }
var data: B
init(_ value: B.C) { data = B(value: value) }
func getValue() -> A.B.C { return data.value }}
// テストlet a = A(A.B.C.x)print(a.getValue())継承はclassでのみ使えます。
Swiftでは別のクラスから継承しないクラスを基本クラス(base class)と呼びます。基本クラスには、C#やJavaのObjectクラスや、Pythonのobjectクラスのような全てのクラスで基底となるクラスがありません。(C++のクラスと同様)
overrideを使ってオーバーライドできます。(インスタンスメソッド、型メソッド、インスタンスプロパティ、型プロパティ、サブスクリプトごとに指定可能)
finalを使ってオーバーライドされないようにできます。(インスタンスメソッド、型メソッド、インスタンスプロパティ、型プロパティ、サブスクリプトに加えてclassにも指定可能)
// 基本クラスの定義class Vehicle { var currentSpeed = 0.0 var description: String { return "速度は \(currentSpeed) km/h" } func makeNoise() { // 乗り物は必ずしも騒音を出しません }}
// サブクラスの定義class Train: Vehicle { override func makeNoise() { // 電車を想定 print("モーター + VVVFインバータ音") }}class Car: Vehicle { var gear = 1 override var description: String { "ギアは \(gear) 速で、" + super.description } override func makeNoise() { print("エンジン音") }}let car = Car()(car.gear, car.currentSpeed) = (3, 40.0)print(car.description)car.makeNoise()
// finalクラスの定義 (これ以上サブクラスを作ることはできない)final class Prius: Car { override func makeNoise() { print("モーター + エンジン音") }}let vehicle: Vehicle = Prius()vehicle.makeNoise()型チェック演算子(is)でクラスのインスタンスが特定のサブクラスかどうか確認できます。
assert(vehicle is Prius)assert(vehicle is Car)assert(!(vehicle is Train))サブクラス型へのダウンキャストには型キャスト演算子(as?またはas!)を使用します。
条件付き形式のas?は、以下のように使います。
vehicle.currentSpeed = 30if let car = vehicle as? Car { car.gear = 2}print(vehicle.description)強制形式のas!は、以下のように使います。
let myCar = vehicle as! Car // もしCarのサブクラスでない場合、実行時エラーmyCar.currentSpeed = 50myCar.gear = 3print(myCar.description)is及びas?またはas!は、プロトコルにも使用できます。
AnyとAnyObjectの型キャスト
Section titled “AnyとAnyObjectの型キャスト”Anyは、関数型も含めて、あらゆる型のインスタンスを表します。また、AnyObjectは、任意のクラス型のインスタンスを表します。
AnyやAnyObjectのデータはisやas演算子で型キャストできます。また、switch文のcaseにisやasでパターンマッチさせることができます。
class A { let x: Int init(x: Int) { self.x = x }}
let values: [Any] = [ 0, 1.2, "Hello", [1, 2, 3], (1, 2), A(x: 123), { (a: Int, b: Int) -> Int in a + b }]
if let value = values[0] as? Int { print("value is \(value) as Int")}
for value in values { switch value { case 0 as Int: print("value is 0 as Int") case 1.2 as Double: print("value is 1.2 as Double") case let array as [Int]: print("value is \(array) as [Int]") case let (x, y) as (Int, Int): print("value is (\(x), \(y)) as (Int, Int)") case is A: print("value is an instance of A") case let add as (Int, Int) -> Int: let result = add(1, 2) print("value is a function: \(result) = add(1, 2)") default: print("value is not supported") }}イニシャライザ
Section titled “イニシャライザ”インスタンス生成時の初期化を行うイニシャライザ(init)をstruct、class、enumで定義できます。
struct 摂氏: Equatable { var 摂氏温度: Double init(華氏 華氏温度: Double) { 摂氏温度 = (華氏温度 - 32.0) / 1.8 } init(K ケルビン: Double) { // 引数の型や数が同じでも引数ラベルが異なるとオーバーロード可能 self.init(ケルビン - 273.15) // イニシャライザの委譲 } init(_ 摂氏温度: Double) { self.摂氏温度 = 摂氏温度 } static func == (lhs: 摂氏, rhs: 摂氏) -> Bool { // `==`演算子の実装 return lhs.摂氏温度 == rhs.摂氏温度 }}let 水の沸点 = 摂氏(華氏: 212.0)assert(水の沸点.摂氏温度 == 100.0)let 水の凝固点 = 摂氏(K: 273.15)assert(水の凝固点 == 摂氏(0.0))初期化に失敗し得るイニシャライザはinit?(nilを返す)を使って失敗可能イニシャライザを定義できます。
struct Counter { var _counter: Int init?(from value: Int? = nil) { if let value { _counter = value } else { return nil } } var count: Int { _counter } mutating func countUp() { _counter += 1 }}
// テストvar counter: Counter? = Counter()counter?.countUp()デフォルトイニシャライザ(クラス)
Section titled “デフォルトイニシャライザ(クラス)”classでは、initを定義しなければデフォルトイニシャライザが自動生成されます。
class Resolution { var width: Int = 0 // 全てのプロパティには初期値が必要 var height: Int = 0 // classにinitを追加すると、デフォルトイニシャライザは生成されない}let someResolution = Resolution()メンバワイズイニシャライザ(構造体)
Section titled “メンバワイズイニシャライザ(構造体)”structでは、initを定義しなければメンバワイズイニシャライザが自動生成されます。
なお、enumにデフォルトイニシャライザやメンバワイズイニシャライザは存在しません。
struct Resolution { var width: Int = 0 var height: Int // デフォルト値が無いためイニシャライザで初期化が必要 // structにinitを追加すると、メンバワイズイニシャライザは生成されない}let fullHD = Resolution(width: 1920, height: 1080)let someResolution = Resolution(height: 480)クラスの継承とイニシャライザ
Section titled “クラスの継承とイニシャライザ”Swiftはクラスに対して2種類のイニシャライザを定義して、サブクラスからスーパークラスまでの全ての格納プロパティを初期化します。
- 指定イニシャライザ (designatedイニシャライザ):
initで定義する- 全てのクラスには少なくとも1つの指定イニシャライザが必要になる (デフォルトイニシャライザも含む)
- 直接スーパークラスの指定イニシャライザを呼び出す必要がある
- convenienceイニシャライザ:
convenience initで定義する- 同じクラスの別の指定イニシャライザまたはconvenienceイニシャライザを呼び出す必要がある・・・①
- 最終的に、同じクラスの指定イニシャライザを呼び出す必要がある・・・②
- 1つの指定イニシャライザに対して、複数のconvenienceイニシャライザを補助的に定義する (引数のデフォルト値を設定するなど)
- 不要ならconvenienceイニシャライザは定義しなくてよい
class A { var n: Int = 0 var s: String = ""
// 指定イニシャライザ init(n: Int, s: String) { print("A: init(n: \(n), s: \"\(s)\")") self.n = n self.s = s }
// convenienceイニシャライザ convenience init(n: Int) { print("A: convenience init(n: \(n))") self.init(n: n, s: "Aのコンビニinit") // ②を満たす }
// convenienceイニシャライザ convenience init() { print("A: convenience init()") self.init(n: 1) // ①を満たす }}
var a1 = A()// A: convenience init()// A: convenience init(n: 1)// A: init(n: 1, s: "Aのコンビニinit")
var a2 = A(n: 2)// A: convenience init(n: 2)// A: init(n: 2, s: "Aのコンビニinit")
var a3 = A(n: 3, s: "Aの指定init")// A: init(n: 3, s: "Aの指定init")Swiftのサブクラスは、デフォルトでスーパークラスのイニシャライザを継承しません。
class B: A { var b: Int
// 指定イニシャライザ init(b: Int, n: Int, s: String) { print("B: init(b: \(b), n: \(n), s: \"\(s)\")") self.b = b // 先にサブクラスのプロパティを初期化する (第1段階の初期化) super.init(n: n, s: s) // スーパークラスの初期化 }
// convenienceイニシャライザ (Aのconvenienceイニシャライザをオーバーライドしていない) convenience init() { print("B: convenience init()") self.init(b: 10, n: 11, s: "Bのコンビニinit") }}
var b1 = B()// B: convenience init()// B: init(b: 10, n: 11, s: "Bのコンビニinit")// A: init(n: 11, s: "Bのコンビニinit")
var b2 = B(b: 20, n: 21, s: "Bの指定init")// B: init(b: 20, n: 21, s: "Bの指定init")// A: init(n: 21, s: "Bの指定init")
var b4 = B(n: 40, s: "") // エラー: Aの指定イニシャライザは継承しないvar b3 = B(n: 30) // エラー: Aのconvenienceイニシャライザは継承しないスーパークラスの指定イニシャライザをサブクラスでも使えるようにするには、override initでオーバーライドします。
class C: A { var c: Int
// 指定イニシャライザのオーバーライド override init(n: Int, s: String) { print("C: init(n: \(n), s: \(s))") self.c = 200 super.init(n: n, s: s) }}
var c = C(n: 100, s: "Cの指定init")// C: init(n: 100, s: Cの指定init)// A: init(n: 100, s: "Cの指定init")ただし、サブクラスのプロパティがデフォルト値を持っている場合、以下のルールが適用されます。
- サブクラスが指定イニシャライザを定義していない場合、スーパークラスの指定イニシャライザを全て継承する (下記サンプルの
Dクラス) - サブクラスがスーパークラスの指定イニシャライザを全て継承している(下記サンプルの
Dクラス)か、全て定義している(下記サンプルのEクラス)場合、スーパークラスのconvenienceイニシャライザを全て継承する
下記のDクラスは指定イニシャライザを定義していないので、スーパークラスの指定イニシャライザとconvenienceイニシャライザを全て継承します。
class D: A { var d: Int = 0}
var d1 = D(n: 1000, s: "Aから継承した指定init")// A: init(n: 1000, s: "Aから継承した指定init")
var d2 = D(n: 2000)// A: convenience init(n: 2000)// A: init(n: 2000, s: "Aのコンビニinit")下記のEクラスは、スーパークラスの指定イニシャライザを全て定義しているので、スーパークラスのconvenienceイニシャライザを全て継承します。
class E: A { var e: Int
// 指定イニシャライザ init(e: Int, n: Int, s: String) { print("E: init(e: \(e), n: \(n), s: \"\(s)\")") self.e = e super.init(n: n, s: s) }
// 指定イニシャライザのオーバーライド override init(n: Int, s: String) { print("E: オーバーライドinit(n: \(n), s: \(s))") self.e = 20000 super.init(n: n, s: s) }}
var e1 = E(e: 30000, n: 30001, s: "Eの指定init")// E: init(e: 30000, n: 30001, s: "Eの指定init")// A: init(n: 30001, s: "Eの指定init")
var e2 = E(n: 40000, s: "オーバーライドしたEの指定init")// E: オーバーライドinit(n: 40000, s: オーバーライドしたEの指定init)// A: init(n: 40000, s: "オーバーライドしたEの指定init")
var e3 = E(n: 50000) // 継承したconvenienceイニシャライザ// A: convenience init(n: 50000)// E: オーバーライドinit(n: 50000, s: Aのコンビニinit)// A: init(n: 50000, s: "Aのコンビニinit")
var e4 = E() // 継承したconvenienceイニシャライザ// A: convenience init()// A: convenience init(n: 1)// E: オーバーライドinit(n: 1, s: Aのコンビニinit)// A: init(n: 1, s: "Aのコンビニinit")オーバーライドしたinitをconvenienceにすることもできます。
class F: A { var f: Int
// 指定イニシャライザ init(f: Int, n: Int, s: String) { print("F: init(f: \(f), n: \(n), s: \"\(s)\")") self.f = f super.init(n: n, s: s) }
// 指定イニシャライザをオーバーライドしてconvenienceイニシャライザにする override convenience init(n: Int, s: String) { print("F: オーバーライドinit(n: \(n), s: \(s))のconvenience版") self.init(f: 100000, n: n, s: s) // convenienceなので同じクラスのイニシャライザを呼び出す }}
var f1 = F(f: 200000, n: 200001, s: "Fの指定init")// F: init(f: 200000, n: 200001, s: "Fの指定init")// A: init(n: 200001, s: "Fの指定init")
var f2 = F(n: 300000, s: "Fのコンビニinit")// F: オーバーライドinit(n: 300000, s: Fのコンビニinit)のconvenience版// F: init(f: 100000, n: 300000, s: "Fのコンビニinit")// A: init(n: 300000, s: "Fのコンビニinit")サブクラスでイニシャライザの実装を必須とする場合は、required initで必須イニシャライザにします。
class X { var value: Int = 0 required init() { // スーパークラスではデフォルト値のままとする } func printValue() { print(value) }}
class Y: X { required init() { // NOTE: このイニシャライザを無くしても動くため、言語仕様の文書が正しくない? super.init() value = 100 }}
// テストvar x = X()x.printValue()var y = Y()y.printValue()デイニシャライザ
Section titled “デイニシャライザ”C/C++のデストラクタのように、インスタンス解放時の終了処理を行うデイニシャライザ(deinit)をclassで1つだけ定義できます。
@MainActorclass A { static var instanceCount: Int = 0 init() { Self.instanceCount += 1 print("created: count = \(Self.instanceCount)") } @MainActor deinit { Self.instanceCount -= 1 print("deleted: count = \(Self.instanceCount)") }}
var a1: A? = A() // created: count = 1var a2: A? = A() // created: count = 2var a3: A? = A() // created: count = 3a1 = nil // deleted: count = 29. プロトコル (Protocol)
Section titled “9. プロトコル (Protocol)”プロトコルはプロパティやメソッドなどの要件を定義して、クラス・構造体・列挙型にそれらの要件を準拠(conform)させます。
プロトコルは実装を持ちません。拡張を使って実装を追加できます。
プロパティ要件
Section titled “プロパティ要件”- インスタンスプロパティ、型プロパティ(static)を
varで定義する- プロトコルに準拠する型では、
letやclassを使って実装してもよい
- プロトコルに準拠する型では、
var プロパティ名: 型名の後ろに{ get set }または{ get }でプロパティを定義する- プロトコルに準拠する型では、計算プロパティや格納プロパティとして実装できる
- プロトコルの定義が
{ get }であっても、準拠する型では{ get set }としてプロパティを実装できる(プロトコルの要件は満たすので)
// プロトコルの定義protocol MotorVehicle { static var description: String { get } // 説明文 static var fuelEfficiency: Double { get } // 燃料効率 (km/ℓ) static var fuelTankCapacity: Double { get } // 燃料タンクの容量 var fuelLevel: Double { get set } // 燃料残量 mutating func fillUp() // 燃料タンクを満タンにする}
// プロトコルを拡張して実装を追加できるextension MotorVehicle { @discardableResult // 呼び出し元で戻り値を使用しない場合もある関数に与える属性(コンパイラ警告を回避) mutating func drive(distance: Double) -> Bool { let fuel = distance / Self.fuelEfficiency if (fuel > fuelLevel) { print("燃料が\(fuel - fuelLevel)ℓ足りません。") return false } else { fuelLevel -= fuel print("燃料の残量は\(fuelLevel)ℓです。") return true } } mutating func fillUp() { // プロトコル要件のデフォルト実装を提供できる fuelLevel = Self.fuelTankCapacity }}
// オートバイ構造体の定義struct MotorCycle: MotorVehicle { static let description = "オートバイX" static let fuelEfficiency = 40.0 // プロトコルではgetのみだが、構造体ではget/setで実装した static let fuelTankCapacity = 20.0 var fuelLevel = 0.0}
// 自動車構造体も同様に定義できるstruct Car: MotorVehicle { static let description = "自動車Y" static let fuelEfficiency = 15.0 static let fuelTankCapacity = 45.0 var fuelLevel = 0.0
// 以下はCar固有の格納プロパティ var doorIsOpen = false}
// テストvar moto: MotorCycle = MotorCycle() // `let`で定義すると、以降のmutatingメソッドの呼び出しはコンパイルエラーになるmoto.drive(distance: 100)moto.fillUp()moto.drive(distance: 100)メソッド要件・イニシャライザ要件
Section titled “メソッド要件・イニシャライザ要件”- インスタンスメソッド、型メソッド(static)を定義する
- プロトコルに準拠するクラスでは、
staticまたはclassを使って実装してもよい
- プロトコルに準拠するクラスでは、
func 名前(引数) -> 戻り値の型名で定義し、関数本体のブロックは省略する- 引数のデフォルト値は指定できない
- イニシャライザも
initで定義し、本体のブロックは省略する- 失敗可能な
init?でも定義できる - クラスの場合、
required initで実装する
- 失敗可能な
- メソッドの前に
mutatingを付けて、変更可能なメソッド要件であることを示す- 準拠する型がクラスの場合、
mutatingを省いて実装する
- 準拠する型がクラスの場合、
protocol Counter { init(from value: Int) static func getName() -> String var count: Int { get } mutating func countUp()}
// Counterプロトコルに準拠したクラスの例class ClassCounter: Counter { var _counter: Int required init(from value: Int) { // `required`が必要 _counter = value } class func getName() -> String { // `class`でもOK "ClassCounter" } var count: Int { _counter } func countUp() { // `mutating`は不要 _counter += 1 }}
// Counterプロトコルに準拠した構造体の例struct StructCounter: Counter { var _counter: Int init(from value: Int) { _counter = value } static func getName() -> String { "StructCounter" } var count: Int { _counter } mutating func countUp() { _counter += 1 }}
// テストprint(ClassCounter.getName())let classCounter = ClassCounter(from: 0)classCounter.countUp()classCounter.countUp()print(classCounter.count)
print(StructCounter.getName())var structCounter = StructCounter(from: 1)structCounter.countUp()structCounter.countUp()print(structCounter.count)セマンティック要件のみのプロトコル
Section titled “セマンティック要件のみのプロトコル”Swift標準ライブラリに組み込まれている以下のプロトコルは、必須のメソッドやプロパティを持ちません。
- Sendable: 並行処理ドメイン(concurrency domain, タスクやアクターの内側でミュータブルな状態を含んでいるプログラムの部分)間で共有できる値であることを示す
- Copyable: 関数に渡すときにコピーできる値であることを示す
- BitwiseCopyable: ビット単位でコピーできる値であることを示す
なお、これらのプロトコルは、型を定義したときにSwiftが暗黙的に準拠を推論します。暗黙の準拠を抑制する場合は、プロトコル名の前に~を付けます。
struct A1 { // 暗黙的にCopyable, Sendable, BitwiseCopyableへ準拠する let x = 123}struct A2: ~Copyable { // Copyableへの準拠を抑制する let x = 456}
let a1: any Copyable = A1()let a2: any Copyable = A2() // エラー: A2はCopyableに準拠しないプロトコルを型として使う
Section titled “プロトコルを型として使う”変数・定数・関数・メソッド・プロパティなどの型にプロトコルを使えます。
例として、Animalプロトコルとそれに準拠した構造体、及び列挙型を定義します。
protocol Animal { var emoji: Character { get }}
struct Dog: Animal { var emoji: Character = "🐶"}
struct Cat: Animal { var emoji: Character = "🐱"}
enum AnimalType { case dog, cat}some プロトコルと記述するとOpaque型となって、プロトコルに準拠した特定の1つの型に固定されます。
func getAnimal(_ type: AnimalType) -> some Animal { // エラー: 戻り値が特定の型に定まっていない switch type { case .cat: return Cat() default: return Dog() }}some プロトコル型で定義した変数に代入しようとすると、初期化時と同じ型であってもエラーになります。
var animal: some Animal = Dog()animal = Cat() // エラー: cannot assign value of type 'Cat' to type 'some Animal'animal = Dog(emoji: "🐕️") // エラー: cannot assign value of type 'Dog' to type 'some Animal'以下のように型注釈のない変数宣言を行って、戻り値がsome プロトコル型の関数を複数回呼び出すことはOKです。
func getDog(_ emoji: Character? = nil) -> some Animal { if let emoji { return Dog(emoji: emoji) } else { return Dog() }}
var animal = getDog()animal = getDog("🐕️")any プロトコルと記述するとBoxプロトコル型となって、Boxing処理により間接的なレイヤーを挟んでプロトコルに準拠する任意の型を扱えるようになりますが、Boxing処理に伴うパフォーマンスコストが増加します。
func getAnimal(_ type: AnimalType) -> any Animal { switch type { case .cat: return Cat() default: return Dog() }}
var animal: any Animal = getAnimal(.dog)animal = getAnimal(.cat)プロトコルはコレクションに格納される型としても使用できます。
let dogs: [some Animal] = [Dog(), Dog(emoji: "🐕️")]let animals: [any Animal] = [Dog(), Dog(emoji: "🐕️"), Cat(), Cat(emoji: "🐈️")]準拠すると実装が提供されるプロトコル (Equatable, Hashable, Comparable)
Section titled “準拠すると実装が提供されるプロトコル (Equatable, Hashable, Comparable)”以下の種類の独自の型では、EquatableやHashableに準拠するとデフォルトの実装が提供されて、自前で実装する必要がありません。
EquatableやHashableに準拠した型の格納プロパティのみで構成される構造体- 関連値が
EquatableやHashableに準拠する型のみの列挙型 - 関連値のない列挙型
関連値のない列挙型がComparableに準拠すると、デフォルトのComparableの実装が提供されて、自前で実装する必要がありません。
struct Complex: Equatable { var real: Double var imaginary: Double func toString() -> String { if imaginary == 0 { return "\(real)" } else if imaginary < 0 { return "\(real)\(imaginary)i" } return "(\(real)+\(imaginary))i" }}
let a = Complex(real: 1.0, imaginary: 2.0)let b = Complex(real: 3.0, imaginary: -5.0)assert(a != b)
enum Number: Comparable { case one, two, three}assert(Number.one < Number.two)
enum ReverseNumber: Comparable { case three, two, one}assert(ReverseNumber.one > ReverseNumber.two)プロトコルの継承
Section titled “プロトコルの継承”プロトコルは1つ以上の他のプロトコルを継承して、継承する要件に要件を追加できます。
プロトコル継承の一覧にAnyObjectを追加すると、クラス型だけがそのプロトコルに準拠できます。
protocol A { var a: Int { get set }}
protocol B { var b: String { get set }}
protocol C: A, B { var value: (Int, String) { get }}
protocol C2: AnyObject, A, B { var value: (Int, String) { get }}
struct X: C { var a: Int = 0 var b: String = "" var value: (Int, String) { (a, b) }}
struct X2: C2 { // エラー: classではない var a: Int = 0 var b: String = "" var value: (Int, String) { (a, b) }}プロトコル合成
Section titled “プロトコル合成”ProtocolA & ProtocolBと記述すると、複数のプロトコルに準拠していることを示せます。
protocol A { var a: Int { get set }}
protocol B { var b: String { get set }}
struct C: A, B { var a: Int = 0 var b: String = ""}
class D: A, B { var a: Int = 0 var b: String = "" init(a: Int, b: String) { self.a = a self.b = b }}
func getValue(_ param: A & B) -> (Int, String) { return (param.a, param.b)}
let c = getValue(C(a:123, b:"ABC"))let d = getValue(D(a:-123, b:"def"))オプショナルのプロトコル要件
Section titled “オプショナルのプロトコル要件”オプショナルのプロトコル要件としてプロパティやメソッドを定義できます。
ただし@objc属性が必要で、Objective-Cと相互運用するクラスだけが準拠できます。
(Objective-Cと相互運用できないデータ型を使うこともできない)
オプショナルのプロトコル要件であるプロパティやメソッドはオプショナルチェーンで呼び出します。
import Foundation
// NSObjectから派生したクラスは@objc属性の付いたプロトコルで扱うことができる@objcclass Value: NSObject { var number: Int = 0 var text: String = "" init(number: Int = 0, text: String = "") { self.number = number self.text = text }}
@objcprotocol A { @objc optional func getValue() -> Value}
class AImpl: NSObject, A { var value: Value = Value() func getValue() -> Value { value } init(number: Int, text: String) { self.value.number = number self.value.text = text }}
class B { var a: A! init(a: A) { self.a = a }}
// テストvar a: A = AImpl(number: 123, text: "hello")var b = B(a: a)if let number = b.a.getValue?().number { print("number: \(number)")}if let text = b.a.getValue?().text { print("text: \(text)")}10. ジェネリクス
Section titled “10. ジェネリクス”C++のテンプレートやC#・Javaのジェネリクスと同様の機能です。
ジェネリック関数
Section titled “ジェネリック関数”サブスクリプト(subscript)もジェネリックで記述できます。
func swapVars<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
// テストvar (n1, n2): (Int, Int) = (123, 456)swapVars(&n1, &n2)print("n1: \(n1), n2: \(n2)")
var (s1, s2): (String, String) = ("ABC", "DEF")swapVars(&s1, &s2)print("s1: \(s1), s2: \(s2)")ジェネリック型
Section titled “ジェネリック型”Array<Element>, Dictionary<Key, Value>などのコレクションもジェネリック型です。
struct Stack<T> { var items: [T] = [] mutating func push(_ item: T) { items.append(item) } mutating func pop() -> T { if items.isEmpty { fatalError("pop from empty stack") } return items.removeLast() }}
// テストvar stack = Stack<Int>()stack.push(123)stack.push(456)print(stack.pop())print(stack.pop())print(stack.pop()) // エラー: pop from empty stackジェネリック型の拡張を書くときは、型パラメータリストは不要です。
extension Array where Element: Equatable { func countOf(_ item: Element) -> Int { var count = 0 for i in self { if i == item { count += 1 } } return count }}
// テストlet numbers = [1, 2, 1, 2, 3, 2, 3, 4]print(numbers.countOf(2))型パラメータは制約できます。
関数パラメータリストと戻り値の間にwhereを使って制約に条件を追加できます。
func countOf<T: Equatable>(_ item: T, in items: Array<T>) -> Int { var count = 0 for i in items { if i == item { count += 1 } } return count}
// テストlet numbers = [1, 2, 1, 2, 3, 2, 3, 4]print(countOf(2, in: numbers))プロトコルでジェネリック型のような書き方(例えばprotocol A<T>)はできません。
associatedtypeを使ってプロトコル内で使用する型にプレースホルダ名を与えます。
制約を付けるときは associatedtype Item: Equatableなどのように書けます。
さらにwhereで制約に条件を追加できます。
protocol Stackable { associatedtype Item mutating func push(_ item: Item) mutating func pop() -> Item}
struct Stack<T>: Stackable { var items: [T] = [] mutating func push(_ item: T) { items.append(item) } mutating func pop() -> T { if items.isEmpty { fatalError("pop from empty stack") } return items.removeLast() }}11. 拡張
Section titled “11. 拡張”extensionで既存のクラス・構造体・列挙型・プロトコルに機能を追加します。IntやStringなど、元のソースコードにアクセスできない型を拡張することもできます。
Int型に計算プロパティとイニシャライザを追加する例:
import Foundation
extension Int { private static var formatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.groupingSeparator = "," formatter.groupingSize = 3 return formatter }
// 3桁区切りの文字列で整数値を取得する計算プロパティ var formatted: String { Self.formatter.string(from: NSNumber(value: self)) ?? "\(self)" }
// 3桁区切りの文字列から整数値を初期化するイニシャライザ init(formatted: String) { self = Int(Self.formatter.number(from: formatted)?.intValue ?? 0) }}
// テストprint(123456789.formatted)print(Int(formatted: "1,234,567,890"))String型にメソッドとサブスクリプトを追加する例:
extension String { // 文字列を文字の配列に変換するメソッド func toChars() -> [Character] { Array(self) }
// String.Index型を使わずに番号(0, 1, 2, ...)で文字を取得するサブスクリプト subscript(index: Int) -> Character { self[self.index(self.startIndex, offsetBy: index)] }}
// テストlet s = "Hello, World!"print(s.toChars())for i in 0..<s.count { print(s[i], terminator: " ")}これらの他にもextensionを使って、既存の型にネスト型を追加したり、既存の型をプロトコルに準拠させたりできます。
protocol Printable { func print()}
extension String: Printable { // ネスト型の定義 enum PrintMode { case nothing, toUpper, toLower }
// ネスト型を使ったメソッド func print(_ printMode: PrintMode) { switch printMode { case .toUpper: Swift.print(self.uppercased()) case .toLower: Swift.print(self.lowercased()) default: self.print() } }
// プロトコルに準拠 func print() { Swift.print(self) }}
"Abc".print()"Abc".print(String.PrintMode.toLower)"Abc".print(.toUpper) // 型推論で型名を省略型が既に特定のプロトコルの要件を満たしているにも関わらず、そのプロトコルに準拠していることを宣言していない場合、空のextensionでプロトコルに準拠することができます。
struct Person { var firstName: String = "" var lastName: String = "" func print() { Swift.print("\(firstName) \(lastName)") }}
// PersonをPrintableプロトコルに準拠するprotocol Printable { func print()}
extension Person: Printable {}
var me: Printable = Person(firstName: "Taro", lastName: "Yamada")me.print()ジェネリクスの場合、where句を使って、特定の条件下でのみプロトコルの要件を満たすように拡張することができます。
protocol TextRepresentable { var text: String { get }}
extension String: TextRepresentable { var text: String { return self }}
// 条件付きでArray<Element>をTextRepresentableプロトコルに準拠するextension Array: TextRepresentable where Element: TextRepresentable { var text: String { return self.map {$0.text}.joined(separator: ", ") }}
let a = ["a", "b", "c"]print(a.text)
let b = [1, 2, 3]print(b.text) // エラー: intはTextRepresentable準拠ではないため拡張されないextensionでは格納プロパティを追加することができない (以下のエラー)ので、protocolとextensionの組み合わせで抽象クラスを実現することはできないと思います。
Extensions must not contain stored properties12. アクセス制御 (Access Control)
Section titled “12. アクセス制御 (Access Control)”C++やC#、Javaのクラスや構造体のように、publicやprivateなどでアクセス制御できます。
| アクセスレベルの境界 | 説明 |
|---|---|
| モジュール | コード配布の単位。importキーワードで別のモジュールからインポートできるフレームワークまたはアプリケーション。 |
| ソースファイル | モジュール内のSwiftのソースコードファイル。 |
| パッケージ | 1つの単位として開発するモジュールのグループ。Package.swiftファイルでパッケージを定義して、XcodeのPackage Access Identifierでパッケージ名を指定する。 |
openが最も制限が緩く、privateは最も制限の厳しいアクセスレベルです。
デフォルトのアクセスレベルはinternalです。
| アクセスレベル | 説明 |
|---|---|
| public | 定義モジュールの任意のソースファイル、及び、定義モジュールをインポートする別のモジュールのソースファイルでも使用できる。 |
| open | publicと同様だが、クラスとクラスメンバにのみ適用され、モジュール外のコードがサブクラス化やオーバーライドできる。 |
| package | 定義されたパッケージのソースファイル内で使用できる。通常、複数のモジュールで構成されるアプリやフレームワークで使用する。 |
| internal | モジュールの任意のソースファイル内で使用できる。 |
| fileprivate | 定義しているソースファイル内だけで使用できる。 |
| private | その宣言を囲んでいる宣言と、同じファイル内にあるその宣言のextensionだけが使用できる。 |
別のブログ記事に書きましたが、アプリからインポートできるモジュールは、Xcodeのライブラリで作成できます。
13. エラー処理 (Error Handling)
Section titled “13. エラー処理 (Error Handling)”他のプログラミング言語の例外処理(throwやraiseで例外オブジェクトをスローして, try-catchやtry-except構文で例外オブジェクトを捕捉する)と同様ですが、Swiftでは、列挙値でエラーをthrowして、do-catch構文でエラーを捕捉するか、try?またはtry!でエラーハンドリングします。
エラーの定義
Section titled “エラーの定義”Errorプロトコルに準拠した列挙型でエラーを定義します。
enum VendingMachineError: Error { case invalidSelection case insufficientFunds(coinsNeeded: Int) // 関連値でエラーの詳細情報を付加する case outOfStock}エラーを投げる関数・メソッドの定義
Section titled “エラーを投げる関数・メソッドの定義”関数宣言・メソッド宣言の引数の後ろにthrowsを付けます。型付きスローthrows(列挙型名)を使うとthrowで型名を省略できます。
struct Item { var price: Int var count: Int}
class VendingMachine { var inventory = ["キャンデー": Item(price: 100, count: 5)] var coinsDeposited = 0
// throws をつけた関数 func vend(itemNamed name: String) throws { // 型名省略時は`throws(any Error)`と同じ // 1. 商品がない場合 guard let item = inventory[name] else { throw VendingMachineError.invalidSelection }
// 2. 在庫がない場合 guard item.count > 0 else { throw VendingMachineError.outOfStock }
// 3. お金が足りない場合 guard item.price <= coinsDeposited else { throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) }
// 成功時の処理 coinsDeposited -= item.price print("\(name)をどうぞ。") }}エラーの捕捉 (do-catch)
Section titled “エラーの捕捉 (do-catch)”エラーをスローし得る関数・メソッドは、tryを付けて呼び出します。
let vendingMachine = VendingMachine()do { vendingMachine.coinsDeposited = 80 try vendingMachine.vend(itemNamed: "キャンデー")} catch VendingMachineError.invalidSelection { print("商品はありません。")} catch VendingMachineError.insufficientFunds(let coinNeeded) { print("コインが\(coinNeeded)足りません。")} catch VendingMachineError.outOfStock { print("在庫がありません。")} catch { // `error`というローカル定数にエラーがバインドされる print("予期しないエラーが発生しました: \(error)")}try?とtry!
Section titled “try?とtry!”try?は、エラーからオプショナル値へ変換します。
try!は、予めエラーをスローしないと分かっている場合にエラーの伝搬を抑えます。
enum ValidationError : Error { case outOfRange}func validate(_ n: Int) throws(ValidationError) -> Int { if n < 1 || 100 < n { throw .outOfRange } return n}let n1: Int? = try? validate(100)let n2: Int? = try? validate(123)let n3: Int? = try? validate(-100)let n4: Int = try! validate(100)let n5: Int = try! validate(123) // 実行時エラーが発生するdeferによる遅延アクションの指定
Section titled “deferによる遅延アクションの指定”エラーのスロー、return、breakなどに関わらず現在のコードブロックから離れるときのクリーンアップ処理はdefer文で定義でき、下から順に実行されます。
// エラー定義enum FileError: Error { case fileNotFound case ioError}
// ファイルのopen・read・closeを模倣するクラスclass FileSimulation { var fileName: String init(fileName: String) throws(FileError) { print("\(fileName): FileSimulation.init") if !["good.txt", "bad.txt"].contains(fileName) {throw .fileNotFound} self.fileName = fileName } func read() throws(FileError) -> String { print("FileSimulation.read") if fileName == "bad.txt" {throw .ioError} return "\(fileName): Hello, World!" } func close() { print("\(fileName): FileSimulation.close") }}
// ファイルを2つ開いて読み込みを行う関数func processFile() throws { let file1 = try FileSimulation(fileName: "good.txt") defer { file1.close() // 2番目に実行される } let file2 = try FileSimulation(fileName: "bad.txt") defer { file2.close() // 1番目に実行される } let content1 = try file1.read() print("読み込みデータは \(content1) です。") let content2 = try file2.read() print("読み込みデータは \(content2) です。")}
do { try processFile()} catch { print("エラー: \(error)")}
// 実行結果:// good.txt: FileSimulation.init// bad.txt: FileSimulation.init// FileSimulation.read// 読み込みデータは good.txt: Hello, World! です。// FileSimulation.read// bad.txt: FileSimulation.close// good.txt: FileSimulation.close// エラー: ioError14. Swift特有の概念
Section titled “14. Swift特有の概念”オプショナル(Optional)
Section titled “オプショナル(Optional)”値がない状態(nil)を型レベルで管理します。
// オプショナルな変数の定義(?)var optionalString: String? = "Hello" // 初期値を省略するとnilになるoptionalString = nil // OKassert(optionalString == nil) // 比較のときは == nil が使える
// 1. オプショナルバインディング(安全な取り出し)if let optionalString { // `let safeString = optionalString`でもよい print(optionalString)} else { print("値はnilです。")}// 条件式に複数のオプショナルバインディングとブール値をカンマ区切りで書けるif let min = Int("123"), let max = Int("abc"), min < max { print("最小値 \(min) と 最大値 \(max) は適切です。")} else { print("最小値・最大値は適切ではありません。")}
// 2. nil結合演算子(??) (フォールバック値の提供)let message = optionalString ?? "Default"
// 3. 強制アンラップ(!)let possibleNumber = "12345"let convertedNumber = Int(possibleNumber)if convertedNumber != nil { let number = convertedNumber! // nilの場合はランタイムエラーが発生する print(number)}
// 4. 暗黙アンラップオプショナル値var assumedString: String! // `assumedString`の型は`String?`assumedString = "文字列" // オプショナルに一度値が設定された後は必ず値が存在するという場合を想定let forcedString: String = assumedString // 強制アンラップ(`!`)が不要オプショナルチェーン
Section titled “オプショナルチェーン”プロパティ・メソッド・サブスクリプトのオプショナル値がnilの場合、プロパティ・メソッド・サブスクリプトの呼び出しはnilを返します。Objective-Cのnilにメッセージを送信したときの挙動に似ていますが、Swiftでは参照型だけでなく値型のデータにも使えます。
// プロトコルの定義protocol NodeRepresentable { var title: String { get set } var body: String { get set } var parent: (any NodeRepresentable)? { get set } var children: [any NodeRepresentable]? { get set } var description: String { get } subscript(index: Int) -> (any NodeRepresentable)? { get set } func getChild(_ index: Int) -> (any NodeRepresentable)?}
// ノードの定義// NOTE: structでのオプショナルチェーンを試したかったので、// Boxプロトコル型で無理やり実装している。// classでNodeを定義すればプロトコルは不要になる。struct Node: NodeRepresentable { var title: String var body: String var parent: (any NodeRepresentable)? var children: [any NodeRepresentable]?
init(title: String, body: String, children: [Node]? = nil) { self.title = title self.body = body if var children { for i in children.indices { children[i].parent = self } self.children = children } else { self.children = nil } }
var description: String { "[\(title)] \(body)" }
subscript(index: Int) -> (any NodeRepresentable)? { get { // オプショナルチェーンでcontainsメソッドの戻り値(Bool)のオプショナル値を取得している (children?.indices.contains(index) ?? false) ? children?[index] : nil } set { if (children?.indices.contains(index) ?? false), let newValue { children?[index] = newValue } } }
func getChild(_ index: Int) -> (any NodeRepresentable)? { self[index] }}
// 現在日時を取得する関数の定義import Foundationlet formatter = DateFormatter()formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"formatter.timeZone = TimeZone.currentfunc currentDateTime(_ number: Int) -> String { let now = formatter.string(from: Date()) print("No.\(number): 現在時刻を取得しました: " + now) return now}
// nil確認関数func check(_ number: Int, _ value: String?) { print("No.\(number): \(value ?? "nil")")}
// ツリーデータの作成var root: Node = Node(title: "ルート", body: "これはルートです。", children: [ Node(title: "子ノード0", body: "子ノード0です。"), Node(title: "子ノード1", body: "子ノード1です。", children: [ Node(title: "孫ノード1-1", body: "孫ノード1-1です。"), Node(title: "孫ノード1-2", body: "孫ノード1-2です。"), ]), ])
// テスト
check(10000, root[0]?.description)check(10001, root.children?[1].description)check(10011, root.getChild(1)?.description)check(10002, root[2]?.description) // root[2]は存在しない
// オプショナルチェーンが失敗すると、`=`演算子の右辺式は評価されないroot[0]?.body = currentDateTime(20000)root.children?[1].body = currentDateTime(20001)root[2]?.body = currentDateTime(20002) // 右辺式の関数は呼び出されない
// オプショナルチェーンが複数階層になっても、descriptionの戻り値はString?として返されるcheck(30000, root[1]?[0]?.description)check(30001, root[1]?.children?[1].description)check(30011, root.getChild(1)?.getChild(1)?.description)check(30002, root[1]?[2]?.description) // root[1][2]は存在しないcheck(30003, root[2]?.children?[0].description) // root[2]は存在しない算術演算子や比較演算子、文字列の加算演算子などは、概ねCやJava、JavaScriptなどの演算子と同じですが、算術演算ではオーバーフローできなくなっていたり、代入演算子(=)は値を返さないなど、異なっている部分があります。
var a: Int, b: Inta = b = 1 // エラー: b = 1は値を返さない変数が参照しているクラスの同じインスタンスが同一か(===)同一ではないか(!==)を確認します。
class X { var value: Int = 0}let a = X()let b = alet c = X()a === ba !== cオーバーフロー演算子
Section titled “オーバーフロー演算子”Swiftの算術演算子はオーバーフローを発生しないようにしているため、オーバーフローするための演算子(&+, &-, &*)が用意されています。
var n = UInt8.maxn = n + 1 // エラー: オーバーフロー発生n = n &+ 1 // オーバーフロー加算後、`0`になる範囲指定演算子
Section titled “範囲指定演算子”閉範囲演算子(a...b)、半開範囲演算子(a..<b)、片側範囲演算子(a..., ...a, ..<a)があります。
let a = Array(1...5) // [1, 2, 3, 4, 5]let b = Array(0..<5) // [0, 1, 2, 3, 4]let a1 = a[...2] // [1, 2, 3]let a2 = a[2...] // [3, 4, 5]let a3 = a[..<2] // [1, 2]let range = ...5assert(range.contains(0)) // 0は含まれるassert(range.contains(-1)) // -1も含まれるassert(!range.contains(7)) // 7は含まれないアンダースコア _ の役割
Section titled “アンダースコア _ の役割”Pythonと異なり、_ は変数名ではなく**「値を捨てる(Discard)ためのトークン」**です。読み取りはできません。
- 引数ラベルの省略:
func f(_ x: Int) - 戻り値の無視:
_ = function() - ループ変数の無視:
for _ in 0..<3 - タプル分解時の無視:
let (data, _) = result
assertとprecondition
Section titled “assertとprecondition”どちらも条件がfalseの場合に実行時エラーとなりますが、
コンパイルオプション(-Onone, -O, -Ounchecked)に応じて動作が異なります。
-Ouncheckedでコンパイルすると、どちらも条件をチェックしなくなります。
let age = -3assert(age >= 0, "年齢は0以上です。") // デバッグビルド(-Onone)の場合に条件をチェックするprecondition(age >= 0, "年齢は0以上です。") // デバッグビルドとリリースビルド(-O)の場合に条件をチェックする拡張(Extension)
Section titled “拡張(Extension)”既存の型(ソースコードを持たないライブラリの型含む)にメソッドやプロパティを追加できます。
extension Double { var km: Double { return self * 1000.0 } var m: Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1000.0 }}let oneInch = 25.4.mm // ドット構文(`.`)で計算プロパティを使用できるアクター(Actor)
Section titled “アクター(Actor)”アクターは、並行コード間でデータを安全に共有することを可能にする参照型のデータ型です。
クラスと異なる点は、アクターの可変状態(mutable state)にアクセスできるのは一度に1つのタスクだけです。複数タスクが同一アクターとやり取りする場合でも、安全にアクセスできるようにします。
メインアクター(MainActor)
Section titled “メインアクター(MainActor)”メインアクターは、UIで使用されるデータを保護し、画面表示やイベント処理などのUIに関連するコードを順番に実行します。 並行処理を使い始める前は、全てのコードはメインアクター上で実行されます。
関数やクロージャ、および、クラス・構造体・列挙型・プロトコルのメソッド・プロパティが常にメインアクター上でのみ実行されることを要求するには、@MainActor属性を記述します。
// メインアクターで実行される関数の定義@MainActor func show(_ text: String) { /* UI処理 */}
// メインアクターで実行されるクロージャの定義 (クロージャの先頭に記述する)let task = Task { @MainActor /* [キャプチャリスト] */ /* (引数) */ in /* UI処理 */}
// 構造体の全メソッド・全プロパティをメインアクター上で実行させる (クラス・列挙型・プロトコルも同様)@MainActorstruct A { /* UI関連のプロパティとメソッドを定義 */}
// 構造体の特定メソッド・特定プロパティをメインアクター上で実行させる (クラス・列挙型・プロトコルも同様)struct B { @MainActor var values: [Int] = [] let constantText = "Hello world!" @MainActor func show() { /* UI処理 */ }}独自のアクターを作る
Section titled “独自のアクターを作る”独自のアクターはactorで定義します。アクターはクラス同様に参照型です。
// 安全なカウンターのアクターactor SafeCounter { private var _count: Int
init(from value: Int) { _count = value }
var count: Int { _count }
func countUp() { _count += 1 }}
// 安全なカウンターのテストlet safeCounter = SafeCounter(from: 0)let workers = 1000, loop = 1000await withTaskGroup(of: Void.self) { group in for _ in 1...workers { group.addTask { for _ in 1...loop { // アクターの外からメソッドやプロパティにアクセスするときは`await`を使用する await safeCounter.countUp() } } } await group.waitForAll()}let result = await safeCounter.countprint("SafeCounter: \(workers) * \(loop) -> \(result)")クラスで実装した場合、resultはworkers * loopにならない場合があります。
// 安全ではないカウンターのクラスclass UnsafeCounter { private var _count: Int
init(from value: Int) { _count = value }
var count: Int { _count }
func countUp() { _count += 1 }}
// 安全ではないカウンターのテストlet unsafeCounter = UnsafeCounter(from: 0)let workers = 1000, loop = 1000await withTaskGroup(of: Void.self) { group in for _ in 1...workers { group.addTask { for _ in 1...loop { unsafeCounter.countUp() } } } await group.waitForAll()}let result = unsafeCounter.countprint("UnsafeCounter: \(workers) * \(loop) -> \(result)")@globalActorやSendable型については未稿です。
#から始まる自立型マクロと、@から始まる付属型マクロがあります。コンパイル時にマクロを展開してコードを追加します。
// ファイル名を展開するマクロprint(#file)
// ファイルIDを展開するマクロprint(#fileID)
// ファイルパス名を展開するマクロprint(#filePath)
// 行番号・桁番号を展開するマクロprint(#line, #column)
// 関数名を展開するマクロfunc testFunc() { print(#function)}testFunc()
// コンパイル時の警告マクロ、エラーマクロ#warning("コンパイル警告です")#error("コンパイルエラーにします")マクロの定義・実装については未稿です。