「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その1

Start Developing iOS AppでFoodTrackerを実際に作ってみる、その1

Swiftのプログラミング言語としての構造や書き方は学んでいますが、実際にアプリを組む時に必要になるUI周りの学習も必要です。ここでは、公式の「Start Developing iOS Apps (Swift)」

を使って、iOS AppのUIについて少し触れてみようと思います。これだけでUI周りを理解できるとは思っていないので、ここでは慣れることを優先して、多少分からなくてもなんとか最後までたどり着くことを目標にしています。ですので、途中中途半端な理解のまま先に進む場合があることをご了承下さい。

追記: 2016/10/07 上記サイトはまだSwift 2.x対応のサンプルコードですが、Swift 3対応のサンプルコードで書いていきますので、公式のサンプルコードとは少し違いが出る可能性があることをご理解下さい。

Swiftの基本を学ぶ | Learning the Essentials of Swift

「Learning Objectives(学習目的)」で列挙されている項目を和訳してみると(少し意訳しています)、

  • 定数と変数の違いを理解
  • 暗黙的・明示的型宣言をいつ使うか
  • Optionalsとoptional bindingを使う利点を理解
  • Optionalsとimplicitly unwrapped optionalsの違いを理解
  • 条件分岐とループの目的を理解
  • 2値状態以上の条件分岐をするためのswitch文
  • 条件分岐に追加制約を加えるためのwhere構文
  • 関数、メソッド、初期化子の違い
  • クラス、構造体、列挙型の違い
  • 継承とprotocol conformanceの構文(とその背後にある概念)を理解
  • 暗黙の型を決めて、Xcode quick help shortcut (option-click)を使って追加情報を見つける
  • importとUIKit

となっています。Swiftプログラミングの基本はカバーされているようですので、ざっくりとSwiftの感覚をつかむのにはもってこいかもしれません。

というわけで早速始めていきます。ここでは上記サイトの内容を学ぶことに主眼を置いているので、サンプルプログラムもそのまま流用します(自分で考えていると時間が掛かり過ぎるので)。重要なキーワードは「Swiftの基本を学ぶ」や「Swiftをもっと深く学ぶ」ページにリンクを貼っておきますので、それぞれの項目に関してさらに掘り下げたい方はそちらをご覧下さい。なるべく分かりやすくまとめるつもりなので、端折る(はしょる)部分もあるかと思います。

基本的な型 | Basic Types

定数と変数 | Constants and Variables

まずは定数(constants)と変数(variables)です。簡単にまとめると、下のようになります。

種類 修飾子 説明
定数(constant) let 固定。一度初期化したら変更できない
変数(variable) var 後で何度でも変更可能
完全に蛇足ですが、公式マニュアルにある英語の説明。英語で「immutable」と「mutable」という言葉を使って、定数と変数の違いを表現していますが、「mutable」というのが「変更可能」で、「immutable」はその逆で「変更不可能」です。頭に「im」が付くと否定(反対)語になります。一番分かりやすいのは「possible(可能)」の否定語「impossible」でしょうか。

補足ですが、letvarは名前の前に置き、名前との間には半角スペースが必要です。

var myVariable = 42
myVariable = 50
let myConstant = 42

この例のように、変数var myVariableは変更可能で、定数let myConstant42という値を後で変更することは出来ません。

型 | Types

型(type)は値の種類を決めるタグのようなもので、整数型や文字列型など、色々あります。

型推論

型の決め方は2通りあって、

  • 上記の例のように明示的に指定しないケース(暗黙の型宣言)
  • 明示的に指定するケース

です。型推論(type inference)とは、暗黙的に型を決める際に使われる機能で、ざっくり定義すると

初期化時に与えられた値から、Swiftが型を勝手に推測して決めること

です。

let implicitInteger = 70 // 小数点なしの数字は整数型。デフォルトではInt
let implicitDouble = 70.0 // 小数点をつけると浮動小数点型。デフォルトではDouble
let explictDouble: Double = 70

最初の2つは値から型を推測していますので、暗黙の型宣言です。3つ目は何もしなければ整数型Intですが、明示的にDoubleを付けて浮動小数点型にしています。

「implicit」は「暗黙の」という意味で、「explicit」はその逆「明示的な」という意味です

型を指定する場合は、定数や変数の名前の後にコロン:を置いて、その後ろに型を書きます。

宿題1

所々「experiment」と称した宿題(直訳すると実験ですが)があるので、それもやっていきます。

EXPERIMENT

In Xcode, Option-click the name of a constant or variable to see its inferred type. Try doing that with the constants in the code above.

ここでは、

Xcode上で定数や変数の名前をoption+clickすることで型推論してみろ、

というものです。試しにやってみると、

Xcode上で型推論(Option+Click)
こんな感じで、クリックした変数の型(今回はDouble)が別窓に表示されます。Optionだけを押しっぱなしにすると、マウスがクエスチョンマークに変わるので、その状態で定数や変数の名前をクリックすると上記の画像のような状態になります。

明示的な型変換

もし型変換が必要な場合は、明示的に指定する必要があります。

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

上記の例では、整数型のwidthを文字列型Stringに明示的に型変換しています。

値は暗黙的に型変換されることはありません。例えば、何もせずに整数から浮動小数点数に変わる、ということはありません。Swiftではこの辺の型安全(type safety)が厳密で、例えば整数と浮動小数点数の演算でも普通にコンパイルエラーになります。

宿題2

EXPERIMENT

Try removing the conversion to String from the last line. What error do you get?

ここでの宿題は、

明示的な型変換で指定しているStringを外すとどういうエラーが出るのか確認してみろ、

です。

試しにやってみると、

error: binary operator '+' cannot be applied to operands of type 'String' and 'Int'

というエラーが出ます。「加算演算子+String型とInt型という異なる型には使えません」、と怒られています。

文字列補間

数値型をStringに含める場合にはバックスラッシュ付きのカッコ\()で囲むという方法が使えます。

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

このように、文字列に埋め込まれた値を実行時に評価して、対応する値に置き換える処理のことを文字列補間(string interpolation)と呼びます。

オプショナル型 | Optionals

オプショナル型とnil

オプショナル型(optionals)は、「値がない」という状態が起こり得る場合に使います。Swiftでは、この「値がない」という状態にnilを使います。値をオプショナル型として定義したい場合は、クエスチョンマーク?を型の後に付けます。

let optionalInt: Int? = 9

Forced Unwrapping

オプショナル型で宣言した定数等はオプショナルで包まれている状態なので、それを剥がして(unwrap)やらないと値として使えません。良く分からないかもしれませんが、そういうものだという程度の理解で充分だと思います。

オプショナル型を強制的に外す方法が、ビックリマーク!を使ったforced unwrappingです(!はforce unwrap operatorという演算子のようです)。

let actualInt: Int = optionalInt!

このようにオプショナル型を外したい定数や変数の直後(スペース無し)にビックリマークを置きます。注意点は、変数等が値を持つことが確実な場合のみforced unwrappingする、ということです。やってみると分かりますが、nilが入っている変数にforced unwrappingすると、実行時エラーを起こしクラッシュします。

オプショナル型が有効になる一例が型変換です。Xcodeのplaygroundで実行した結果を先に見せると、

オプショナル型を使った明示的型変換
のようになります。

var myString = "7"
var possibleInt = Int(myString)
print(possibleInt)

上記の例ではmyStringは文字列ですが7という数字を持っていますので、整数型に型変換すると、数値としての7に変換されます。

Playground上でprint()によって出力された結果を見ると分かりますが、7ではなくてOptional(7)、と表示されていることが分かります。これがオプショナル型で包まれている状態です。これを数字の7にするためにはunwrapする必要があります。
myString = "banana"
possibleInt = Int(myString)
print(possibleInt)

しかし、文字列が数字ではない場合(上記例だとbanana)、整数への型変換はうまくいかないのでnilになります。

配列とコメント

配列(Arrayは同じ型を持った値を順序立てて収めるための型です。配列を作る場合は角括弧[]を使います。また配列要素にアクセスする際には、[2]のようにカッコの中に配列要素のインデックス(指数)を指定します。配列要素のインデックスは0から始まることに注意が必要です。

var ratingList = ["Poor", "Fine", "Good", "Excellent"]
ratingList[1] = "OK"
ratingList

空の配列も作ることが出来ます。

// 空の配列を作る
let emptyArray = [String]()

空の配列を作る場合には上記のような初期化構文を用います。初期化に関しては、後の方で学びます。

上記コードにはコメントがついています。コメントはコンパイル時には無視されますが、「空の配列を作る」という風に、我々が見て分かりやすい情報を提供するための機能です。分かりやすいコメントを入れておくと、(自分自身のためでもありますが)自分以外の人がソースコードを見やすくなるという利点もあります。コメントが1行の場合は//、複数行にまたがる場合は/* */が使えます。

Implicitly Unwrapped Optionals

Implicitly unwrapped optionalsはオプショナル型ですが、アクセスする際にビックリマーク!を付ける必要がないオプショナル型になります。これはimplicitly unwrapped optionalsが、最初に値をセットされて以降は常に値を持つ(絶対nilにならない)と仮定されているからです。Implicitly unwrapped optionalsを宣言するときは、クエスチョンマーク?じゃなくてビックリマーク!を使います。

var implicitlyUnwrappedOptionalInt: Int!

You’ll rarely need to create implicitly unwrapped optionals in your own code. More often, you’ll see them used to keep track of outlets between an interface and source code (which you’ll learn about in a later lesson) and in the APIs you’ll see throughout the lessons.

と書いてあるように、implicitly unwrapped optionalsの出番は余りないようです(自分が何かコードを書く場合)。ただ、「インターフェースとソースコードのアウトレットを追跡するため」に使ったり、API内部で使用されていたりするそうです。インターフェースとかアウトレットって何のことか良く分かりませんが、先に進むと分かるでしょう。

制御構文 | Control Flow

Swiftには2つの制御構文があり、まとめると以下のようになります。

条件分岐 ifswitch 値の真偽(Bool型)判定
ループ for-in, while 繰り返し処理

If文

if, else if, elseを使った条件分岐

if文を使った条件分岐

let number = 23
if number < 10 {
    print("The number is small")
} else if number > 100 {
    print("The number is pretty big")
} else {
    print("The number is between 10 and 100")
}

のようになります。ifの後ろに続く条件判定が真(true)なら、if文の本体を実行します。もし偽(false)ならその後に続く条件判定に移動。elseifの条件が偽の場合に実行される条件文です。さらに細かく条件指定したい場合はelse ifを使います。

宿題3

EXPERIMENT

Change number to a different integer value to see how that affects which line prints.

上記例のif文を使った条件分岐でnumberの数字を変えて出力の違いを見なさい

という宿題です。

Xcodeでif文のテスト1
試しに6を入れてみると、最初のif文の条件number < 10が真になるので、「The number is small」が表示されているのが分かります。

Xcodeでif文のテスト2
次はデカイ数字100000を入れてみます。そうするとelse ifの条件判定number > 100が真になるので、「The number is pretty big」が表示されます。

「Pretty」は普通「可愛い」とかで使う形容詞と思いがちですが、「pretty big」とかの文脈では「非常に大きい」という意味になります。

制御構文の入れ子

制御構文は入れ子(階層化)することが可能です。下記の例だとfor-inループで配列要素を取り出しつつ、その要素をif文の条件判定でチェックしています。

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

最終的なteamScore11になります。

Optional Binding

オプショナル型がnilかどうか分からない場合、安全に取り出すための手法として、if文を使ったoptional bindingがあります。

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

Optional bindingではif文の条件中で、let name = optionalNameという形でオプショナル型の値を別の定数に移すため、それ以降のアクセスでビックリマーク!を使う必要がありません。

宿題4

EXPERIMENT

Change optionalName to nil. What greeting do you get? Add an else clause that sets a different greeting if optionalName is nil.

宿題は、

  1. optionalNamenilにして出力を確認
  2. else文を追加してnilの場合の挨拶greetingを変更する

です。

Xcodeのplaygroundでoptional bindingのテスト
optionalNamenilにすると、当然ですがgreetingは何も変更されないのでHello!のままです。optionalNamenilにするのに、「オプショナル型では宣言の際何も値を入れないとnilになる」という便利機能を使っています。

Xcodeのplaygroundでoptional bindingのテスト、elseを追加
else文を加えてテスト。確かにelse文の中に追加したgreetingが実行されていることを確認。

複数のoptional bindingとwhere構文

1つのif文に対して複数のoptional bindingを加えることが出来ます。また、where構文を使うことで細かい条件の指定も可能です。複数のoptional bindingや加えた条件が全て真の場合に限り、if文の中身が実行されます。言い換えると、1つでも偽の条件があればif文はスキップされます。

var optionalHello: String? = "Hello"
if let hello = optionalHello, hello.hasPrefix("H"), let name = optionalName {
    greeting = "\(hello), \(name)"
}

// Swift 2.xでの記述
/*
var optionalHello: String? = "Hello"
if let hello = optionalHello where hello.hasPrefix("H"), let name = optionalName {
    greeting = "\(hello), \(name)"
}
*/

上記の例だと、2つのoptional bindingがあります。さらに、optionalHelloではhello.hasPrefix("H")という条件指定も入っています。hasPrefix("H")というのは、文字列の頭文字が"H"だったらtrue、というのを判定しているメソッドです。

prefixというのは日本語で「接頭辞」、suffixを「接尾辞」と呼びます。またファイルの後ろに拡張子が付くのは見たことがあるかもしれません(例えばkeynoteのファイルなら.keyとか)が、拡張子のことをsuffixと呼ぶこともあります。

Switch文

Swiftのswitch文は整数だけの比較にとどまらず、文字列型の比較等々、あらゆる型の比較をサポートしている非常に使える条件分岐構文です。

let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
    let vegetableComment = "That would make a good tea sandwitch."
case let x where x.hasSuffix("pepper"):
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

vegetableには"red pepper"という文字列が入っているので、3番目の条件に合致して、出力が"Is it a spicy red pepper?"になります。

宿題5

EXPERIMENT

Try removing the default case. What error do you get?

defaultケースを除くとどんなエラーが出るか?

という宿題。

試してみると

error: switch must be exhaustive, consider adding a default clause

という感じで怒られます。これについては次のセクションで説明。

Swiftのswitch文について

Switch文でも複数の条件指定をする場合は、

case "cucumber", "watercress":

のようにコンマで区切ります。Switch文の場合、どちらか一方の条件を満たせばcaseの条件判定を通ります。Optional bindingでのif文の場合はAND判定(全ての条件を満たさないと真にならない)でしたが、switch文(case)の場合はどれか1つtrueなら良いというOR判定になっています。

Switch文を実行すると分かりますが、Swiftのswitch文では特に何も指定しなくても、1つの条件(case)に合致するとそれ以外の条件をスキップします。C言語などでは明示的にスキップする指定をしなければ、条件判定を続行するので、期待していた挙動をしない場合があります。

宿題5で見たエラー文で、「switch文はexhaustive」であるべき、とあります。「Exhaustive」とは「余すところのない」とか「完全な」という意味ですから、

switch文内での条件分岐は全ての条件を満たしていないといけない、

ということです。基本的には、複数の明示的な条件をcaseで指定し、それ以外はdefault、という場合がほとんどだと思います。もしcaseのみの条件分岐で全ての条件を網羅出来る場合には、defaultを使用しなくても良いかもしれません。

for-inループ

繰り返し処理に用いるループですが、for-inループを回す時、インデックスの範囲を指定する方法があります。

var firstForLoop = 0
for i in 0..<4 {
    firstForLoop += 1
}
print(firstForLoop)

範囲演算子を使うと簡単にインデックスの範囲を指定できて、例えば上限を含まない範囲演算子(..<、half-open range operator)があります。上記の例の場合、上限4を含まない0,1,2,3for-inループが回りますので、firstForLoopの最終的な値は4になります。

上限を含めたい場合は...(closed range operator)を使います。

var secondForLoop = 0
for _ in 0...4 {
    secondForLoop += 1
}
print(secondForLoop)

この例では上限4を含むループなのでsecondForLoopの最終値は5です。また、for-inの内部インデックスが必要ない場合はアンダースコア_を指定します。

関数とメソッド | Functions and Methods

関数 | Functions

関数とは

再利用可能なコード(演算、または機能)のかたまり

で、プログラム中のどこからでも参照できるものです。

関数の定義にはfuncキーワードを指定します。また、関数はパラメータ(parameters)戻り値(return values)を持つことができますが、必須ではありません。パラメータや戻り値についてはサンプルコードを使って説明します。

すごくざっくり説明すると、パラメータは関数へのインプット(入力)で、戻り値は関数からのアウトプット(出力)です。

具体的な関数宣言を見てみます。

func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)"
}

関数宣言の基本はfunc greetの部分で「greetという名前の関数を宣言しますよ」と言っています。

パラメータはカッコ()の中に定義されている定数で、今回の例ですと

(name: String, day: String)

で、両方とも文字列型String、名前はそれぞれnamedayです。

戻り値は矢印-> Stringという形で、矢印の後に型を指定します。この例だと戻り値はStringです。

実際に関数を呼び出すと、以下のサンプルコードのようになります。

greet(name: "Anna", day: "Tuesday")
greet(name: "Bob", day: "Friday")
greet(name: "Chalie", day: "a nice day")

// Swift 2.xでの記述
/*
greet("Anna", day: "Tuesday")
greet("Bob", day: "Friday")
greet("Chalie", day: "a nice day")
*/

関数呼び出しの際、最初のパラメータの名前は指定せず値のみを指定し、2個目のパラメータから名前と値を指定します。

Swift3から全てのパラメータの名前を指定するような仕様に変更になりました。

メソッド | Methods

メソッドとは「ある特定の型に内包された関数」です。関数はグローバルに定義されているのでプログラムのどこでも呼び出せます。しかし、メソッドはある特定の型からしか呼び出せません。Switch文の所ですでにメソッドが出ていましたが、

let exampleString = "hello"
if exampleString.hasSuffix("lo") {
    print("ends in lo")
}

これはString型が持っているhasSuffix()というメソッドの使用例です。メソッドを呼び出す場合にはdot syntax(ドット.の後にメソッド名)を使います。

メソッドの使い方も関数と全く一緒で、2番目以降のパラメータには名前と値の指定が必要です。

var array = ["apple", "banana", "dragonfruit"]
array.insert("cherry", at: 2)
array

insert()メソッドは、指定した配列のインデックスに要素を挿入するメソッドです。出力結果はどうなるかXcodeのplaygroundで試してみてください。

クラスと初期化子 | Classes and Initializers

クラス | Classes

オブジェクト指向言語と言えばクラスです。オブジェクトとはクラスインスタンス(クラスの実体)で、クラスというのは設計図のようなものです。つまり、ざっくり言うと

クラス=設計図、オブジェクト=設計図を実体化したもの

という感じです。クラスの中身を形成するのがプロパティ(properties)、クラスの振る舞い(機能)を決定するのがメソッド(methods)です。

クラス定義にはclassキーワードを使います。

class Shape {
    var numberOfSides = 0 // プロパティ
    // メソッド
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

プロパティ宣言は定数や変数の場合と一緒です。「メソッドは型に内包された関数」と言いましたが、プロパティは言わば「型に内包された定数や変数」(厳密に言うと少し違いますが)です。

クラスの実体(インスタンス)を作るには、クラス名の後にカッコを付けます。クラス内部のプロパティやメソッドにアクセスする時にはdot syntaxです。

Xcode playgroundでクラスのテスト
(画像をクリックすると拡大します)

初期化子 | Initializers

初期化子の基本

上記クラスには欠けているものがあって、それが初期化子です。初期化子というのは実はメソッドの1つですが、

初期化子=クラスインスタンス(を使用可能な状態にするため)の初期設定を行うメソッド

です。初期設定というのは、基本的にはプロパティに初期値をセットすることです。初期化子はinit()で作成可能です。

class NamedShape {
    var numberOfSides = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

初期化子部分だけ抜き出すと、

init(name: String) {
    self.name = name
}

ですが、selfという自分自身を指す特殊なプロパティを使って、クラス内部のプロパティであるself.nameとパラメータで与えられたnameを区別しています。

クラスが持つ全てのプロパティは「プロパティの宣言と同時に初期化(上記例でnumberOfSides)」もしくは「初期化子内部で初期化(上記例でname)」されないといけません。

初期化子は直接init()で呼び出すのではなく、クラスインスタンスを作る時にパラメータを指定する形で呼び出します。

let namedShape = NamedShape(name: "my named shape")

今回の例ですと、初期化子はinit(name: String)なので、クラスインスタンスを作るときのパラメータ指定がそのようになっていると思います。

クラス継承と初期化子

クラス特有の機能が継承です。別のクラスを継承した子クラスをサブクラス(サブクラス、subclass)、継承元をスーパークラス(スーパークラス、superclass)と呼びます。Swiftでは1つのクラスが複数のクラスを同時に継承することが不可能なようです。ただし、クラス継承階層の深さに制限はないので、幾らでも継承の階層を掘り下げることは可能です。

また、サブクラスのメソッドはスーパークラスのメソッドの「上書き」が可能で、それをオーバーライドと呼びます。上書きするのは中身で、メソッド自体の名前が変わらないのがオーバーライドです。その際はoverrideキーワードを使います。逆に言うと、意図せずオーバーライドした場合、Swiftはコンパイルエラーを起こします。また、オーバーライド指定したけどスーパークラスにオーバーライドするメソッドが存在しない場合もちゃんと検知してくれます。

これらを踏まえた上で、以下の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 testSquare = Square(sideLength: 5.2, name: "my test square")
testSquare.area()
testSquare.simpleDescription()

初期化子だけに注目すると、

  1. まず、自分自身(サブクラスSquare)のプロパティsideLengthをセット
  2. スーパークラス(NamedShape)の初期化子を呼び出しsuper.init()
  3. スーパークラスのプロパティnumberOfSideの値を変更

という順序で初期化していることが分かります。これは初期化子の委譲(initializer delegation)と関わっていてややこしいのですが、この順番にはちゃんと意味があり、初期化の順序を入れ替えることは出来ません。

初期化の失敗 | Failable Initializers

オブジェクトの初期化をわざと失敗させるケース、というのも必要かもしれなくて、例えば

  • パラメータに渡された値が、欲しい値の範囲外
  • 必要なタイミングでデータが空になっている

等々。

初期化を失敗させることの出来る初期化子をfailable initializersと呼びます。難しそうですが、単に初期化が失敗した時にnilを返す初期化子、です。Failable initializerを宣言する時にはinit?()を使います(initの後に?)。

class Circle: NamedShape {
    var radius: Double
    
    init?(radius: Double, name: String) {
        self.radius = radius
        super.init(name: name)
        numberOfSides = 1
        if radius <= 0 {
            return nil
        }
    }
    
    override func simpleDescription() -> String {
        return "A circle with a radius of \(radius)."
    }
}

let successfulCircle = Circle(radius: 4.2, name: "successful circle")
let failedCircle = Circle(radius: -7, name: "failed circle")

色々な初期化子

初期化子に付随するキーワードは2つあって1つはconvenience、もう1つはrequiredです。キーワードが何も付かない初期化子はdesignated initializerと言って、最も重要な初期化子です。

convenienceキーワードが付いた初期化子(convenience init())は、プロパティのカスタマイズ用で、必須な初期化子ではありません。最終的には必ずdesignated initializerを呼び出さないといけない仕様になっています。

requiredキーワードが付いた初期化子を持ったスーパークラスを継承したサブクラスは、必ず同じ初期化子を(requiredキーワード付きで)サブクラスで定義しないといけません。

convenienceは「便利な」「都合のいい」、requiredは「必須の」「必要な」という意味ですから、初期化子としての用途が(ある程度)腑に落ちるかもしれません

クラスのダウンキャスト | Type casting

Type castingとは明示的な型変換のことで、ダウンキャストはその一部です。ダウンキャストというのは、スーパークラスのインスタンスをサブクラスの型に無理やり変換することですから、失敗することもあります(逆はアップキャストで、これは安全な変換です)。

型変換演算子(type cast operator)には2種類あって、as?as!です。

演算子 成功時 失敗時
as? オプショナル型インスタンス nil
as! インスタンス 実行時エラー

?!から推測できるように、キャストが成功・失敗した際の効果はoptionalsとimplicitly unwrapped optionalsの場合と同じです。

class Triangle: NamedShape {
    init(sideLength: Double, name: String) {
        super.init(name: name)
        numberOfSides = 3
    }
}

let shapesArray = [Triangle(sideLength: 1.5, name: "triangle1"), 
    Triangle(sideLength: 4.2, name: "triangle2"), 
    Square(sideLength: 3.2, name: "square1"), 
    Square(sideLength: 2.7, name: "square2")
]
var squares = 0
var triangles = 0
for shape in shapesArray {
    if let square = shape as? Square {
        squares += 1
    } else if let triangle = shape as? Triangle {
        triangles += 1
    }
}
print("\(squares) squares and \(triangles) triangles.")

まず配列shapedArrayの型ですが、これは型推論でスーパークラスのNamedShapeになっています。

for-inループで四角(Square)か三角(Triangle)かを確認していますが、これはスーパークラス(NamedShape)をサブクラス(Square or Triangle)にキャストするので、ダウンキャストです。as?だとオプショナル型になるので、optional bindingを使ってnilかどうかの確認をしています。

宿題6

EXPERIMENT

Try replacing as? with as!. What error do you get?

as?as!に変更すると、どういうエラーが出るか?

という宿題ですので、やってみると、

Xcode playgroundでダウンキャストのテスト
(画像をクリックすると拡大します)

error: initializer for conditional binding must have Optional type, not 'Square'

と言って怒られます。これはas!でキャストすると、インスタンスそのものになっているというのが良く分かるエラーですね。

列挙型と構造体 | Enumerations and Structures

Swiftでは列挙型や構造体もメソッドを持てるので、例えばC言語の列挙型や構造体と比較すると非常に多機能です。

列挙型 | Enumerations

列挙型は関連する値をグループ化する時に便利です。列挙型を作るときには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
let aceRawValue = ace.rawValue

列挙型で型を指定する場合enum Rank: Intのようにします。これは列挙型の持つrawValueの型を指定していて、この例だと整数型Intです。

上記の例を見ると分かりますが、明示的に整数を指定しているのは最初だけ(case ace = 1)です。残りの値は並べた順番に割り当てられていきますので、two2three3、という具合になります。この数字自体にアクセスしたい場合はrawValueというプロパティを使います。

rawValueを指定して列挙型インスタンスを作る場合は、failable initializerのinit?(rawValue:)を使います。

// Rank.threeと同じ
if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

列挙型のfailable initializerは元々用意されている初期化子なので、ユーザが改めて定義する必要はありません。

列挙型のメンバー(上記例でaceなど)は、実際の値そのもので、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()

一つ前の例でも出ていましたが、例えば.spadesという省略記法は、元々Suit.spadesです。今、selfの値がすでにsuitであることが分かっているため、suitの部分を省略できています。

構造体 | Structures

ものすごくざっくり言うと、構造体はクラスとほぼ同じようなものです。決定的な違いは、

であるということです。つまり、構造体は値渡しの際に必ずコピーが作られますが(厳密には必ずしもそうでないケースもあるようです)、クラスは参照渡しになります。継承やタイプキャストはクラス特有の機能なので、それが必要ない場合は構造体を利用するのがベターかもしれませんね。

構造体を作る時にはstructキーワードを使います。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}

let threeOfSpade = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpade.simpleDescription()

プロトコル | Protocols

プロトコルはメソッドやプロパティ等々、何か特定の処理や機能の設計図を描くためのものです。プロトコルは雛形(インターフェース)を提供するだけで、実際の具体的な実装部分を提供するわけではありません。このインターフェースをクラスや、構造体、または列挙型に持たせて、具体的な中身を実装することになります。

Any type that satisfies the requirements of a protocol is said to conform to that protocol.

とありますが、プロトコルの仕様を満たすような型は「プロトコルに沿う(準拠する)」(conform to that protocolの部分)という言い方をするようです。

プロトコルを定義する時にはprotocolキーワードを使います。

protocol ExampleProtocol {
    var simpleDescription: String { get }
    func adjust()
}

この例では、String型のプロパティsimpleDescription、パラメータも返り値も無いメソッド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

プロトコルの仕様をクラス等に実装する場合には、継承のように名前の後にコロン:を付けてプロトコル名を書きます。プロトコルは幾つ採用(adopt)しても良いので、複数採用する場合はコンマ,で分けてプロトコル名を書きます。

SimpleClassはプロトコルExampleProtocolを採用しているので、その仕様に従って(conform)、プロパティsimpleDescriptionとメソッドadjust()の具体的な中身を実装しています。

プロトコルは普通の型のように使えますので、「インターフェースの共有」を有効活用することが出来ます。

class SimpleClass2: ExampleProtocol {
    var simpleDescription: String = "Another very simple class."
    func adjust() {
        simpleDescription += "  Adjusted."
    }
}

var protocolArray: [ExampleProtocol] = [SimpleClass(), SimpleClass(), SimpleClass2()]
for instance in protocolArray {
    instance.adjust()
}
protocolArray

adjust()という共通のメソッドを呼び出していますが、その実装はSimpleClassSimpleClass2で違います。また、プロトコル採用によってadjust()が定義されていないというケースは排除されるので、安全にメソッド呼び出しが出来るというのも利点でしょうか?

Swift and Cocoa Touch

日本語訳しても今いちピンと来ません。最後まで行けば意味が分かるようになるはず。

  • SwiftはCocoa Touchとシームレスな互換性(相互運用性?)を持つ
  • Cocoa TouchはiOS app開発に使うappleのフレームワークの集合体
  • Swift standard libraryでは、これまで見てきた型などをサポート。例)StringArrayなど
  • iOS appsの開発ではそれ以外のライブラリも多用する。その中でも重要なのがUIKit
  • Swiftファイルやplaygroundで使う場合はimport UIKitと書く

次回以降Xcodeを使って基本的なUIの作り方を学んでいきます。

Part2です。

「Start Developing iOS Apps (Swift)」でFoodTracker Appを実際に作ってみる その2 [基本的なUIを作る]
タイトルにもあるように、公式にある「Start Developing iOS Apps (Swift)」を使って実際にFoodTracker...
スポンサーリンク
広告1
広告1

シェアする

フォローする