コレクションの種類

Swiftは、配列、セット、および辞書の3つのコレクションタイプを提供します。配列は、順序付けられた値のコレクションです。セットは、一意の値の順序付けられていないコレクションです。辞書は、キーと値の関連付けの順序付けられていないコレクションです。

../_images/CollectionTypes_intro_2x.png

コレクションの可変性

配列、セット、または辞書を作成し、それを変数に割り当てると、作成されるコレクションは変更可能になります。コレクションを変更する必要がない場合に、let による不変コレクションを作成することを勧める。そうすることで、コードを推論しやすくなり、コンパイラーがパフォーマンスを最適化できる。

配列

配列の順序付きリスト内の同じタイプの値を格納する。NSArrayに橋渡しされている。

配列型の省略構文

Array<Element> ここで、Elementは、配列に格納できる値のタイプ。

省略形:[Element]

空の配列の作成

初期化構文を使用して、特定のタイプの空の配列を作成できる。

var someInts = [Int]()

デフォルト値を持つ配列の作成

すべての値が同じデフォルト値で、特定のサイズの配列の作成。

var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]

2つの配列を一緒に追加して配列を作成する

加算演算子(+)を使用して、互換性のあるタイプの2つの既存の配列を加算することにより、新しい配列を作成できます。新しい配列のタイプは、一緒に追加する2つの配列のタイプから推測されます。

var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

配列リテラルを使用した配列の作成

また、配列リテラルで配列を初期化することもできます。これは、1つ以上の値を配列コレクションとして書き込むための簡単な方法です。配列リテラルは、コンマで区切られた値のリストとして記述され、角括弧のペアで囲まれます。

[value 1, value 2, value 3]

// 例
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items

// 型推論により、次のように記述できる。
var shoppingList = ["Eggs", "Milk"]

配列へのアクセスと変更

メソッドとプロパティを介して、または添字構文を使用して、配列にアクセスして変更します。

// 配列内のアイテム数には、読み取り専用countプロパティ
print("The shopping list contains \(shoppingList.count) items.")

// 配列が空かどうかは、isEmpty プロパティ
if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}

// 配列の最後に新しいアイテムを追加
shoppingList.append("Flour")

// 追加の代入演算子 += 1つ以上の互換性のあるアイテムの配列を追加
shoppingList += ["Baking Powder"]
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]

// インデックス
// 配列の最初の項目のインデックスは0
var firstItem = shoppingList[0]

// 特定のインデックスにある既存の値を変更
shoppingList[0] = "Six eggs"

// 次はランタイムエラー
shoppingList[shoppingList.count] = "Salt"

// 配列の長さが置換する長さと異なっていても、一度に変更できる。
// 次の場合は、"Baking Powder", "ChocolateSpread", "Cheese", "Butter", "Bananas", "Apples" となる。
shoppingList[4...6] = ["Bananas", "Apples"]

// 配列の指定したインデックスにアイテムを挿入
shoppingList.insert("Maple Syrup", at: 0)

// 指定されたインデックスにある項目を削除し、削除された項目を返す
let mapleSyrup = shoppingList.remove(at: 0)

// 最初のアイテム
firstItem = shoppingList[0]

// 配列から最後の項目を削除する場合
// 配列のcountプロパティを確認する必要がない
let apples = shoppingList.removeLast()

配列の反復

for– inループを使用して、配列内の値のセット全体を反復処理できます。

for item in shoppingList {
    print(item)
}

// 各アイテムの整数インデックスとその値が必要な場合はenumerated()
for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)")
}

セット

一意の値の順序付けられていないコレクション。

アイテムの順序が重要ではない場合、またはアイテムが1回だけ表示されるようにする必要がある場合は、配列の代わりにセットを使用できます。NSSetに橋渡しされている。

セット型のハッシュ値

セットに格納するためには、hashable型である必要がある。

String, Int, Double, Boolなどの基本的なタイプは、デフォルトでハッシュ可能であり、セットの値の型または辞書のキータイプとして使用することができる。関連する値のない列挙型のケース値も、デフォルトでハッシュ可能。

タイプ構文の設定

Set<Element>

配列とは異なり、セットには省略形はありません。

空のセットの作成と初期化

// 初期化構文
var letters = Set<Character>()

// 挿入
letters.insert("a")

// 空のセット
letters = []

配列リテラルを使用したセットの作成

// 配列リテラルでセットを初期化
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]

// 1つの型のみの配列リテラルで初期化する場合は、型は必要はありません
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

セットへのアクセスと変更

// アイテム数
print("I have \(favoriteGenres.count) favorite music genres.")

// 空かどうか
if favoriteGenres.isEmpty {
    print("空のセットです")
}

// 新しいアイテムをセットに追加
favoriteGenres.insert("Jazz")

// セットからアイテムを削除
// 削除された値を返す
if let removedGenre = favoriteGenres.remove("Rock") {
    print("\(removedGenre)? I'm over it.")
} else {
    print("I never much cared for that.")
}

// セットに特定のアイテムが含まれているかどうかを確認
favoriteGenres.contains("Funk") 

セットの反復

// for- inループを使用すると、セットの値を反復処理
for genre in favoriteGenres {
    print("\(genre)")
}

// 特定の順序でセットの値を反復処理
for genre in favoriteGenres.sorted() {
    print("\(genre)")
}

集合演算の実行

基本セット演算

../_images/setVennDiagram_2x.png

let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]

メンバーシップと平等を設定する

../_images/setEulerDiagram_2x.png

  • 「等しい」演算子(==)を使用して、2つのセットに同じ値がすべて含まれているかどうかを判断します。
  • isSubset(of:)メソッドを使用して、セットのすべての値が指定されたセットに含まれているかどうかを判別します。
  • isSuperset(of:)メソッドを使用して、指定したセットのすべての値がセットに含まれているかどうかを確認します。
  • isStrictSubset(of:)or isStrictSuperset(of:)メソッドを使用して、セットがサブセットであるかスーパーセットであるかを指定しますが、指定されたセットとは異なります。
  • isDisjoint(with:)メソッドを使用して、2つのセットに共通の値がないかどうかを判別します。
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]
houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

辞書

辞書は、同じ型のキーと同じ型の値の関連を、順序なしコレクションとして格納します。各値は一意のキーに関連づけられる。NSDictionaryクラスに橋渡されている。

辞書型の短縮構文

Dictionary<Key, Value>KeyValue

省略形: [Key: Value]

KeyはHashable

空の辞書を作成する

// 特定のタイプの空の辞書
var namesOfIntegers = [Int: String]()

// 1つのキー値ペアを作成
namesOfIntegers[16] = "sixteen"

// 空のディクショナリ
namesOfIntegers = [:]

辞書リテラルを使用した辞書の作成

辞書リテラルで辞書を初期化することもできます。これは、前に見た配列リテラルと同様の構文です。辞書リテラルは、1つ以上のキーと値のペアをDictionaryコレクションとして記述するための簡単な方法です。

// 国際空港の名前を格納する辞書
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

// キーと値の型が一致する辞書リテラルで辞書を初期化する場合、型を記述する必要はありません
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

辞書へのアクセスと変更

// アイテム数
airports.count

// 空かどうか
airports.isEmpty

// 追加
airports["LHR"] = "London
// 変更
airports["LHR"] = "London Heathrow"

// 特定のキーの値を設定・更新
// すでにキーが存在する場合、古い値を返す
// 存在しない場合、nil を返す
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
     print("The old value for DUB was \(oldValue).")
}

// 特定のキーのディクショナリから値を取得
// すでにキーが存在する場合、値を返す
// 存在しない場合、nil を返す
if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} 

// キーに値を設定
airports["APL"] = "Apple International"
// 値を削除
airports["APL"] = nil

// キーと値のペアを削除
// 削除された値を返す
// 存在しない場合、nil を返す
if let removedValue = airports.removeValue(forKey: "DUB") {
    print("The removed airport's name is \(removedValue).")
}

辞書の反復

for– inループを使用して、ディクショナリ内のキーと値のペアを反復できます。ディクショナリ内の各アイテムはタプルとして返され、反復の一部としてタプルのメンバーを一時的な定数または変数に分解できます。(key, value)

// for- inループを使用して、ディクショナリ内のキーと値のペアを反復
// 各アイテムはタプルとして返される
for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}

// keys プロパティにアクセス
for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}

// values プロパティにアクセス
for airportName in airports.values {
    print("Airport name: \(airportName)")
}

// keys プロパティでアクセス
let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]

// values プロパティでアクセス
let airportNames = [String](airports.values)
// airportNames is ["London Heathrow", "Toronto Pearson"]

Dictionary型には順序が定義されていません。ディクショナリのキーまたは値を特定の順序で反復するには、そのkeysor valuesプロパティへのsorted()メソッドを使用します。

文字列と文字

すべての文字列は、エンコーディングに依存しないUnicode文字で構成されている。

注意

Swift の String 型は、Foundation の NSString classへ橋渡しされており、Foundation をインポートすると、NSString のメソッドをキャストせずに使うことができる。

文字列リテラル

文字列リテラルは、二重引用符(")で囲まれた文字のシーケンスです。

let someString = "Some string literal value"

複数行文字列リテラル

複数行にまたがる文字列が必要な場合は、複数行の文字列リテラル(”””)を使用します。

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."
"""

// ソースコードを読みやすくするために改行を使用したいが、改行を文字列の値の一部にしたくない場合は、バックスラッシュを用いる。
let softWrappedQuotation = """
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."
"""

// 改行で開始または終了する複数行の文字列リテラル
let lineBreaks = """

This string starts with a line break.
It also ends with a line break.

"""

複数行の文字列をインデントして、周囲のコードと一致させることができます。../_images/multilineStringWhitespace_2x.png

文字列リテラルの特殊文字

文字列リテラルには、次の特殊文字を含めることができます。

  • エスケープされた特殊文字\0(null文字)、\\(バックスラッシュ)、\t(水平タブ)、\n(改行)、\r(キャリッジリターン)、\"(二重引用符)および\'(単一引用符)
  • 以下のように記述任意のUnicodeスカラー値、\u{nは}nは 1〜8桁の16進数
// エスケープされたダブルクオート
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein

// Unicode 
let dollarSign = "\u{24}" 
// $, Unicode scalar U+0024

let blackHeart = "\u{2665}" 
// ♥, Unicode scalar U+2665

let sparklingHeart = "\u{1F496}" 
// 💖, Unicode scalar U+1F496

複数行文字列リテラルは1つではなく3つの二重引用符を使用するため、"""複数行の文字列にテキストを含めるには、少なくとも1つの引用符をエスケープします。

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
"""
// 結果
// Escaping the first quotation mark """
// Escaping all three quotation marks """

拡張文字列区切り文字

文字列リテラルを拡張区切り文字内に配置して、特殊文字を文字列に含めることができます。

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#
// Here are three more double quotes: """

let teststr = #"Line 1\nLine 2"#
 print(teststr)
// Line 1\nLine 2

空の文字列の初期化

空の文字列の初期化には、空の文字列リテラルを変数に割り当てるか、イニシャライザ構文で新しいインスタンスを初期化する。

var emptyString = "" // 空の文字列
var anotherEmptyString = String() // 空の文字列

文字列の可変性

特定のかどうかを示すString変更された(またはされ得る変異、または(それは変更することができない場合)定数(この場合、それは変更することができる)変数に割り当てることによって)。

var variableString = "Horse"
variableString += " and carriage"
// 変更可能

let constantString = "Highlander"
constantString += " and another Highlander"
// 定数は変更できず、コンパイル時エラー

文字列は値型

String は値方です。Stringの値は、関数またはメソッドに渡されるとき、または定数または変数に割り当てられるときにコピーされる。

キャラクターの操作

ループで文字列を反復することにより、個々の文字にアクセスできます。
for character in "Dog!🐶" {
     print(character)
}
// D
// o
// g
// !
// 🐶

// Character タイプアノテーションにより、文字列から文字を得る。
let exclamationMark: Character = "!"

// Stringに、Characterの配列を渡すことにより、文字列を得る。
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cat!🐱"

文字列と文字列の連結

String値は、追加演算子()を使用して一緒に追加(または連結)して+、新しいString値を作成できます。

// + による連結
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

// += による連結
var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

// String.appendによる連結
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

複数行の文字列リテラルを使用して長い文字列の行を作成する場合は、最後の行を含め、文字列のすべての行を改行で終了する必要がある。例えば:

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three

文字列補間

文字列補間は、定数、変数、リテラル、および式の組み合わせから値を文字列リテラル内に含めることにより、新しい値を構築する。文字列補間は、単一行と複数行の両方の文字列リテラルで使用できます。

バックスラッシュは、Option キー + ”¥” キーで入力する。

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

// 拡張文字列区切り文字を使用して、文字列補間の文字列を作成できる
print(#"Write an interpolated string in Swift using \(multiplier)."#)
// Prints "Write an interpolated string in Swift using \(multiplier)."

// 拡張区切り文字を使用する文字列内で文字列補間を使用するには、バックスラッシュの後のシャープ記号の数を、文字列の先頭と末尾のシャープ記号の数と一致させる。
print(#"6 times 7 is \#(6 * 7)."#)
// Prints "6 times 7 is 42."

Unicode

Unicodeスカラー値

Swiftの String 型はUnicodeスカラー値から構築されています。Unicodeスカラー値は、一意の21ビット数値です。

U+0061 は、 LATIN SMALL LETTER A ("a")
U+1F425 は、FRONT-FACING BABY CHICK ("🐥").

すべての21ビットUnicodeスカラー値が文字に割り当てられるわけではないことに注意してください。一部のスカラーは、将来の割り当てまたはUTF-16エンコーディングでの使用のために予約されています。文字に割り当てられているスカラー値には、通常、上記の例やなどの名前も付いています。

Extended Grapheme Clusters

SwiftのCharacter型のすべてのインスタンスは、単一のextended grapheme cluster表しますextended grapheme cluster は、1つ以上のUnicodeスカラーのシーケンスであり、組み合わせて、人間が読める単一の文字を生成します。

例えば、文字éは、単一のUnicodeスカラーé はé (LATIN SMALL LETTER E WITH ACUTE, or U+00E9)として表すことができます。
また、同じ文字を1 のスカラー 標準の文字 e (LATIN SMALL LETTER E, or U+0065), 及びそれに続く COMBINING ACUTE ACCENT scalar (U+0301) として表すこともできます。

どちらの場合も、文字éは、extended grapheme cluster を表す単一の値として表されます。最初のケースでは、クラスターには単一のスカラーが含まれています。2番目のケースでは、2つのスカラーのクラスターです。

let eAcute: Character = "\u{E9}" // é
let combinedEAcute: Character = "\u{65}\u{301}" 
// e followed by eAcute is é, combinedEAcute is é

// ハングルの例
let precomposed: Character = "\u{D55C}" // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
// precomposed is 한, decomposed is 한

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// 丸の中にé

// 地域インジケーターシンボルの例
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸

文字を数える

Character文字列の値の数を取得するには、文字列のcountプロパティを使用します。

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"

SwiftがCharacter値にextended grapheme cluster を使用するということは、文字列の連結と変更が常に文字列の文字数に影響を与えるとは限らないことに注意してください。

たとえば、新しい文字列を4文字の単語cafeで初期化してから、文字列の末尾にCOMBINING ACUTE ACCENTU  (U+0301) を追加しても4文字となる。

var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe is 4"
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in café is 4"

文字列へのアクセスと変更

文字列インデックス

String.Indexは、文字列のそれぞれの位置に対応した文字です。

上記のように、文字ごとに格納するメモリの量が異なる可能性があるためCharacter、特定の位置にある文字を特定するには、その先頭または末尾から各Unicodeスカラーを反復処理する必要があります。このため、Swift文字列は整数値でインデックスを作成できません。

let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

startIndex:最初Characterの位置
endIndex:最後の文字の1つ後の位置

文字列の範囲Character外のインデックスまたは文字列の範囲外のインデックスにアクセスしようとすると、ランタイムエラーが発生します。

greeting[greeting.endIndex] // Error
greeting.index(after: greeting.endIndex) // Error

indicesプロパティを使用して、文字列内の個々の文字のすべてのインデックスにアクセス。

for index in greeting.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n T a g ! "

挿入と削除

文字列の指定したインデックスに単一の文字を挿入するには、insert(_:at:)メソッドを使用し、指定したインデックスに別の文字列の内容を挿入するには、insert(contentsOf:at:)メソッドを使用します。

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"
welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

remove(at:):指定されたインデックスの文字列から1文字を削除
removeSubrange(_:):指定された範囲の部分文字列を削除

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"

Substring

String.firstIndex(of:)
String.endIndex(of:)

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"
// Convert the result to a String for long-term storage.
let newString = String(beginning)

上記の例でgreetingは、文字列です。これは、文字列を構成する文字が格納されるメモリ領域があることを意味します。beginningはSubstringなので、メモリを再利用します。対照的に、newStringは文字列です。Substringから作成された場合、独自のストレージを持っています。次の図は、これらの関係を示しています。../_images/stringSubstring_2x.png

文字列の比較

Swiftは、テキスト値を比較する3つの方法を提供する。

  • 文字列と文字の等価性
  • プレフィックスの等価性
  • サフィックスの等価性。

文字列と文字の等価性

比較演算子で説明されているように、文字列と文字の等価性は、== 演算子と!=演算子でチェックされます。

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("等しい")
}
// Prints "等しい"

2つのString値(または2つのCharacter値)は、Extended Grapheme Clusters が標準的に等価である場合、等しいと見なされます。Extended Grapheme Clustersは、それらが舞台裏で異なるUnicodeスカラーから構成されている場合でも、同じ言語的意味と外観を持っている場合、標準的に同等です。

例えば、
 LATIN SMALL LETTER E WITH ACUTE (U+00E9)
LATIN SMALL LETTER E (U+0065) followed by COMBINING ACUTE ACCENT (U+0301)
これらのExtended Grapheme Clustersはどちらも、é文字を表現する有効な方法であるため、同等であると見なされます。

// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
if eAcuteQuestion == combinedEAcuteQuestion {
print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

逆に、
LATIN CAPITAL LETTER A (U+0041, or "A") 英語で使用される
CYRILLIC CAPITAL LETTER A (U+0410, or "А") ロシア語で使用される
は、視覚的には似ていますが、同じ言語的意味はありません。

let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
print("These two characters are not equivalent.")
}
// Prints "These two characters are not equivalent."

注意

Swiftでの文字列と文字の比較は、ロケールに依存しません。

接頭辞と接尾辞が等しい

文字列に特定の文字列の接頭辞または接尾辞があるかどうかを確認するには、文字列のhasPrefix(_:)およびhasSuffix(_:)メソッドを使用する。

let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]

var act1SceneCount = 0
for scene in romeoAndJuliet {
if scene.hasPrefix("Act 1 ") {
act1SceneCount += 1
}
}
print("There are \(act1SceneCount) scenes in Act 1")
// Prints "There are 5 scenes in Act 1"

var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
if scene.hasSuffix("Capulet's mansion") {
mansionCount += 1
} else if scene.hasSuffix("Friar Lawrence's cell") {
cellCount += 1
}
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// Prints "6 mansion scenes; 2 cell scenes"

文字列のUnicode表現

Unicode文字列がテキストファイルまたはその他のストレージに書き込まれると、その文字列内のUnicodeスカラーは、コード単位と呼ばれる小さなチャンクにエンコードされます。これらには、UTF-8エンコード形式、UTF-16エンコード形式、UTF-32エンコード形式がある。

Swiftは、文字列のUnicode表現にアクセスするためのいくつかの異なる方法を提供します。for– inステートメントを使用して文字列を反復処理し、Unicode extended grapheme clustersとして個々の値にアクセスできます。

または、String他の3つのUnicode準拠の表現のいずれかの値にアクセスします。

  • UTF-8コード単位のコレクション(文字列のutf8プロパティでアクセス)
  • UTF-16コード単位のコレクション(文字列のutf16プロパティでアクセス)
  • 21ビットUnicodeスカラー値のコレクション。文字列のUTF-32エンコード形式(文字列のunicodeScalarsプロパティでアクセスされる)と同等

D, o, g, ‼ (DOUBLE EXCLAMATION MARK, or Unicode scalar U+203C), と 🐶  (DOG FACE, or Unicode scalar U+1F436):

let dogString = "Dog‼🐶"

UTF-8表現

../_images/UTF8_2x.png

for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "

UTF-16表現

../_images/UTF16_2x.png

for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "

Unicodeスカラー表現

../_images/UnicodeScalar_2x.png

for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

valueプロパティをクエリする代わりに、各UnicodeScalar値を使用して、文字列補間などの新しい値を作成することもできます。

for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶

基本的な演算子

代入演算子

let b = 10
var a = 5
a = b

// タプル
let (x, y) = (1, 2

算術演算子

Swiftは、すべての数値タイプに対して4つの標準算術演算子をサポートしています。

  • 加算(+
  • 減算(-
  • 乗算(*
  • 除算(/

CおよびObjective-Cの算術演算子とは異なり、Swift算術演算子では、デフォルトで値がオーバーフローすることはない。

文字列も加算可能。
“hello, ” + “world”

剰余演算子

a % b

9 % 4 // equals 1
-9 % 4   // equals -1

単項マイナス演算子

let three = 3
let minusThree = -three // -3

単項プラス演算子

let minusSix = -6
let alsoMinusSix = +minusSix // 単項プラス演算子は実際には何もしません

複合代入演算子

var a = 1
a += 2 // 3
let b = a += 2  // エラー。複合代入演算子は値を返さないので、エラーになる。

比較演算子

a == b  // 等しい
a != b // 等しくない
a > b // より大きい
a < b // より小さい
a >= b // 以上
a <= b // 以下

Swiftには2つのID演算子===および!==)も用意されている。これを使用して、2つのオブジェクト参照が両方とも同じオブジェクトインスタンスを参照しているかどうかをテストできる。詳細については、「アイデンティティオペレーター」を参照。

タプルの比較

2つのタプルのタイプと値の数が同じである場合、比較できる。
タプルは、比較で2つの値が等しくないことが見つかるまで、左から右に、一度に1つの値が比較されます。これらの2つの値が比較され、その比較の結果がタプル比較の全体的な結果となる。

(1, "zebra") < (2, "apple") 
// true 
// 1 < 2 なので比較を終了、"zebra" と "apple" は比較されない

(3, "apple") < (3, "bird") 
// true
// 3==3 なので比較を継続、"apple" < "bird"

(4, "dog") == (4, "dog") 
// true 
// 4==4 なので比較を継続、"dog" == "dog"

("blue", -1) < ("purple", 1) 
// true

("blue", false) < ("purple", true) 
// エラー。ブーリアンを比較できない。

注意

Swift標準ライブラリには、7要素未満のタプル用のタプル比較演算子が含まれています。7つ以上の要素を持つタプルを比較するには、比較演算子を自分で実装する必要がある。

三項条件乗演算子

question ? answer1 : answer2questionquestionanswer1answer2

三項条件演算子は、以下のコードの省略形です。

if question {
    answer1
} else {
    answer2
}

// 例
let hasHeader = true
let rowHeight = hasHeader ? 50 : 20
// rowHeight is equal to 50

nil結合演算子

a ?? b

a が nil でなければ アンラップしたaの値、aがnilであれば bを返す。
式は常にオプショナルタイプ。

nil 結合演算子は、以下のコードの省略形です。

a != nil ? a! : b

注意

aの値がnilではない場合、b の値は評価されません。

範囲演算子

Closed Range 演算子

a…b

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

Half-Open Range 演算子

a..<b

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
     print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

One-Sided Ranges 演算子

for name in names[2...] {
    print(name)
}
// Brian
// Jack

for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true

論理演算子

Swiftは、Cベースの言語にある3つの標準論理演算子をサポートしています。

  • 論理否定 !a
  • 論理AND a && b
  • 論理OR a || b

論理否定演算子

論理否定演算子は !a のようにブール値を反転する。

論理AND演算子

a && b

最初の値がのfalse場合、2番目の値は評価されません。

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

論理OR演算子

a || b

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

論理演算子の組み合わせ

複数の論理演算子を組み合わせて、より長い複合式を作成できます。

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

複数の論理演算子を持つ複合式は、左端の部分式を最初に評価します。

明示的な括弧

複雑な式の意図を読みやすくするために、括弧を含めると良い。
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

Basics

定数と変数

// 定数
let maximumNumberOfLoginAttempts = 10 

// 変数
var currentLoginAttempt = 0

// 複数の定数または複数の変数は、コンマ区切り
var x = 0.0, y = 0.0, z = 0.0

// 型宣言
var welcomeMessage: String

// 複数の変数の型宣言
var red, green, blue: Double

// 定数と変数の命名
let π = 3.14159
let 日本語 = "日本語の変数名"
let 🐶🐮 = "dogcow"

// 定数と変数の印刷
let helloWorld = "Hello World!"
print(helloWorld)
print("\(helloWorld) こんにちは")

コメント

複数の定数または複数の変数を、コンマで区切って1行で宣言できます。

// C++ Style
// This is a comment.

// C Style
/* This is also a comment
but is written over multiple lines. */

// ネストされたコメント
/* Cの複数行コメントとは異なり、
 /* Swiftの複数行コメントは他の複数行コメント内にネストできます。 */
コメント入りのコードをコメントアウトすることができる */

セミコロン

// 1行に複数の個別のステートメントを記述したい場合
let cat = "🐱"; print(cat)

整数

Intは、32ビットプラットフォームではInt32、64ビットプラットフォームではUInt64。

UIntは、32ビットプラットフォームではUInt32、64ビットプラットフォームではUInt64。

浮動小数点数

Double 64ビット浮動小数点数。通常は、こちらを推奨。

Float 32ビット浮動小数点数。

Type Safe と型推論

Swift は Type Safe。

浮動小数点は、既定でDoubleとなる。
let pi = 3.14159

Numeric リテラル

// 十進数
let decimalInteger = 17

// 二進数
let binaryInteger = 0b10001 

// 八進数
let octalInteger = 0o21

// 十六進数
let hexadecimalInteger = 0x11

// 浮動小数点
let decimalDouble = 12.1875

// 十進数は、10exp。
let exponentDouble = 1.21875e1 // これは、12.1875

// 十六進数は、2exp
let hexadecimalDouble = 0xC.3p0 // これは、12.3 の2の0乗

// 整数と浮動小数点では、0が先行しても良い
let paddedDouble = 000123.456

// 読みやすさのために、"_"を間に挟んでも良い
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

数値型変換

整数変換

整数の場合は、自動的に型変換が行われる。

// Error
UInt8 = -1 
Int8 = Int8.max + 1

整数と浮動小数点の変換

// 整数型と浮動小数点型の間の変換は明示的に行う必要がある。
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine

// 浮動小数点から整数への変換も明示的に行う必要がある。
let integerPi = Int(pi)
// この場合、浮動小数点値は常に切り捨てられる。
// 例えば、4.75 >>> 4、  -3.9 >>> -3 になる。

タイプエイリアス

typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0

Bool

trueまたはfalse

let i = 1
if i { } // これはコンパイルエラーになる。

タプル

複数の値を単一の複合値にグループ化したもの。タプル内の値は任意のタイプを持つことができる。関数の戻り値としてよく使われる。

let http404Error = (404, "Not Found")

// 分離
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
print("The status message is \(statusMessage)")

// アンダースコアの部分は無視される
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")

// ゼロから始まるインデックス番号を使用して、タプル内の個々の要素の値にアクセスできる。
print("The status code is \(http404Error.0)")
// Prints "The status code is 404"
print("The status message is \(http404Error.1)")
// Prints "The status message is Not Found"

// タプル内の個々の要素に名前を付けることができる。
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")
// Prints "The status code is 200"
print("The status message is \(http200Status.description)")
// Prints "The status message is OK"

オプショナル

// nil になる可能性がある場合
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"

nil

値のない状態

var serverResponseCode: Int? = 404
serverResponseCode = nil

// デフォルト値を指定せずにオプションの変数を定義すると、nilとなる
var surveyAnswer: String?
// surveyAnswer は、nil で定義される

Swiftでは、nil はポインターではなく、特定の型の値がないことを示しています。オブジェクトタイプだけでなく、任意のタイプのオプションをに設定できます。

if文と強制アンラップ

ifステートメントを使用して、オプションとを比較することにより、オプションに値が含まれているかどうかを確認できますnil。この比較は、「等しい」演算子(==)または「等しくない」演算子(!=)を使用して実行します。

オプションに値がある場合、「等しくない」と見なされますnil

if convertedNumber != nil {
    // 値が存在している
    // !をつけることにより、値を強制アンラップできる。
    // 存在しない値にアクセスしようとすると、ランタイムエラーが発生する。
    // 強制アンラップする前に、nil ではないことを必ず確認すること。
    print("\(convertedNumber!).")
}

オプショナル バインディング

if文、while文で、値が含まれているかどうかを調べ、含まれている場合は、その値を一時的な定数または変数として使用できるようにする。

if let constantName = someOptional {
    statements
}

// 強制アンラップは、次のように書き換えることができる。
if let actualNumber = Int(possibleNumber) {
    // 値が存在している
    print("\(actualNumber)")
}

// Optional Bindingをコンマで区切って含めることができる。
if let firstNumber = Int("4"), let secondNumber = Int("42") {
     print("\(firstNumber) and \(secondNumber) ")
}

// これは、次の文と同等
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        print("\(firstNumber) < \(secondNumber)")
    }
}

guard 文は、別途説明。

暗黙的にアンラップされたオプショナル

// オプショナル
let possibleString: String? = "An optional string."
// ! による強制アンラップが必要
let forcedString: String = possibleString! // requires an exclamation point

// 暗黙的にアンラップされたオプショナル
let assumedString: String! = "An implicitly unwrapped optional string."
// ! による強制アンラップは必要ない。
let implicitString: String = assumedString

// 次の場合は、オプショナル
let optionalString = assumedString

// 暗黙的にアンラップされたオプショナル
if assumedString != nil {
    print(assumedString!)
}

// 暗黙的にアンラップされたオプショナル
if let definiteString = assumedString {
    print(definiteString)
}

エラー処理

エラー処理を使用して、プログラムの実行中に発生する可能性のあるエラー条件に対応します。

値の有無を使用して関数の成功または失敗を伝えることができるオプションとは対照的に、エラー処理では、失敗の根本的な原因を特定し、必要に応じて、プログラムの別の部分にエラーを伝播できます。 。

関数がエラー条件を検出すると、エラーをスローします。その関数の呼び出し元は、エラーをキャッチして適切に応答できます。

func canThrowAnError() throws {
    // エラーを throw することができる
}

do {
    try canThrowAnError()
    // エラーはthrowされなかった
} catch {
    // エラーがthrowされた
}

// Swiftは、エラーがcatch節で処理されるまで、自動的に現在のスコープから伝播します。

// さまざまなエラー条件に応答する方法
func makeASandwich() throws {
    // ...
}
do {
    try makeASandwich()
     eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

アサーションとプリコンディション

アサーションプリコンディションは、実行時に行われるチェックです。これらを使用して、さらにコードを実行する前に、必須条件が満たされていることを確認します。アサーションまたはプリコンディションが true の場合、コードの実行は通常どおり続行されます。false の場合、コードの実行が終了し、アプリが終了します。

アサーションと前提条件の違い

アサーションはデバッグビルドでのみチェックで、開発中の間違いや誤った仮定を見つけるのに役立つ。
前提条件はデバッグビルドと本番ビルドの両方でチェックされ、本番環境で問題を検出するのに役立つ。

アサーションを使用したデバッグ

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// このアサーションは、失敗する。

// アサーションメッセージを省略可能
assert(age >= 0)

// 既に条件をチェックしているので、無条件にアサーションに失敗する場合
if age >= 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

プリコンディションの適用

// 実行を継続するためには必ず trueでなければならない
precondition(index > 0, "Index must be greater than zero.")

SwiftUI Edit UI

Version: xcode11

SwiftUIで UI の編集方法

Preview モードではない状態で、例えば Text を⌘+クリックすると、メニューが表示される。
Preview モードでは、⌘+クリックしても、メニューは表示されない。

Show SwiftUI Inspector…を選択すると、SwiftUI Inspectorダイアログが表示される。ここから、Font, Weight, Color, Alignmentなどが設定できる。

xcode Documentation

バージョン: xcode11

⌘を押しながら、funcをクリックすると、Actionsが表示される。

Add Parameter, Add Return Type を実行する。

パラメータと返り値が挿入される。

func test を実装し、⌘を押しながら、test をクリックし、Add Documentationをクリックする。

コメントのテンプレートが挿入される。

Description, Parameter, Returns を記入する。また、Note: でノートを追加できる。
⌘を押しながら、func testをクリックし、Show Quick Helpを実行する
Quick Help に関数のドキュメンテーションが表示される。

Debugging in Xcode 11

バージョン: xcode11

ソース: WWDC 2019 Video https://developer.apple.com/videos/play/wwdc2019/412/

Device Conditions

Thermal state condition

Menu > View > Navigators > Show Debug Navigators ⌘7

Memoryをダブルクリックすると、Memory Reportが表示される

デバイスの場合、Energy Impact でデバイスのエネルギーインパクトを確認できる。

Menu > Window > Devices and Simulators を選択する

ダイアログ中央下の Condition > Termal Stateを選択し、Profile でシミュレートしたい熱状態(ここではCritical)を選択し、Start ボタンを押すことにより、Termal State をシミュレートすることができる。Stopすると、状態は自動的にノーマルに戻る。

同様にネットワーク状態もシミュレートできる。

Live Environment

Swift UI で、下図のように Debug Preview に切り替え、シミュレータしたのPreview をクリックし、プレビューを開始する。

これにより、リアルタイムで、Environment Overrides が可能となる。

SwiftUIのデバッグ

SwiftUIでは、従来のUIKit のデバッグ 方法とは少し異なる。

SwiftUIでデバッグ するには、コンテキストメニューから、ライブプレビューを開始する。ソースコードを変更した時点で、新しいデバッグ セッションが始まってしまうので、ソースコードを変更してしまうと、バグがあった原因が掴めなくなってしまう可能性がある。

Debug View Hierarchy

option + Debug View Hierarchy ボタンで、次のようにソースコード、プレビュー、Canvas を表示できる。

レイヤーを3Dで表示できる。

Root ViewController と Orientation の関係

複数のViewController を作成し、それぞれ shouldAutorotate, supportedInterfaceOrientations を設定しても、ルートViewControllerしかその設定が反映されない。何故だろうと思い、調べたところ、ViewControllerはルートViewControllerとそれ以外の ViewController で動きが違うことがわかった。詳細は、次のURLを参照。

iOS 8以降、回転はViewController の View のサイズを変更することで対応するようになった。インターフェースの向きが変わると、UIKitはウィンドウのルートViewController で viewWillTransitionToSize メソッドを呼び出します。次に、そのViewController は子ビューコントローラーに通知し、メッセージをビューコントローラー階層全体に伝達します。 したがって、ルートViewController のshouldAutorotate, supportedInterfaceOrientationsは参照されるが、子のViewControllerでは、これらの値は参照されない。したがって、子のViewControllerの shouldAutorotate, supportedInterfaceOrientations を設定しても、orientationを制約することができない。viewWillTransitionToSize から回転を設定するのが正しいアプローチ思われる。

無理やり変更するのであれば、子のViewController が表示されていても、ルートViewController の shouldAutorotate, supportedInterfaceOrientations が呼び出されるので、その時に表示されている presentedViewControllerの設定値を返すようにする。

import UIKit


class ViewController: UIViewController {


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        print("MenuViewController viewDidLoad")
    }


    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if let vc = presentedViewController {
            print("ViewController supportedInterfaceOrientations for Menu")
            return vc.supportedInterfaceOrientations
        } else {
            print("ViewController supportedInterfaceOrientation for Main")
            return [.portrait, .landscapeRight]
        }
    }
    
    override var shouldAutorotate: Bool {
        if let vc = presentedViewController {
            print("ViewController shouldAutorotate for Menu")
            return vc.shouldAutorotate
        } else {
            return true
        }
    }
    
    override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
        print("ViewController preferredInterfaceOrientationForPresentation")
        return UIInterfaceOrientation.portrait
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        
        print("Main viewWillTransition")
                
    }
}

Storyboard doesn’t contain a view controller with identifier ‘***ViewController’ Error

2020-05-25 16:41:34.872079+0900 ***[5641:1831362] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Storyboard (<UIStoryboard: 0x283952340>) doesn’t contain a view controller with identifier ‘***ViewController”

次のように Storyboardの中のview controllerの Storyboard IDが設定されていない場合、このエラーになる。