Swift の紹介#
Swift は iOS、macOS、watchOS、tvOS アプリを開発するための新しい言語です。
Swift は安全で、高速で、インタラクティブなプログラミング言語です。
Swift はコードプレビュー(playgrounds)をサポートしており、この機能によりプログラマーはアプリケーションをコンパイルして実行することなく Swift コードを実行し、リアルタイムで結果を確認できます。
Swift は現代的なプログラミングパターンを採用することで、多くの一般的なプログラミングエラーを回避します:
- 変数は常に使用前に初期化されます。
- 配列のインデックスが範囲外であるかどうかをチェックします。
- 整数がオーバーフローしているかどうかをチェックします。
- オプショナル値は nil 値を明示的に処理することを保証します。
- メモリは自動的に管理されます。
- エラーハンドリングにより、予期しない障害からの制御の回復が可能です。
基礎部分#
定数と変数#
定数と変数を宣言します。定数と変数は使用前に宣言する必要があり、定数は let を使用して宣言し、変数は var を使用して宣言します。
例:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
// 型注釈
var welcomeMessage: String
コメント#
単一行コメントは二重スラッシュ(//)、複数行コメントは(/* 複数行の */)です。Swift の複数行コメントは他の複数行コメントの中にネストできます。
例:
// これはコメントです
/* これもコメントですが、
複数行です */
/* これは最初の複数行コメントの始まり
/* これは二番目のネストされた複数行コメントです */
これは最初の複数行コメントの終わりです */
セミコロン#
Swift では各文の末尾にセミコロン(;)を使用することは強制されません。
同じ行に複数の独立した文を書く場合は、セミコロンで区切る必要があります。
let cat = "🐱"; print(cat)
// 出力“🐱”
識別子#
識別子は変数、定数、メソッド、関数、列挙型、構造体、クラス、プロトコルなどに指定された名前です。識別子を構成する文字には一定の規則があります。Swift 言語における識別子の命名規則は以下の通りです:
-
大文字と小文字を区別します。Myname と myname は異なる識別子です;
-
識別子の最初の文字はアンダースコア(_)または文字で始めることができますが、数字では始めることはできません;
-
識別子の他の文字はアンダースコア(_)、文字または数字であることができます。
例えば:userName、User_Name、_sys_val、身高などは合法的な識別子ですが、2mail、room#、class は不正な識別子です。
注意:Swift の文字は Unicode エンコーディングを使用しています。Unicode は統一エンコーディング方式であり、中国語、日本語、韓国語などのアジア文字のエンコーディングを含み、チャットツールで使用される絵文字も含まれます。
キーワードを識別子として使用する必要がある場合は、キーワードの前後にバッククォート(`)を追加できます。例えば:
let `class` = "xiaobai"
キーワード#
キーワードは識別子に似た予約された文字列であり、バッククォート(`)で囲まない限り識別子として使用できません。キーワードはコンパイラに特別な意味を持つ事前定義された予約識別子です。一般的なキーワードは以下の 4 種類です。
宣言に関連するキーワード
class deinit enum extension
func import init internal
let operator private protocol
public static struct subscript
typealias var
文に関連するキーワード
break case continue default
do else fallthrough for
if in return switch
where while
式と型のキーワード
as dynamicType false is
nil self Self super
true _COLUMN_ _FILE_ _FUNCTION_
_LINE_
特定の文脈で使用されるキーワード
associativity convenience dynamic didSet
final get infix inout
lazy left mutating none
nonmutating optional override postfix
precedence prefix Protocol required
right set Type unowned
weak willSet
Swift のスペース#
Swift ではスペースの使用に一定の要件があります。
Swift では、演算子は変数や定数の後に直接続けることはできません。例えば、以下のコードはエラーになります:
let a= 1 + 2
エラーメッセージは:
error: prefix/postfix '=' is reserved
これは、等号が前または後に直接続くこの使用法が予約されていることを意味します。
以下のコードもエラーになります(スペースに注意してください):
let a = 1+ 2
エラーメッセージは:
error: consecutive statements on a line must be separated by ';'
これは、Swift が 1 + という文が終了したと見なしており、2 が次の文であると見なしているためです。
このように書けばエラーは発生しません:
let a = 1 + 2; // コーディング規範ではこの書き方を推奨します
let b = 3+4 // これもOKです
整数、浮動小数点数#
統一して Int を使用することで、コードの再利用性が向上し、異なる型の数字間の変換を避け、数字の型推論に一致します。
例:
let minValue = UInt8.min // minValueは0で、UInt8型です
let maxValue = UInt8.max // maxValueは255で、UInt8型です
型安全と型推論#
Swift は型安全な言語であり、これは Swift が値の型を明確に知ることを可能にします。
明示的に型を指定しない場合、Swift は型推論を使用して適切な型を選択します。(int、double)。
例:
let meaningOfLife = 42
// meaningOfLifeはInt型として推測されます
let pi = 3.14159
// piはDouble型として推測されます
数値リテラル、数値型の型変換#
例:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二進数の17
let octalInteger = 0o21 // 八進数の17
let hexadecimalInteger = 0x11 // 十六進数の17
型エイリアス#
型エイリアス(type aliases)は、既存の型に別の名前を定義することです。typealias キーワードを使用して型エイリアスを定義できます。
例:
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFoundは現在0です
ブール値#
例:
let orangesAreOrange = true
let turnipsAreDelicious = false
タプル#
タプル(tuples)は複数の値を組み合わせて複合値を作成します。タプル内の値は任意の型であり、同じ型である必要はありません。
例:
let http404Error = (404, "Not Found")
// http404Errorの型は(Int, String)で、値は(404, "Not Found")です
オプショナル型#
オプショナル型(optionals)を使用して、値が欠落する可能性のある状況を処理します。オプショナル型は 2 つの可能性を示します:値がある場合、オプショナル型を解析してこの値にアクセスできます、または全く値がない場合です。
例:
var serverResponseCode: Int? = 404
// serverResponseCodeはオプショナルなInt値404を含みます
serverResponseCode = nil
// serverResponseCodeは現在値を含みません
エラーハンドリング#
エラーハンドリングは、プログラムの実行中に遭遇する可能性のあるエラー条件に対処します。
例:
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
アサーションと前提条件#
アサーションと前提条件は、実行時に行われるチェックです。
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// age < 0なので、アサーションがトリガーされます
基本演算子#
Swiftはほとんどの標準C言語の演算子をサポートしており、C言語にはない範囲演算子(例えばa..<bまたはa...b)も提供しています。
代入演算子、算術演算子、複合代入演算子、比較演算子、三項演算子、空合演算子、範囲演算子、論理演算子
演算子は一元、二元、三元演算子に分けられます。
閉区間演算子(a...b)は、a から b(a と b を含む)までのすべての値を含む範囲を定義します。
半開区間演算子(a..<b)は、a から b までの範囲を定義しますが、b は含まれません。
閉区間演算子には、片側が無限に延びる範囲を表現する別の表現形式があります(a...、...b)。
例:
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack
文字列と文字#
文字列リテラル、文字列補間、文字数の計算、文字列のアクセスと変更、部分文字列、文字列の比較
空の文字列を初期化し、文字列の可変性、文字列は値型であり、文字列と文字を結合する(+、+=)。
文字を使用して、for-in ループを使用して文字列を反復処理し、文字列内の各文字の値を取得できます。
文字列補間は新しい文字列を構築する方法であり、定数、変数、リテラル、式を含めることができます。既存の文字列に定数、変数、リテラル、式を挿入して、より長い文字列を形成できます。
Swift はテキスト値を比較するための 3 つの方法を提供します:文字列の文字の等価性、接頭辞の等価性、および接尾辞の等価性。
例:
// 複数行文字列リテラル
let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""
// 以下の2つの文字列は実際には同じです
let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""
// 文字列補間
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// messageは"3 times 2.5 is 7.5"です
// 文字数の計算
var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// 出力“the number of characters in cafe is 4”
var emptyString = "" // 空の文字列リテラル
var anotherEmptyString = String() // 初期化メソッド
// 2つの文字列は空であり、等価です。
let catCharacters: [Character] = ["C", "a", "t", "!"]
let catString = String(catCharacters)
print(catString)
// 出力:“Cat!”
コレクション型#
Swift 言語は、配列(Array)、集合(Set)、辞書(Dictionary)の 3 つの基本的なコレクション型を提供して、コレクションデータを保存します。配列は順序付きデータの集まりです。集合は順序なしで重複しないデータの集まりです。辞書は順序なしのキーと値のペアの集まりです。
コレクションの可変性、配列(Arrays)、集合(Sets)、コレクション操作、辞書
配列は同じ型の複数の値を順序付きリストで保存します。同じ値は配列の異なる位置に複数回出現できます。
集合は同じ型の値を保存し、順序が決まっていない場合に使用されます。集合の要素の順序が重要でない場合や、各要素が一度だけ出現することを保証したい場合は、配列の代わりに集合を使用できます。
集合操作は、2 つの集合を組み合わせたり、2 つの集合の共通要素を判断したり、2 つの集合が完全に含まれているか、一部含まれているか、交差していないかを判断するなど、基本的な操作を効率的に実行できます。
辞書は順序のないコレクションであり、キーと値の関係を保存します。すべてのキーの値は同じ型である必要があり、すべての値の型も同じである必要があります。各値(value)は一意のキー(key)に関連付けられ、キーは辞書内のこの値データの識別子です。
例:
// コレクション
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 出力“someInts is of type [Int] with 0 items.”
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoublesは[Double]型の配列で、[0.0, 0.0, 0.0]に等しいです。
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoublesは[Double]として推測され、[2.5, 2.5, 2.5]に等しいです。
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoublesは[Double]として推測され、[0.0, 0.0, 0.0, 2.5, 2.5, 2.5]に等しいです。
// enumerated()メソッドで配列を反復処理
var shoppingList: [String] = ["Eggs", "Milk"]
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
制御フロー#
For-In ループ、While ループ(Repeat-While)、条件文、制御転送文、早期退出(guard)、API の可用性の検出
if 文と同様に、guard の実行は式のブール値に依存します。guard 文を使用して、条件が真である場合に guard 文の後のコードを実行するように要求できます。if 文とは異なり、guard 文には常に else 句があり、条件が真でない場合は else 句内のコードが実行されます。
Swift は API の可用性をチェックするための組み込みサポートを提供しており、コンパイラは SDK 内の可用情報を使用して、プロジェクトで指定されたデプロイメントターゲットで使用されるすべての API が利用可能かどうかを検証します。使用できない API を使用しようとすると、Swift はコンパイル時にエラーを報告します。
例:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// repeat-whileループの一般的な形式
repeat {
statements
} while condition
// 早期退出
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])
// 出力“Hello John!”
// 出力“I hope the weather is nice near you。”
greet(person: ["name": "Jane", "location": "Cupertino"])
// 出力“Hello Jane!”
// 出力“I hope the weather is nice in Cupertino。”
関数#
関数の定義と呼び出し、関数パラメータと戻り値、関数パラメータラベルとパラメータ名、関数型、ネストされた関数
オプショナルタプル戻り値型。
入力出力パラメータを定義する場合は、パラメータ定義の前に inout キーワードを追加します。
例:
// 関数
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 出力“Hello Bill! Glad you could visit from Cupertino。”
// オプショナルタプル戻り値型
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
// 暗黙的に戻る関数
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// 出力 "Hello, Dave!"
// パラメータラベル
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 出力“Hello Bill! Glad you could visit from Cupertino。”
クロージャ#
クロージャは自己完結型の関数コードブロックであり、コード内で渡されて使用されます。一部のプログラミング言語の匿名関数(ラムダ)に似ています。
クロージャ式、トレイリングクロージャ、値のキャプチャ、クロージャは参照型、逃げるクロージャ(@escaping)、自動クロージャ
長いクロージャ式を関数に渡す最後のパラメータとして使用する必要がある場合、そのクロージャをトレイリングクロージャの形式に置き換えることが便利です。
クロージャは、その定義されたコンテキスト内で定数または変数をキャプチャできます。これらの定数や変数の元のスコープが存在しなくなっても、クロージャはこれらの値を参照して変更できます。
例:
// クロージャ式の構文
{ (parameters) -> return type in
statements
}
// トレイリングクロージャ
let digitNames = [
0: "ゼロ", 1: "一", 2: "二", 3: "三", 4: "四",
5: "五", 6: "六", 7: "七", 8: "八", 9: "九"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings定数は文字列型の配列として推測され、値は["一六", "五八", "五一零"]です。
// 値のキャプチャ
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
// 自動クロージャ、遅延評価
var customersInLine = ["クリス", "アレックス", "エワ", "バリー", "ダニエラ"]
print(customersInLine.count)
// 出力“5”
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 出力“5”
print("Now serving \(customerProvider())!")
// 出力 "Now serving クリス!"
print(customersInLine.count)
// 出力“4”
列挙型#
enum キーワードを使用して列挙型を作成し、その全体の定義を一対の波括弧内に置きます。
列挙型の構文、switch 文を使用して列挙値をマッチング、列挙メンバーの反復、関連値、原始値(デフォルト値)、再帰列挙型(indirect)
Swift の列挙型を定義して、任意の型の関連値を保存できます。各列挙メンバーの関連値の型は異なる場合があります。
例:
// 列挙型の構文
enum SomeEnumeration {
// 列挙の定義はここに置きます
}
enum CompassPoint {
case north
case south
case east
case west
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("ほとんど無害です")
default:
print("人間にとって安全な場所ではありません")
// 出力“ほとんど無害です”
// 関連値
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QRコード: \(productCode).")
// 出力“QRコード: ABCDEFGHIJKLMNOP.”
// 再帰列挙型
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
// (5 + 4) * 2
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// 出力“18”
構造体とクラス#
構造体とクラスの比較、構造体と列挙型は値型、クラスは参照型
構造体とクラスは一般的で柔軟な構造として、コードを構築するための基礎となります。定数、変数、関数を定義する構文を使用して、構造体とクラスにプロパティを定義し、メソッドを追加できます。
例:
// クラスと構造体
struct SomeStructure {
// ここに構造体を定義します
}
class SomeClass {
// ここにクラスを定義します
}
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
プロパティ#
ストレージプロパティ、計算プロパティ、プロパティオブザーバ、プロパティラッパー、グローバル変数とローカル変数、型プロパティ(static)
プロパティは特定のクラス、構造体、または列挙型に値を関連付けます。ストレージプロパティは定数と変数をインスタンスの一部として保存し、計算プロパティは値を直接計算します(保存するのではなく)。計算プロパティはクラス、構造体、列挙型で使用できますが、ストレージプロパティはクラスと構造体でのみ使用できます。
プロパティオブザーバはプロパティ値の変化を監視し、プロパティが値を設定されるたびにプロパティオブザーバが呼び出されます。新しい値と現在の値が同じ場合でも例外ではありません。
- willSet は新しい値が設定される前に呼び出されます
- didSet は新しい値が設定された後に呼び出されます
プロパティラッパーは、プロパティがどのように保存されるかを管理するコードとプロパティを定義するコードの間に分離層を追加します。
型プロパティもドット演算子を使用してアクセスされます。ただし、型プロパティはインスタンスではなく、型自体を介してアクセスされます。
例:
// プロパティ
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size() //ストレージプロパティ
var center: Point { //計算型プロパティ
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 出力“square.origin is now at (10.0, 10.0)”
// プロパティラッパー
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
メソッド#
インスタンスメソッド(Instance Methods)、型メソッド(static)
メソッドは特定の型に関連付けられた関数です。
クラス、構造体、列挙型はインスタンスメソッドを定義できます。インスタンスメソッドは特定の型のインスタンスに具体的なタスクと機能をカプセル化します。
クラス、構造体、列挙型は型メソッドも定義できます。型メソッドは型自体に関連付けられています。
例:
// メソッド
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
添字#
添字はクラス、構造体、列挙型に定義でき、コレクション、リスト、またはシーケンス内の要素にアクセスするためのショートカットです。
添字構文(subscript)、添字の使用、添字オプション、型添字(static)
subscript(index: Int) -> Int {
get {
// 適切なInt型の値を返します
}
set(newValue) {
// 適切な代入操作を実行します
}
}
// 例
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 出力“six times three is 18”
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
// 型添字
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
継承#
基底クラスの定義、サブクラスの生成、オーバーライド(override)、オーバーライドを防ぐ(final)
他のクラスを継承しないクラスは基底クラスと呼ばれます。
例:
// 継承
class SomeClass: SomeSuperclass {
// ここはサブクラスの定義です
}
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "時速\(currentSpeed)マイルで移動中"
}
func makeNoise() {
// 何もしない——車両は必ずしも音を出すわけではありません
}
}
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " ギア\(gear)で"
}
}
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
構造プロセス#
構造プロセスは、クラス、構造体、または列挙型のインスタンスを使用する前の準備プロセスです。
ストレージプロパティの初期代入、カスタム構造プロセス、デフォルト構造体、値型の構造体代理、クラスの継承と構造プロセス、失敗する可能性のある構造体、必須構造体(required)
構造体は他の構造体を呼び出すことでインスタンスの部分構造プロセスを完了できます。このプロセスは構造体代理と呼ばれ、複数の構造体間のコードの重複を避けることができます。
Swift はクラス型に 2 つの構造体を提供して、インスタンス内のすべてのストレージ型プロパティが初期値を取得できるようにします。これらは指定構造体と便利構造体と呼ばれます。
クラス、構造体、または列挙型の定義に 1 つ以上の失敗する可能性のある構造体を追加できます。その構文は init キーワードの後に疑問符(init?)を追加することです。
必須構造体は、クラスの構造体の前に required 修飾子を追加して、すべてのサブクラスがその構造体を実装する必要があることを示します。
例:
// 構造プロセス
init() {
// ここで構造プロセスを実行します
}
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("デフォルトの温度は\(f.temperature)°Fです")
// 出力“デフォルトの温度は32.0°Fです”
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
解構プロセス#
解構プロセスはクラス型にのみ適用され、クラスのインスタンスが解放される前に解構プロセスが即座に呼び出されます。解構プロセスはキーワード deinit で示され、構造プロセスは init で示されます。
Swift は、もはや必要ないインスタンスを自動的に解放してリソースを解放します。
例:
// 解構プロセス
deinit {
// 解構プロセスを実行します
}
class Bank {
static var coinsInBank = 10_000
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
deinit {
Bank.receive(coins: coinsInPurse)
}
}
オプショナルチェーン呼び出し#
オプショナルチェーン呼び出しは、現在の値が nil である可能性のあるオプショナル値のプロパティ、メソッド、および添字を要求して呼び出す方法です。
呼び出したいプロパティ、メソッド、または添字のオプショナル値の後に疑問符(?)を置くことでオプショナルチェーンを定義できます。オプショナル値の後に感嘆符(!)を置いて強制的に展開することもできます。主な違いは、オプショナル値が空である場合、オプショナルチェーン呼び出しは単に失敗しますが、強制展開は実行時エラーを引き起こします。
例:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
let roomCount = john.residence!.numberOfRooms
// これは実行時エラーを引き起こします
if let roomCount = john.residence?.numberOfRooms {
print("Johnの住居には\(roomCount)部屋があります。")
} else {
print("部屋の数を取得できません。")
// 出力“部屋の数を取得できません。”
john.residence = Residence()
if let roomCount = john.residence?.numberOfRooms {
print("Johnの住居には\(roomCount)部屋があります。")
} else {
print("部屋の数を取得できません。")
// 出力“Johnの住居には1部屋あります。”
エラーハンドリング#
エラーハンドリング(Error handling)は、エラーに応答し、エラーから回復するプロセスです。Swift は実行時にスロー、キャッチ、伝播、および操作可能なエラー(recoverable errors)を一級サポートします。
エラーを表す、エラーを処理する、クリーンアップ操作を指定する
Swift では、エラーは Error プロトコルに準拠した型の値で表されます。
Swift にはエラーを処理する 4 つの方法があります。関数がスローするエラーをこの関数を呼び出すコードに伝播させる(throws)、do-catch 文を使用してエラーを処理する、エラーをオプショナル型として処理する(try?)、またはこのエラーが発生しないと断言する(try!)。
defer 文は、現在のスコープが終了する前にコードの実行を遅らせます。
例:
// エラーハンドリング
enum VendingMachineError: Error {
case invalidSelection //無効な選択
case insufficientFunds(coinsNeeded: Int) //資金不足
case outOfStock //在庫切れ
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("成功!おいしい。")
} catch VendingMachineError.invalidSelection {
print("無効な選択です。")
} catch VendingMachineError.outOfStock {
print("在庫切れです。")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("資金が不足しています。追加で\(coinsNeeded)コインを挿入してください。")
} catch {
print("予期しないエラー: \(error).")
}
// 出力“資金が不足しています。追加で2コインを挿入してください。”
// クリーンアップ操作を指定する
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// ファイルを処理します。
}
// close(file)はここで呼び出されます。スコープの最後に。
}
}
型変換#
型変換は Swift で is および as 演算子を使用して実現されます。これらの演算子は、値の型をチェックしたり、その型を変換したりするための簡潔で明確な方法を提供します。
型変換のためのクラス階層を定義する、型をチェックする(is)、ダウンキャストする(as? または as!)、Any および AnyObject の型変換
型変換はクラスとサブクラスの階層に適用でき、特定のクラスインスタンスの型をチェックし、そのクラスインスタンスの型をこの階層内の他の型に変換できます。
Swift は不確定な型に対して 2 つの特別な型エイリアスを提供します:
-
Any は任意の型を表すことができ、関数型も含まれます。
-
AnyObject は任意のクラス型のインスタンスを表すことができます。
例:
// 型変換
// 基底クラスMediaItem
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library = [
Movie(name: "カサブランカ", director: "ミケル・カーティズ"),
Song(name: "ブルースエードシューズ", artist: "エルビス・プレスリー"),
Movie(name: "市民ケーン", director: "オーソン・ウェルズ"),
Song(name: "唯一無二", artist: "チェズニー・ホークス"),
Song(name: "絶対にあなたを手放さない", artist: "リック・アスティリー")
]
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("メディアライブラリには\(movieCount)本の映画と\(songCount)曲があります")
// 出力“メディアライブラリには2本の映画と3曲があります”
for item in library {
if let movie = item as? Movie {
print("映画: \(movie.name), 監督: \(movie.director)")
} else if let song = item as? Song {
print("曲: \(song.name), アーティスト: \(song.artist)")
}
}
// 映画: カサブランカ, 監督: ミケル・カーティズ
// 曲: ブルースエードシューズ, アーティスト: エルビス・プレスリー
// 映画: 市民ケーン, 監督: オーソン・ウェルズ
// 曲: 唯一無二, アーティスト: チェズニー・ホークス
// 曲: 絶対にあなたを手放さない, アーティスト: リック・アスティリー
ネストされた型#
Swift はネストされた型を定義することを許可しており、サポートされている型内にネストされた列挙型、クラス、構造体を定義できます。
ネストされた型の実践、ネストされた型を参照する
型内に別の型をネストするには、ネストされた型の定義をその外部型の {} 内に記述し、必要に応じて多層ネストを定義できます。
例:
// ネストされた型
struct BlackjackCard {
// ネストされたSuit列挙型
enum Suit: Character {
case spades = "1", hearts = "2", diamonds = "3", clubs = "4"
}
// ネストされたRank列挙型
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
// BlackjackCardのプロパティとメソッド
let rank: Rank, suit: Suit
var description: String {
var output = "スートは\(suit.rawValue)、"
output += "値は\(rank.values.first)"
if let second = rank.values.second {
output += " または\(second)"
}
return output
}
}
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 出力“theAceOfSpades: スートは1、値は1または11”
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// 2
拡張#
拡張は、既存のクラス、構造体、列挙型、およびプロトコルに新しい機能を追加できます。
拡張の構文、計算型プロパティ、構造体、メソッド、添字、ネストされた型
Swift の拡張は:
-
計算型インスタンスプロパティと計算型クラスプロパティを追加できます
-
インスタンスメソッドとクラスメソッドを定義できます
-
新しい構造体を提供できます
-
添字を定義できます
-
新しいネストされた型を定義して使用できます
-
既存の型をプロトコルに準拠させることができます
拡張の構文:
extension SomeType {
// ここにSomeTypeに新しい機能を追加します
}
拡張は既存の型に計算型インスタンスプロパティと計算型クラスプロパティを追加できます。
拡張は既存の型に新しい構造体を追加できます。
拡張は既存の型に新しいインスタンスメソッドとクラスメソッドを追加できます。
拡張は既存の型に新しい添字を追加できます。
拡張は既存のクラス、構造体、および列挙型に新しいネストされた型を追加できます。
例:
// 拡張の構文
extension SomeType {
// ここにSomeTypeに新しい機能を追加します
}
// 1つ以上のプロトコルを追加
extension SomeType: SomeProtocol, AnotherProtocol {
// プロトコルに必要な実装はここに書きます
}
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 3)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRectのoriginは(2.5, 2.5)で、サイズは(3.0, 3.0)です。
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("こんにちは!")
}
// こんにちは!
// こんにちは!
// こんにちは!
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someIntは現在9です
プロトコル#
プロトコルは、特定のタスクまたは機能を実装するために必要なメソッド、プロパティ、およびその他の要件を定義する青写真です。
クラス、構造体、または列挙型はプロトコルに準拠し、プロトコルで定義された要件を具体的に実装できます。
プロトコルの構文、プロパティ要件、メソッド要件、変異メソッド要件、構造体要件、プロトコルを型として使用する、委任、プロトコル型のコレクション、プロトコルの継承、クラス専用のプロトコル、プロトコル合成、プロトコルの整合性をチェックする、オプショナルなプロトコル要件、プロトコル拡張、
プロトコルの構文
protocol SomeProtocol {
// ここはプロトコルの定義部分です
}
プロトコルは、プロトコルに準拠する型が特定の名前と型のインスタンスプロパティまたは型プロパティを提供することを要求できます。
プロトコルは、プロトコルに準拠する型が特定のインスタンスメソッドまたはクラスメソッドを実装することを要求できます。
値型(構造体や列挙型)のインスタンスメソッドでは、mutating キーワードをメソッドの接頭辞として func キーワードの前に書き、メソッド内でそのメソッドに属するインスタンスやインスタンスの任意のプロパティの値を変更できることを示します。
委任は、クラスまたは構造体がそれらが責任を持つ必要がある機能を他の型のインスタンスに委任することを許可する設計パターンです。
例:
// プロトコルの構文
protocol SomeProtocol {
// ここはプロトコルの定義部分です
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// ここは構造体の定義部分です
}
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// ここはクラスの定義部分です
}
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "ジョン・アップルシード")
// john.fullNameは"ジョン・アップルシード"です
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "エンタープライズ", prefix: "USS")
// ncc1701.fullNameは"USS エンタープライズ"です
ジェネリック#
ジェネリックコードを使用すると、カスタムのニーズに応じて、任意の型に適用できる柔軟で再利用可能な関数や型を作成できます。
重複したコードを書くことを避け、コードの意図を明確に抽象的な方法で表現できます。
ジェネリック関数、型パラメータ、命名型パラメータ、ジェネリック型、ジェネリック拡張、型制約、関連型
例:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someIntは現在107、anotherIntは現在3です
var someString = "こんにちは"
var anotherString = "世界"
swapTwoValues(&someString, &anotherString)
// someStringは現在“世界”、anotherStringは現在“こんにちは”です
不透明な型#
不透明な戻り値型を持つ関数やメソッドは、戻り値の型情報を隠します。
関数は具体的な型を戻り値型として提供するのではなく、サポートされているプロトコルに基づいて戻り値を説明します。
不透明な型が解決する問題、不透明な型を返す、不透明な型とプロトコル型の違い
モジュールと呼び出しコード間の関係を処理する際に、型情報を隠すことは非常に便利です。戻り値の基になるデータ型は依然としてプライベートに保つことができます。
不透明な型はジェネリックとは逆です。不透明な型は、関数の実装時に呼び出しコードに依存しない戻り値型を選択することを許可します。
関数内に不透明な型を返す場所が複数ある場合、すべての可能な戻り値は同じ型でなければなりません。不透明な型を返すこととプロトコル型を返すことの主な違いは、型の一貫性を保証する必要があるかどうかです。
不透明な型は具体的な型にのみ対応し、関数呼び出し者はその型が何であるかを知ることはできません。プロトコル型は、同じプロトコルに準拠する複数の型に対応できます。
例:
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result = [String]()
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
自動参照カウント#
Swift は自動参照カウント(ARC)メカニズムを使用して、アプリケーションのメモリを追跡および管理します。
2 つのクラスインスタンスが互いに強い参照を持ち、互いに存在し続ける場合、これは循環強参照と呼ばれます。
Swift は、クラスのプロパティを使用する際に循環強参照の問題を解決するために、弱参照(weak reference)と無主参照(unowned reference)の 2 つの方法を提供します。
-
プロパティまたは変数を宣言する際に、前に weak キーワードを追加して、これは弱参照であることを示します。
-
プロパティまたは変数を宣言する際に、前に unowned キーワードを追加して、これは無主参照であることを示します。
例:
// 自動参照カウントの実践
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name)が初期化されています")
}
deinit {
print("\(name)が解放されています")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "ジョン・アップルシード")
// 出力“ジョン・アップルシードが初期化されています”
reference2 = reference1
reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
// 出力“ジョン・アップルシードが解放されています”
// 循環強参照
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name)が解放されています") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("アパート\(unit)が解放されています") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "ジョン・アップルシード")
unit4A = Apartment(unit: "4A")
john = nil
unit4A = nil
// 弱参照
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name)が解放されています") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("アパート\(unit)が解放されています") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "ジョン・アップルシード")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 出力“ジョン・アップルシードが解放されています”
メモリ安全性#
デフォルトでは、Swift はコード内の不安全な動作を防ぎます。
メモリアクセスの競合を理解する、In-Out パラメータのアクセス競合、メソッド内の self のアクセス競合、プロパティのアクセス競合
例:
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // 正常
balance(&playerOneScore, &playerOneScore)
// エラー:playerOneScoreのアクセス競合
アクセス制御#
アクセス制御は、他のソースファイルやモジュールがコードにアクセスすることを制限できます。
-
open および public レベルは、エンティティが同じモジュールのソースファイル内のすべてのエンティティからアクセスでき、モジュール外でもそのモジュールをインポートすることでソースファイル内のすべてのエンティティにアクセスできます。通常、フレームワークの外部インターフェースを指定するために open または public レベルを使用します。
-
internal レベルは、エンティティが同じモジュールのソースファイル内の任意のエンティティからアクセスできるが、モジュール外のエンティティからはアクセスできません。通常、アプリケーションやフレームワーク内でのみ使用されるインターフェースは internal レベルに設定できます。
-
fileprivate は、エンティティがその定義されたファイル内でのみアクセスできることを制限します。機能の部分的な実装の詳細がファイル内でのみ使用される場合、fileprivate を使用して隠すことができます。
-
private は、エンティティがその定義されたスコープおよび同じファイル内の拡張からアクセスできることを制限します。機能の部分的な詳細が現在のスコープ内でのみ使用される場合、private を使用して隠すことができます。
open は最高のアクセスレベル(制限が最も少ない)、private は最低のアクセスレベル(制限が最も多い)です。
open はクラスとクラスのメンバーにのみ作用し、public との違いは、open で指定されたクラスとメンバーはモジュール外で継承およびオーバーライドできることです。
例:
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
class SomeInternalClass {} // 暗黙的にinternal
var someInternalConstant = 0 // 暗黙的にinternal
public class SomePublicClass { // 明示的なpublicクラス
public var somePublicProperty = 0 // 明示的なpublicクラスメンバー
var someInternalProperty = 0 // 暗黙的にinternalクラスメンバー
fileprivate func someFilePrivateMethod() {} // 明示的なfileprivateクラスメンバー
private func somePrivateMethod() {} // 明示的なprivateクラスメンバー
}
class SomeInternalClass { // 暗黙的にinternalクラス
var someInternalProperty = 0 // 暗黙的にinternalクラスメンバー
fileprivate func someFilePrivateMethod() {} // 明示的なfileprivateクラスメンバー
private func somePrivateMethod() {} // 明示的なprivateクラスメンバー
}
fileprivate class SomeFilePrivateClass { // 明示的なfileprivateクラス
func someFilePrivateMethod() {} // 暗黙的にfileprivateクラスメンバー
private func somePrivateMethod() {} // 明示的なprivateクラスメンバー
}
private class SomePrivateClass { // 明示的なprivateクラス
func somePrivateMethod() {} // 暗黙的にprivateクラスメンバー
}
高度な演算子#
Swift は、数値に対して複雑な演算を行うためのいくつかの高度な演算子も提供しています。これらにはビット演算子やシフト演算子が含まれます。
ビット演算子、オーバーフロー演算子、優先順位と結合性、演算子関数、カスタム演算子
例:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等しい0b11110000
var potentialOverflow = Int16.max
// potentialOverflowの値は32767で、これはInt16が収容できる最大整数です
potentialOverflow += 1
// ここでエラーが発生します
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVectorは新しいVector2Dインスタンスで、値は(5.0, 5.0)です