LoginSignup
15
13

More than 3 years have passed since last update.

概要

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

条件はifswitchで、ループはfor-inwhilerepeat-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")

persondayが引数にあたるラベルであり、それぞれのラベルのコロンの後の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

列の要素それぞれの値は、先頭から012...というように順番に割り当てられる。
(例:case a, b, cという列なら、aには0が、bには1cには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

mutatingstructenumにおいて自身の値を変更する場合に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

最後に

先人の知恵を勝手にたくさん拝借しました。ありがとうございます。
(リンク先の記事がどれもわかりやすく、理解に際して非常にありがたかったです…)

修正や参考資料などございましたらご指定いただけますと幸いです。

ご覧くださいましてありがとうございました。

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13