Swiftのプロトコルやジェネリクスなどについての勉強メモ
お久しぶりです。
プロトコルとジェネリクスは奥が深く、すべてをまとめることは不可能なので、自分なりに特徴をピックアップしてまとめてみようと思います。
プロトコルとは
「実装を伴わない宣言の集合」で、振る舞いや性質を表す。
※オブジェクト指向言語における継承の概念は
・クラス継承
・インタフェース継承
の2種類があり、Swiftはプロトコルを利用してインタフェース継承を実現する。
拡張(Extension)とは
すでに存在するクラス、構造体、列挙型、プロトコルに新しい機能を追加することができる 注意しておきたいこととしては、「格納型プロパティ」と「プロパティオブザーバ」は追加できない。
ジェネリクスとは
Swiftの最も強力な特徴の1つで、柔軟で再利用可能な関数やあらゆる型を抽象的に記述することができる。 Swiftの標準ライブラリでもジェネリクスのコードが多用されている
※ちなみにジェネリクスはSwift特有の機能ではなく、Javaにもあり、C#ではジェネリック、C++ではテンプレートとなっている
ジェネリクスには2種類ある
・ジェネリクス関数
・ジェネリクス型
Swiftの多重継承について
C++やPythonとは異なり、Swiftでは多重継承の機能はないので、プロトコルを使用して多重継承を実現する
その際の複数のプロトコルを適合させる際はプロトコルは振る舞いなどを持った性質のようなものなので感覚では「性質の融合」。
たとえば
CollectionTypeは
CollectionType: Indexable, SequenceType {}
は
という性質のIndexable
と
という性質のSequenceType
の融合であると言える
関数の戻り値にプロトコルを使ってアクセス制御
// プロトコルを使ってアクセス制御(フィルター) // アクセス可能なものを定義 protocol AccessibleFilter { var name: String { get } var age: Int { get } } // モデルクラス class Model: AccessibleFilter { // 5つのプロパティ var id: String = NSUUID().UUIDString var createdAt: NSDate = NSDate() var updatedAt: NSDate = NSDate() var name: String var age: Int init(name: String, age: Int) { self.name = name self.age = age } } func readData() -> AccessibleFilter { let data = Model(name: "taro", age: 10) return data }
これを使うと
ちゃんとフィルターがかかり補完にアクセス制御がされる
ジェネリクス型について
ジェネリクス型は基本的に構造体やクラスで使われる
型パラメータTにはプロトコルに準拠させることができる
クラスの場合
class MyClass<T> {}
構造体の場合
struct MyStruct<T: プロトコル> {}
ジェネリクス関数について
Intと全く同じ性質を持つ型パラメータTを作成してみる(今回は理解のため、TはIntと同じ笑)
func genericsFunction<T: SignedIntegetType where T: Comparable, T: Equatable>() {}
もしくは
func genericsFunction<T where T: SignedIntegerType, T: Comparable, T: Equatable>() {}
と書ける。今回の場合は下のほうがわかりやすい気がしますね。 もし複数のプロトコルとクラスを継承する場合は
func genericsFunction<T: クラス where T: プロトコル1, T: プロトコル2, T: プロトコル3>() {}
などとクラスをwhereの前に出したほうがわかりやすい
※型パラメータには慣習的ににT, U, V, Wなどが使われる
※クラス継承の場合はクラスとプロトコルを継承する際の順番はまずクラスを継承し、そのあとプロトコルを適合させるが、ジェネリクス関数の型パラメータの場合は順番は関係なく、最初に型パラメータにプロトコルを適合させてから、クラスを継承しても良い
Selfについて
Self・・・自分自身の型
プロトコルの関数定義の際によく用いられ、そのプロトコルを適合させたクラス、構造体、列挙型自身になる。 特に
func 関数名 -> Self { // do something return something }
となっていた場合はそれ自身のインスタンスを返す。
付属型について
ジェネリクスの機能の1つ
まずなぜ付属型が必要なのか?
=> プロトコル内では型パラメータのような抽象的なものが使えないので、associatedtypeを使って付属型(抽象的な型)を表現する
そしてassociatedtypeは付属型を宣言するための宣言子である
associatedtype 付属型: 条件
以下のようにプロトコル内で「なんでも良い抽象的な型」を宣言することができる
protocol SequenceType { associatedtype SubSequence }
※型パラメータのように条件をつけることも可能
protocol Indexable { associatedtype Index: ForwardIndexType }
感想
やっぱりプロトコルやジェネリクスは難しいですね。しかしマスターするとコードの表現力も上がりますし、抽象化もでき、いろいろ役立つと思うので少しずつ頑張っていこうかと思っています。
もし間違いや良い表現がありましたら気軽にコメントください!
そしてこれから知識が付き次第追記していこうと考えています