Swift で自作のオブジェクトをコレクションとして扱う

Swift プログラミング

Swift で自作のクラスをコレクションとして扱えるようにしてみました。

コレクションはインデックスによる操作ができるオブジェクトです。

このとき SequenceType にも準拠するので for ... in 構文にも対応します。


オブジェクトをコレクションに対応させる

コレクションというのは、オブジェクト内で複数の値をひとまとめに扱うものです。

たとえば文字列を表すString型 は、複数の文字をCharacter型 でひとまとめにして扱うコレクションになっています。


Swift言語 で独自のクラスや構造体をコレクションとして扱えるようにしたいときは、オブジェクトをCollectionTypeプロトコル に準拠させます。

CollectionTypeプロトコル

CollectionTypeプロトコル に準拠するには次の機能を実装する必要があります。

protocol CollectionType : _CollectionType, SequenceType {
	
    subscript (position: Self.Index) -> Self.Generator.Element { get }
}

protocol _CollectionType : _SequenceType {

    typealias Index : ForwardIndexType

    var startIndex: Index { get }
    var endIndex: Index { get }
}

必須機能は「開始インデックスの取得」と「終了インデックスの取得」、そして「インデックスを使って値を取り出すサブスクリプト」の実装になります。

また、CollectionTypeプロトコルSequenceTypeプロトコル を継承しているため、それが要求する機能も必要です。

protocol SequenceType : _Sequence_Type {

    typealias Generator : GeneratorType

    func generate() -> Generator
}

これらの機能を実装することで、コレクションを想定して設計された関数などで自前のオブジェクトを使えるようになります。

CollectionTypeプロトコルSequenceTypeプロトコル を継承しているため、for ... in 構文やmap関数 といった機能も利用できるようになります。

SequenceTypeプロトコル で必要になる機能についてはこちら で説明しています。

CollectionTypeプロトコル に準拠したオブジェクトを定義する

それでは、コレクションとして扱えるオブジェクトを定義してみます。

今回は、たとえば『三角関数cos の値を 0°から 360°未満まで 1°刻みで持つCosineClassクラス 』を作成してみます。

CosineClassクラス の定義

まず、0°から 360°未満までの三角関数cos の値を取り出せるCosineClassクラス を作成します。

今回はこのオブジェクトをコレクションとして扱えるようにCollectionTypeプロトコル に準拠させています。

class CosineClass : CollectionType {

	/// 範囲の開始位置と終了位置を決定しています。
	let startIndex:Int = 0
	let endIndex:Int = 360
	
	/// 位置から cos の値を取り出すサブスクリプトです。
	subscript (angle: Int) -> Double {
		
		let π:Double = 3.141592653589793
		let getRadian = {
			
			Double(angle) * π / 180.0
		}
		
		if contains(indices(self), angle) {
			
			return cos(getRadian())
		}
		else {

			abort()
		}
	}
	
	/// 範囲内の値を順番に取り出すためのジェネレーターを取得します。
	func generate() -> IndexingGenerator<CosineClass> {
		
		return IndexingGenerator(self)
	}
}

ジェネレーターの実装について

ところで、CollectionTypeプロトコル が継承しているSequenceTypeプロトコル に準拠させるとき、値を順次取得するのに使うジェネレーターを実装する必要があります。

このジェネレーターは、通常であれば自分で適切に実装する必要がありますが、SequenceTypeプロトコル に準拠したオブジェクトであれば、標準で用意されたIndexingGenerator型 を使えます。

func generate() -> IndexingGenerator<CosineClass> {
		
	return IndexingGenerator(self)
}

今回の例であれば、上のように、IndexingGenerator に自身の型を指定したものを返すgenerate関数 を用意して、IndexingGenerator のイニシャライザにself を渡して生成したインスタンスを返すようにします。

これだけで、実装中のコレクションに適切なジェネレーターを生成できます。

CollectionTypeプロトコル に準拠することで得られる機能

自作オブジェクトをCollectionTypeプロトコル に準拠させたら、コレクションを想定して実装された各種機能を利用できるようになります。

CollectionTypeプロトコルSequenceTypeプロトコル にも準拠しているので、こちら で紹介した機能も利用できます。

for ... in 構文で使用する

コレクションは、次のようにしてfor ... in 構文で利用できます。

let cosine = CosineClass()

for x in cosine {
	
}

このようにすることで、CosineClassクラス のインスタンスからジェネレーターが生成され、そこからひとつずつ値が取り出されて処理されます。

指定した要素が格納されているインデックスを取得する

find関数 を使用することで、指定した要素がコレクションのどのインデックスに格納されているかを取得できます。存在しない場合はnil が得られます。

let cosine = CosineClass()
let index = find(cosine, 0.9975640502598242)

最初の要素や最後の要素を取得する

first関数last関数 を使って、格納されている要素の最初の要素や最後の要素を取得できます。要素が存在しない場合はnil が得られます。

let cosine = CosineClass()

let head = first(cosine)
let tail = last(cosine)

コレクションの取り得るインデックス範囲を取得する

indices関数 を使うと、コレクションが取る要素の範囲を取得できます。

範囲はstartIndex..<endIndex という最後の要素を含まない『ハーフオープン』の形で取得できます。

let cosine = CosineClass()
let range = indices(cosine)

コレクションが空かどうかを判定する

コレクションに要素が含まれないときにtrue を返すisEmpty関数 も利用できます。

let cosine = CosineClass()

if isEmpty(cosine) {

}

要素を逆順に並べ替える

reverse関数 を使うと、要素を逆順に並べたコレクションを取得できます。

let cosine = CosineClass()
let reversedValues = reverse(cosine)

その他のコレクション

コレクションを実装するときに使うプロトコルには、ほかにも次のものがあります。

プロトコル 要求される実装
MutableCollectionType サブスクリプトを使って読み書きが可能なコレクションです。
ExtensibleCollectionType 要素サイズを拡張できるコレクションです。
RangeReplaceableCollectionType 挿入や削除といった操作ができるコレクションです。ExtensibleCollectionType にも準拠します。

要求される実装も増えますが、これらに準拠することで、要素の追加や削除といった関数も利用できるようになったりします。