概要
Swift開発経験0.001程度のプログラマーが少しでもSwift理解を深めるためにA Swift Tourの内容をまとめました。
ただ、そもそもツアーだけでは理解をすることができない点が多々あったので、自分で追記したり、参考記事のリンクを添付しています。
そのため完全なSwiftTourのただのまとめではなく自分なりにアレンジを加えたものになります。
まとめといったな?あれは嘘だ
※Tourで使われているコードはそのまま添付しています。検証の際はMyPlaygroundのご利用をおすすめします。
本文
Simple Values
変数と定数
変数はvar
で、定数はlet
で定義する。
// 変数を定義
var myVariable = 42
myVariable = 50 // 上書き
// 定数を定義
let myConstant = 42
myConstant = 11 // エラーになる
以下の形式で、変数や定数に型を明示することができる。
let explicitDouble: Double = 70 // コロンの後のDoubleが型の定義。
※型の一覧と意味はこちらを参照。
下記のように変数の結合で、型がマッチしていない場合、エラーになるので注意。
let label = "The width is " // 文字列はダブルクオーテーションで囲む
let width = 94
let widthLabel = label + String(width) // widthがInt型のため、string型に変換
文字列内で変数を展開する場合は\()
で変数を囲う。
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
配列と辞書
配列型と辞書型の値は[]
で囲う。 (配列と辞書について)
// 配列
var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"
// 配列に追加
shoppingList.append("blue paint")
// 辞書
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
// 辞書の追加
occupations["Jayne"] = "Public Relations"
※辞書型はPHPでいう連想配列といったところか。
※辞書型についてはこちらも参照。
空の配列型、辞書型の定義もできる。
// 空配列
let emptyArray = [String]() // 値の型を定義
// 空辞書
let emptyDictionary = [String: Float]() // キーと値の型をそれぞれ定義
すでに定義されている配列・辞書の初期化は以下のように書く。
// 配列の初期化
shoppingList = []
// 辞書の初期化
occupations = [:]
Control Flow
条件はif
やswitch
で、ループはfor-in
や while
、 repeat-while
で書く。
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
// inの後にループさせる変数を置く
// forの後のteamScoreにループの結果取得できる値が格納される
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// Prints "11"
詳しくはこちらも参照。
オプショナル型とif let
オプショナル型は、変数の型のひとつで、nilの可能性がある値を指す。
nil
が考えられるような変数をチェックする場合、if let
を使うと良い。
var optionalName: String? = "John Appleseed" // オプショナル型変数
var greeting = "Hello!"
// optionalNameがnilのとき、条件節内は通らない。(falseのため)
if let name = optionalName {
greeting = "Hello, \(name)"
}
optionalName
の定義で使われているString
の後の?
で、オプショナル型を定義。
下記のように??
を使って文字列内でnilかどうかの判定をすることもできる。
let nickName: String? = nil
let fullName: String = "John Appleseed"
// nickNameがnilなら、fullNameが使用される
let informalGreeting = "Hi \(nickName ?? fullName)"
switch構文
switch case
を使えば、いろいろなかたちでの比較ができる。
let vegetable = "red pepper"
switch vegetable {
// 文字列の比較。一致していたら「Add some raisins and make ants on a log.」が表示される。
case "celery":
print("Add some raisins and make ants on a log.")
// 同じ結果を出力する場合、比較対象をカンマで区切って書くこともできる。
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
// 変数xが「pepper」という文字を含む場合、letの後の定数xに「pepper」を格納する
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
// defaultを設定しないとエラーになる
default:
print("Everything tastes good in soup.")
}
// Prints "Is it a spicy red pepper?"
// 上記いずれかのcaseに合致すれば、switch構文は終了する。
// ※caseに一致しない場合、defaultが出力される。
// (続くcaseが比較されることはない)
for-in構文
for in
を使えば、ループ対象の配列・辞書のキーと値のそれぞれを取り出せる。
// 辞書型
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
// kindで「Prime」などのキーを、numbersで配列を取り出せる
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
// Prints "25"
while構文
while構文は、while後に書かれた条件に該当する限り構文内を通る。
var n = 2
while n < 100 {
n *= 2
}
print(n)
// Prints "128"
for 変数 in 開始値 ..< 終了値
で、+1
ずつ変数に格納され、変数が終了値と同値の場合、条件節内を通らない。
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// Prints "6"
※for 変数 in 開始値 ... 終了値
は、終了値と同値でも条件節内を通り、超過した場合falseとなり、条件節内を通らない。
Functions and Closures
functionはfunc
で始めて定義する。
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
// functionの実装
greet(person: "Bob", day: "Tuesday")
person
とday
が引数にあたるラベルであり、それぞれのラベルのコロンの後のString
が値の型を表す。
->
後のString
は、返り値(戻り値)の型を表す。
また、_
のみ記述してラベルを省略することも可能。
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
// 「_」を用いることで、第一引数はラベルが不要。
greet("John", on: "Wednesday")
タプル
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"
ネスト
関数をネスト(入れ子)構造で書くこともできる。
func returnFifteen() -> Int {
var y = 10
func add() {
// 上位の関数で定義された変数にアクセスすることもできる
y += 5
}
add()
return y
}
print(returnFifteen())
// 15
ファーストクラスオブジェクト
Swiftでは関数は「ファーストクラスオブジェクト」として定義されている。
よって、値として関数を用いることもできる。
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
また、引数に関数を入れることもできる。
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
// 第二引数でlessThanTenという関数を用いている
hasAnyMatches(list: numbers, condition: lessThanTen)
クロージャ
クロージャは、たとえば次のように書く。
var numbers = [20, 19, 7, 12]
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"
他にも、in
などを省略することができる。
var numbers = [20, 19, 7, 12]
let sortedNumbers = numbers.sorted { $0 > $1 } // $0と$1については下記のクロージャの記法について参照を
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"
※クロージャの記法についてはこちらを参照
Objects and Classes
クラスの定義はclass
で始める。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
定義したクラスは以下のように用いる。
var shape = Shape()
// ShapeクラスのnumberOfSides変数に 7 を格納
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
print(shapeDescription)
// A shape with 7 sides.
イニシャライザ
インスタンスの生成時、イニシャライザinit
での初期化をする必要がある。
※その理由についてはこちら。
class NamedShape {
var numberOfSides: Int = 0
var name: String
// name初期化 イニシャライザのため、funcは不要
init(name: String) {
// selfで自クラス内の変数を指示する。(initのnameを指していない)
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
オブジェクトのクリーンアップ処理をする場合、deinit
でデイニシャライザを生成する。
継承
継承
の概念があり、親クラスの属性を引き継ぐためには、子クラスの後に: 親クラス
を記載する。
// SquareがNamedShapeクラスを継承
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
親クラスにある関数と同一の関数名を用いる場合、override
をつけて定義。(なければエラーになる。)
ゲッター・セッター
ゲッターとセッターを利用することもできる。
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
// 値の取得
get {
return 3.0 * sideLength
}
// 値のセット
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// Prints "9.3" ゲッター
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003" セッター
上記のEquilateralTriangle
クラスでは、3つのステップが踏まれている。
- サブクラスが宣言するプロパティの値をセット
- サブクラスのイニシャライザの呼び出し
- スーパークラス(親クラス)で定義されたプロパティの値を変更。
willSet と didSet
willSet
didSet
でプロパティの変更前、変更後で何らかの処理を行わせることができます。
class TriangleAndSquare {
// 先のEquilateralTriangleクラスを定義
var triangle: EquilateralTriangle {
willSet {
// newValueはwillSetの定数
square.sideLength = newValue.sideLength
}
}
// 先のSquareクラスを定義
var square: Square {
didSet {
// oldValueはdidSetの定数
triangle.sideLength = oldValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
結果がオプショナル型の場合があるとき、Square?
のように?
をつける。
オプショナル型の変数ないし定数を利用する際は、optionalSquare?
のように対象のプロパティの後に?
をつける。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
Enumerations and Structures
enum
列挙型を書く際は、enum
で始める。
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
// 列挙型でも関数を書くこともできる
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
print(ace)
// ace
let aceRawValue = ace.rawValue // rawValueで要素の値を取得
print(aceRawValue)
// 1
列の要素それぞれの値は、先頭から0
、1
、2
...というように順番に割り当てられる。
(例:case a, b, c
という列なら、a
には0
が、b
には1
、c
には2
が割り当てられる。)
ただ、case ace = 1
のように要素に対して明示的に値を割り当てることによって、要素の値を変更することができる。
(例:case ace = 1
では明示的に1
を要素の値に割り当てていますが、そうでなければ0
が入る。)
また、続く要素の値は2、3...というように明示された値に続く。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
上記のようにインスタンス生成時にrawValueを指定して列挙子を取得する際、自動的にイニシャライザーinit(rawValue:)
が付与される。
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
自クラス外の特定の要素にアクセスする場合は、Suit.hearts
のように要素の前にSuit
をつける必要があるが、クラス内では.hearts
のようにクラスを省略できる。
switch文のself
で、引数自体にアクセスできる。
下記のように要素を定数に格納し、要素と一致する値を抽出することもできる。
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
print("Failure... \(message)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."
構造体
struct
をつけて構造体を生成できる。イニシャライザを持つことができるが、継承することはできず、クラスが参照型であるのに対し、構造体は値型。
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
Protocols and Extensions
プロトコルは以下のように定義する。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
クラスも列挙も構造体もプロトコルを継承することができる。
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
mutating
はstruct
やenum
において自身の値を変更する場合にfunc
の前につける。(変更できることを明示する必要がある)
class
ではつける必要がない。クラスは常に変更できるから。
extension
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
// Prints "The number 7"
extension
をつけると既存の機能を拡張できる。
上記の例でいうと、ExampleProtocol
の型プロパティをInt
で拡張し、adjust
メソッドをExampleProtocol
に追加している。
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class. Now 100% adjusted."
// print(protocolValue.anotherProperty) // Uncomment to see the error
プロトコル名は型としても利用できる。
また上記の例では、protocolValue
変数はすでにSimpleClass
クラスで利用されているが、コンパイラがExampleProtocol
の変数として認識するため問題ない。クラスが実装するプロパティにアクセスできない。
Error Handling
エラーを取得する際はError
プロトコルを用いる。
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
エラーハンドリングはthrow
を用い、エラーハンドリングをする関数を用いる場合はthrows
を用いる。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
下記の例ではIf節を通った場合、即座にエラーを返す。
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
エラーハンドリングの方法は他にもある。
do-catch
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
// Prints "Job sent"
do-catch
では、do
ブロック内でtry
文を書くことで、エラー時にcatch
のエラーコードが自動的に出力される。
下記のように複数のcatch
ブロックを用意することも可能。
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
// Prints "Job sent"
try?
try?
と書けばオプショナル型で結果を返せる。関数がエラーを返せば、結果ではnil
が返る。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
defer
defer
を使ってブロック要素を書くことで最終的には関数内のコードをすべて通す。(returnするまで)
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
// Prints "false"
Generics
ジェネリクスで、IntやStringなどの予め指定された型とは異なり、任意の指定した型パラメータを用いることができる。
<>
(山括弧)で括ることで、ジェネリクスの関数・型を定義することができる。
※下記でいうと<Item>
がジェネリクス。
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
ジェネリクスは、クラス、列挙、構造体でも用いることができる。
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
where
下記のようにwhere
を関数内部の前に記述することで、2つの型が同じであること、またはあるクラスがスーパークラスを継承していることを明示することができる。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
<T: Equatable>
と、<T> ... where T: Equatable
は同じ意味。
End
最後に
先人の知恵を勝手にたくさん拝借しました。ありがとうございます。
(リンク先の記事がどれもわかりやすく、理解に際して非常にありがたかったです…)
修正や参考資料などございましたらご指定いただけますと幸いです。
ご覧くださいましてありがとうございました。