趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。
参考文献
この記事は以下の書籍の情報を参考にして執筆しました。
Swiftのプロトコル
Swiftのプロトコル : 型が持つべきメソッドやプロパティの宣言をまとめる仕組み。
(JavaやC#のインターフェースに当たる)
異なる型の共通した操作
例として
Int、Double、Stringは共通して、演算子を使って「互いに比較をできる」機能を持っている。
この「互いに比較をできる」機能というのは、Swift標準ライブラリで定義されているComparableプロトコルがまとめている。
何が便利なのかというと
例えばソート関数は「互いに比較をできる」必要がある。
なので型にComparableプロトコルを適応すると並び替えの対象にできる。
プロトコル思考
Swiftはインターフェースを定義するためのプロトコルをクラス・構造体・列挙型にも適応可能な仕組みとして導入している。
そのためプロトコルを中心にソフトウェアの設計、プログラミングを進めることができる。プロトコル指向と呼ぶ。
プロトコルの採用
Swiftの標準ライブラリにあるCustomStringConvertibleプロトコルを採用してみる。
public protocol CustomStringConvertible {
var description: String { get }
}
CustomStringConvertibleプロトコルはdescriptionを定義するが、このときに適当な文字列を代入して、
構造体がも文字列として呼ばれた時の動作を確認した。
struct Hoge : CustomStringConvertible {
let hoge = "hoge"
var description: String{
hoge + "HogeHoge"
}
}
struct Fuga {
var fuga: String = "fuga"
}
let hoge = Hoge()
print("\(hoge)") // hogeHogeHoge
let fuga = Fuga()
print("\(fuga)") // Fuga(fuga: "fuga")
CustomStringConvertibleを採用した構造体が文字列として出力されたときに、
descriptionに入った値を返すことが確認できた。
型としてのプロトコル
プロトコルはプログラム内で型としても使用できる。
型に一致したプロトコルを持つ値を使用できる。
protocol Dog {
var size: Int { get }
}
struct Pochi: Dog {
var size: Int
}
struct Taro: Dog {
var size: Int
}
struct Tama {
var size: Int
}
let hoge = Pochi(size: 10)
let fuga = Taro(size: 20)
let aaa = Tama(size: 5)
var list: [Dog] = [] // protocol Dogを型に指定
list.append(hoge)
list.append(fuga)
list.append(aaa) // error Dogプロパティを採用していない構造体は配列に加えれない
プロトコルの継承
protocol Dog {
var size: Int { get }
}
protocol GuideDog : Dog {
var rank: Int { get }
}
プロトコルの合成
protocol Dog {
var size: Int { get }
}
protocol Porice {
var strength: Int { get }
}
protocol PoriceDog : Dog ,Porice{
var strength: Int { get }
}
var hoge: PoriceDog
一方プロトコルを定義せずに、プトとコルの和集合として型を指定する場合
var hoge: Dog & Porice
プロトコルと付属型
プロトコルの型定義内部に付属型を定義できる。
associatedtypeというキーワードを使う。
protocol SimpleVector {
associatedtype Element //xとyの型を統一する
var x : Element { get set }
var y : Element { get set }
}
struct VectorFlat : SimpleVector {
typealias Element = Float //型を明示
var x, y : Float
}
struct VectorDouble : SimpleVector, CustomStringConvertible {
var x, y : Double // 型推論される
var description: String { "x: \(x),y: \(y)"}
}
struct VectorGrade : SimpleVector, CustomStringConvertible {
enum Element : String { case A, B, C, D, X } //型をcase型で列挙した文字に指定
var x, y: Element
var description: String { "x: \(x),y: \(y)"}
}
var a = VectorFlat(x: 10.0, y: 15.0)
print(a) // VectorFlat(x: 10.0, y: 15.0)
var b = VectorDouble(x: 20.0, y: 40.0)
print(b) // x: 20.0,y: 40.0
var c = VectorGrade(x: .A, y: .X)
print(c) // x: A,y: X
型パラメータに制約を指定
(1)associatedtype A
制約なし
(2)associatedtype A : プロトコル
型Aはプロトコルに適応する
(3)associatedtype A = 型名
このプロトコルを採用した型のAに規定値がなければ型の規定値を使用する
(2)と(2)を組み合わせて
associatedtype A : プロトコル = 型名
(1)~(3)の条件の後にwhere条件を記述できる。
条件は(a)(b)のどっちか
(a)型: プロトコル
型は指定したプロトコルに適応
(b)型1 == 型2
型1と2の一致
プロトコルを継承する際にプロトコルの付属型に対して制約をつけることができる。
protocol B : プロトコルA where 条件
条件は(a)(b)のどっちか
(a)型パラメータ: プロトコル
型パラメータは指定したプロトコルに適応
(b)型パラメータ== 型
型パラメータと型の一致
protocol SimpleVector : Equatable{
associatedtype Element : Equatable //Equatableを条件に指定
var x : Element { get set }
var y : Element { get set }
}
struct VectorDouble : SimpleVector, CustomStringConvertible {
var x, y : Double // Equatableに適合する
var description: String { "x: \(x),y: \(y)"}
}
struct ShopOnMap : SimpleVector, CustomStringConvertible {
static func == (lhs: ShopOnMap, rhs: ShopOnMap) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
var shop: (name : String, comment : String?) //タプル型なのでEquatableに適合しない
var x, y : Double
init(n: String, c: String? = nil, X: Double, Y: Double){
shop = (name: n, comment: c)
x = X
y = Y
}
var description: String { "\(shop.name) x: \(x),y: \(y) \(shop.comment ?? "”)"}
}
var a = ShopOnMap(n: "a”, X:11.0, Y:30)
print(a) // a x: 11.0,y: 30.0
var b = ShopOnMap(n: "b”, c:"bbbb”, X:11.0, Y:30)
print(a==b) // true プロパティのx,yが等しいだけで同じとみなされる
var c = ShopOnMap(n: "a”, X:2.0, Y:2)
print(a==c) // false
Copy-On-Weite
Swiftでは参照型のデータはクラスのインスタンスやクロージャーくらいしかない。
実数や配列や構造体などは値型のデータ。
値型のデータは値全体がコピーされるように思うが、Swiftでは値型の受け渡しの際に、内部的には参照を行い、値の変更があった時点でコピーを取ることで、メモリを効率的に使っている。
部分配列を作る時も同様にシークエンスの一部を取り出す時に、シークエンスの複製を作るのではなく部分配列の情報を効率よく格納できる型を作り、元のシークエンスの値は共有。