###1. 変数のスコープとは
スコープ(scope)とは変数の有効範囲を表します。
つまり、変数のスコープは変数が使える(生きている)有効範囲のことを表します。
一般的にはSwiftでは{}に囲まれた部分がスコープの範囲ですが
guard let s = some else {
return
}
print(s)
のような例外もあります。
####1-1. ローカル変数、グローバル変数、メンバ変数
一般的に変数にはローカル変数、グローバル変数、メンバ変数があります。
この2つの変数はスコープの範囲が異なります。
#####1-1-1. ローカル変数
ローカル変数はメソッド内で定義した変数です。ローカル変数のスコープの範囲は基本的には、その定義したメソッド内だけになります。
func viewDidLoad() {
var localVariable: Int = 1
localVariable += 5
}
上記の場合、ローカル変数であるlocalVariable
のスコープの範囲はlocal関数内になります。
#####1-1-2. グローバル変数
グローバル変数はクラスの外側に定義した変数です。
ローカル変数は基本的には定義したメソッド内だけでしか使えませんが、グローバル変数は、メソッド、さらにはクラスも関係なく全ファイルからアクセスできる変数です。
便利な一方、予期せぬポイントで値を書き換えてしまうなどのバグが発生する可能性があります。また、グローバル変数はプログラムが終了するまでメモリが確保されるため多くのメモリを消費することになります。
var globalVariable: Int = 1
class ViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
globalVariable += 5
}
}
上記のように、どこからでもグローバル変数にアクセスすることができます。
#####1-1-3. メンバ変数
メンバ変数はクラス内で定義される変数です。メンバ変数はインスタンス変数とも呼ばれ基本的にはインスタンス毎に生成されます。
スコープの範囲は基本的にクラス内になります。
class ViewController: UIViewController {
var memberVariable: Int = 1
func viewDidLoad() {
super.viewDidLoad()
memberlVariable += 5
}
}
IBOutlet
やIBAction
などのUIコンポーネントなどの変数は、このメンバ変数で定義することがほとんどです。
###2. Swiftの関数・メソッドとは何か
オブジェクト指向プログラミングにおいて、各オブジェクトが持っている自身に対する操作。つまり、複数の処理や変数をひとまとめにした手続き(ブロック)です。
メソッドを定義するときの構文は
func メソッド名 (引数) -> 返り値 { }
です。引数や返り値は必要でなければ空欄にすることができます。
その場合は func メソッド名 () { }
となります。
func hoge(var1: Int) -> (String) {
return (String(var1))
}
返り値が必要な場合、return
という記述を書いた後に実際に返す値を書きます。
今回の例では、Int型の引数var1
をString型にキャスト(型変換)して値を返しています。
####2-1. メンバメソッド(インスタンスメソッド)とクラスメソッド
メソッドにはメンバメソッドとクラスメソッドの2種類あります。
#####2-1-1. メンバ関数(インスタンスメソッド)
クラス内の定義で、特に修飾子無しで記述したメソッドはメンバ関数になります。
メンバ関数は状態(自分のインスタンスのプロパティの値)に依存します。
また、メンバ関数では自分のインスタンスはself.
として参照できます。
class Hoge {
func getName() -> String {
//処理内容
}
}
#####2-1-2. クラスメソッド
メソッドを宣言する時にfunc
の前にclass
を記述したメソッドはクラスメソッドになります。
クラスメソッドは状態(自分のインスタンスのプロパティの値)に依存しません。
クラスメソッドの特徴として、インスタンス化しなくてもコールすることができます。
class Hoge {
class func getName() -> String{
//処理内容
}
}
####2-2. メソッドのコールの方法
メンバ関数はインスタンスの後ろに続けて書くことによって呼び出しますが、クラスメソッドはクラス名の後ろに続けて書くことによって呼び出します。
// メンバ関数
let name = Hoge()
name.getName()
// クラスメソッド
let name = Human.getName()
###3. Optional
Optional型とは、変数の型がもつ通常の値に加えて、空の(値が無い)状態を保持できる変数です。空の状態はnil
で表現します。
「値を保持しているか保持していないか」というフラグを持っていて、nil
を代入することによって「保持していない」状態にしています。
オプショナル型は変数の宣言時に、型の後ろに?
をつけて宣言します。
var name: String?
name = nil
// 非オプショナル型の変数にはnilは代入できません。
var name: String
name = nil // エラーになります。
オプショナル型は宣言時に値を代入しない場合は自動的にnil
の値が設定されています。そのため宣言時にnil
で初期化する必要はありません。
####3-1. オプショナルバインディング
オプショナル型は、次の様にif文の条件で使用することができます。値が空nil
の場合はfalse
、それ以外はtrue
と判定されます。
var hoge: Int?
if let p = hoge {
// 処理
} else {
// ここが実行される
}
###4. OptionalのUnwrap
Optional型を計算で使用したり、通常の型を受け取る関数の引数に渡したりする場合は、値がnil
でないことを明示するために、アンラップをする必要があります。アンラップはオプションという箱に入っている変数を箱から取り出すイメージです。つまり、箱から取り出さないとその変数は使うことができません。
アンラップするには、オプショナル型の変数名の後ろに!
をつけます。
var number: Int?
print(String(number!))
中身がnil
のOptional型の変数をアンラップすると実行時エラーが発生します。
下記のようなオプショナルバインディングとアンラップを組み合わせると実行時エラーを防ぐことができます。
var number: Int?
if let p = number {
print(String(number!))
} else {
print("Error: number == nil")
}
###5. 暗黙的なUnwrapping
Optional型にはもう1つの宣言の仕方があります。暗黙的なOptional型と呼ばれるもので、宣言時に?
ではなく!
をつけて宣言します。
var name: String!
暗黙的なオプショナル型は、nil
を代入することが可能という点では上のオプショナル型と同じですが、使用時にアンラップする必要がありません。(自動的にアンラップされる)
アンラップの必要がないので、初期値がnil
でも使用時までには必ず値が入っていると想定されるケースでは、暗黙的なOptional型の方が便利です。
但し、アンラップが必要な状況で、暗黙的なOptional型をnil
のまま使用すると実行時エラーが発生します。
var number: Int!
number = 100
print(number) // 変数名の後ろに!をつける必要がない
price = nil
print(number) // 実行時エラー
###6. Optional chaining
Swiftのオプショナル型は、値を取り出したいときに開示(unwrap)をしますが、開示した際にnilだった場合、実行時にエラーになってしまいます。そのような時にOptional chainingを使います。
Optional chainingでは?
を使ってOptional型をつなげます。 こうすると、いずれかのOptional型の変数がnil
だった場合に、その変数で評価が打ち切られnil
を返すようになります。したがって、実行時エラーにはなりません。
var n: String = hoge?.fuga?.foo?.name
上記のような代入処理では、hoge?
、fuga?
、foo
のいずれかがnil
の場合、変数n
にはnil
が代入されます。
もちろん、メソッドにもOptional chainingは値の参照だけでなく、メソッドのコールにも使えます。
var f = hoge?.fuga?.foo?.funcHoge()
###7. タプルとは
タプルは、複数の値を一組にしたものです。配列と似ていますが、配列と違って異なる型の値をまとめることができます。但し、後から要素を追加したり削除することはできません。
配列にも異なる型を入れることができますが、その場合、各要素の型はAnyやAnyObjectとなり、各要素を演算等で使用する場合にキャスト(型変換)が必要になります。タプルの要素はそのまま演算にも使用することができます。
タプルは次の様に各要素を,
(カンマ)区切りで並べ、()
で括って記述します。
配列のように各要素に0から通し番号が割り振られ、これによって要素を識別するようなデータ構造を表します。
値を参照する時は変数名の後に頭から順に0から始まる番号をつけます。
let item = ("りんご", 100, 0.08)
print(item.0, item.1, item.2 * 100) // りんご, 100, 8
タプルの各要素を別々の変数に受け取ることもできます。
let item(name, price, tax = ("りんご", 100, 0.08)
print(name, price, tax * 100) // りんご, 100, 8
次の様にタプルにラベルをつけて参照することもできます。
let item = (name: "りんご", price: 100, tax: 0.8)
print(name, price, tax * 100) // りんご, 100, 50
###8. クラスの継承
クラスの継承とは親クラスの属性を引き継いで新たなクラスを作成することです。継承するクラスをサブクラス又は子クラス、継承されるクラスをスーパークラス又は親クラスと呼びます。
Swiftでは、ユーザ定義のクラスをベースクラスとすることができ、特定のクラスを継承する必要はありません。
継承するとサブクラスにはスーパークラスで宣言されたすべてのオブジェクトとメソッドが使えるようになります。
Swiftではクラスの継承を サブクラス名: スーパークラス名
と定義します。
class Monster {
var name: String
var level: Int
init(name: String, level: Int) {
self.name = name
self.level = level
}
func attackMonster(enemy: Monster) {
print("\(self.name)は\(enemy.name)を攻撃した。");
}
}
/* Slimeクラス(Monsterクラスを継承) */
class Slime: Monster {
func escapeFromMonster(enemy: Monster) {
print("\(self.name)は\(enemy.name)から逃げた。");
}
}
let monster = Monster(name: "モンスター", level:3)
let slime = Slime(name: "スライム", level:2)
monster.atackMonster(slime) // モンスターはスライムを攻撃した。
slime.atackMonster(monster) // スライムはモンスターを攻撃した。
slime.escapeFromMonster(monster) // スライムはモンスターから逃げた。
上の例はMonsterクラスを継承したSlimeクラスを定義したものです。Slimeクラスはインスタンス化した時にイニシャライザが呼ばれますが、Slimeクラスではinit
を宣言していないのでスーパークラスであるMonsterクラスのinit
が呼ばれます。
スーパークラスのattackMonsterメソッドにはMonsterクラス、Slimeクラスのどちらのオブジェクトからもアクセスできます。
####8-1. オーバーライド
オーバーライドとはスーパークラスとサブクラスで同じ名前の関数を定義することです。
class Monster {
var name: String
var level: Int
init(name: String, level: Int) {
self.name = name
self.level = level
}
func attackMonster(enemy: Monster) {
print("\(self.name)は\(enemy.name)を攻撃した。");
}
}
/* Slimeクラス(Monsterクラスを継承) */
class Slime: Monster {
override func attackMonster(enemy: Monster) {
print("\(self.name)は\(enemy.name)をおちょくった。");
}
}
let monster = Monster(name: "モンスター", level:3)
let slime = Slime(name: "スライム", level:2)
monster.atackMonster(slime) // モンスターはスライムを攻撃した。
slime.atackMonster(monster) // スライムはモンスターをおちょくった。
slime.super.atackMonster(monster) // スライムはモンスターを攻撃した。
スーパークラスで既に定義されているatackMonsterメソッドをサブクラスで再宣言するにはoverride
をサブクラスの再宣言するメソッドの先頭に記述する必要がある。
SlimeクラスのオブジェクトからスーパークラスのatackMonsterメソッドを呼びたい場合、super
を記述することでスーパクラスのメソッドを呼ぶことができます。
###9. プロトコル
クラスの挙動を決めた設計書(仕様書)みたいなものです。クラス自体にも設計図という側面がありますが、クラスがその属性や挙動を細部に渡って記述するのに対して、プロトコルでは外部へのインターフェースのみを定義します。そして実際の挙動はそのプロトコルを採用するクラスで記述することになります。つまり、プロトコルは必要最小限の実装をすることができます。
プロトコルの定義の方法はクラスや構造体の場合と似ており下記のように記述します。
protocol SomeProtocol {
// インターフェースの定義
func someMethod()
}
プロトコルに適合させるには、型名の後に:
(コロン)をつけて宣言します。次は構造体をプロトコルに適合させる場合です。
struct SomeStructure: SomeProtocol {
// インターフェースの定義
func someMethod()
}
また、クラスにプロトコルを適合させる場合、そのクラスがスーパークラスを持っている場合は、スーパークラス名の後にプロトコル名を書きます。
class SomeClass: SuperClass, SomeProtocol, AnotherProtocol {
// インターフェースの定義
func someMethod()
}
###10. Type Casting
型変換とは、ある型のデータを別の型に変換することです。例として、整数型の「1」を実数型の「1.0」へ変換することなどが挙げられます。整数の1と実数の1.0は同じ値ですが、プログラムを実行する際、コンピュータの内部では違う形で表現されています。この内部表現で表わされたデータを、ある形から別の形へ変換することを型変換(=型キャスト)と呼びます。
####10-1. as, as?, as!
型変換には一般的にas
が使われますが、これらは使い分けられます。
#####10-1-1. as
- Swfitコンパイラにより型変換・キャストが成功すると保証されるときに使用
- アップキャスト
- リテラルの型を指定
class Animal {}
class Dog: Animal {}
let dog = Dog()
let animal = dog as Animal // アップキャスト
let floatValue = 1 as Float // 型指定
#####10-1-2. as!
- 強制的にダウンキャスト
- ダウンキャストが成功することが分かっている場合に使用
- ダウンキャストが失敗するとruntimeエラーになる
let animal1: Animal = Dog()
let dog = animal1 as! Dog // OK
let animal2 = Animal()
let dog2 = animal as! Dog // ダウンキャストできないのでruntimeエラー
#####10-1-3. as?
- ダウンキャストが成功するか分からない場合に使用
- 戻り値はオプショナル型
- 失敗した場合はOptional.None
let animal1: Animal = Dog()
let dog = animal1 as? Dog // dogの型はOptional<Dog>
let animal2 = Animal()
let dog2 = animal as? Dog // エラーは起きない。dog2の中身はOptional.None
###11. UIKitとは何か
UIKit Frameworkは、iOSで開発するアプリのGUI機能を提供するフレームワーク、クラス群です。
UIはUser Interfaceの略であり、ユーザとアプリケーション間のデータのやりとりの方法、仕組みの総称であり、UIKitはそれらを提供するためのフレームワークです。
###12. ViewControllerのライフサイクルとは
Viewが作られ読み込まれ、そして消えるまでの一連の流れのことライフサイクルと呼びます。
- loadView
- viewDidLoad
- viewWillAppear
- viewWillLayoutSubviews
- viewDidLayoutSubviews
- viewDidAppear
- viewWillDisappear
- viewDidDisappear
上記の8つの流れのことを指します。
#####12-1. loadView
nib=xibなどを使用しない場合にカスタムViewの初期化を行う場所。
Storyboardやxibなど、InterfaceBuilderを利用している場合はこのメソッドを呼び出してはなりません。
#####12-2. viewDidLoad
ViewControllerのviewがロードされた後に呼び出されます。
上記の通り、InterfaceBuilderを使用している場合はサブビューのセットアップは一般的にここで行うことになります。
#####12-3. viewWillAppear
viewが表示される直前に呼ばれます。
#####12-4. viewWillLayoutSubviews
viewControllerのviewのlayoutSubviewsメソッドが実行される前に呼ばれます。
#####12-5. viewDidLayoutSubviews
viewControllerのviewのlayoutSubviewsメソッドが実行された後に呼ばれます。
#####12-6. viewDidAppear
完全に遷移が行われ、スクリーン上に表示された時に呼ばれます。
viewDidAppear
が呼ばれたタイミングではすでに画面に描画されている状態なので、このタイミングでviewの生成などを行うと当然おかしなことになります。
#####12-7. viewWillDisappear
viewが表示されなくなる直前に呼び出されます。
#####12-8. viewDidDisappear
完全に遷移が行われ、スクリーン上からViewControllerが表示されなくなったときに呼ばれます。
viewDidDisappear:が呼び出されたからといってviewControllerオブジェクトが破棄されるわけではありません。
これらのサイクルはiOSから特定の名前の関数が自動的に、規定のタイミングで呼び出されます。
###13. 構造体とClassの違い
この2つの大きく異なるポイントは構造体は値渡し、クラスは参照渡しである点です。
また、構造体は継承できませんが、クラスは継承することができスーパークラスを拡張することができます。
構造体は下記のようにstruct
を構造体名の先頭に付けることで宣言する。
struct CGPoint {
var x: CGFloat
var y: CGFloat
init() {
}
init(x: CGFloat, y: CGFloat) {
self.x = x
self.y = y
}
}
構造体はシンプルなデータ構造やメソッドの引数に渡す時、自身の更新を目的としないものに使うことが多いです。
また、構造体は値渡しです。値渡しであることは、コピーが作られるということで、プロパティの数が多いとメモリ消費も大きくなります。
###14. MVCとは
Model View Controllerの略称でViewはユーザーが見る部分です。Modelはそのアプリが保持するデータを表します。ControllerはViewとModelの間を受け持ってやりとりするパーツです。
例えばModel内でデータが変わった場合、Controllerがその情報を受け取りViewにあるラベルなどの数値を変えることを命令するといった仕事が分担されているということを表わしたものです。
以上