Edited at

【Swift】標準ライブラリに使用されているプロトコル


標準ライブラリに使用されているプロトコル

Swiftの標準ライブラリに使用されているデフォルトのプロトコルについて本記事では解説していきます。


比較系


Equatableプロトコル

public protocol Equatable {

public static func ==(lhs: Self, rhs: Self) -> Bool
}

このプロトコルは同値性を検証可能にするためのプロトコルです。中置演算子==を利用するための定義として上記のように記述されています。つまり、私たちが普段使用しているif文などで用いる==はこのプロトコルで定義されているおかげで使用可能になっていると言えます。

使用例

struct Temperature : Equatable {

var celsius: Double = 0

static func == (lhs: Temperature, rhs: Temperature) -> Bool {
return lhs.celsius == rhs.celsius
}
}

let temperaturel = Temperature(celsius: 25)
let temperature2 = Temperature(celsius: 25)
temperaturel = temperature2 //true

上記の例ではEquatableプロトコルの効果をわかりやすくするため、そのプロトコルを適合した構造体のサンプルコードを記載しました。

このコードを見る限り、同じ値の25同士なのだからプロトコル関係なしに同じ値だとtrue判定を出せることは当たり前だと思うかもしれません。しかし、それは間違いなのです。普段私たちが使用しているString型やInt型もこのEquatableプロトコルにデフォルトで準拠しているためString型同士で同値か判定可能になります。そのため、普段無意識に行っている==を使用したコードはこのプロトコルなしには実装できないコードなのです。


Comparableプロトコル

このプロトコルは大小関係を比較可能にするためのプロトコルです。そのため、普段使用する<、>、<=、>=はこのプロトコルによってメソッドとして定義されているため使用可能ということになります。

public protocol Comparable :Equatable{

public static func <(lhs: Self, rhs: Self) -> Bool
public static func <=(lhs: Self, rhs: Self) -> Bool
public static func >=(lhs: Self, rhs: Self) -> Bool
 public static func >(lhs: Self, rhs: Self) -> Bool
}

上記のコードを見るとEquatableプロトコルを継承していることがわかります。これはComparableプロトコルがEquatableプロトコルに準拠しているためによるものです。

使用例

struct Test : Comparable {

let score: Int
let answer: String

static func ==(lhs: Test, rhs: Test)-> Bool {
return lhs.score == rhs.score && lhs.answer == rhs.answer
}
static func <(lhs: Test, rhs: Test) -> Bool {
return lhs.score < rhs.score
}
}

let a = Test(score: 90, answer: "Tom")
let b = Test(score: 85, answer: "Bob")

a < b //false
a <= b //false
a >= b //true
a > b //true

上記のサンプルコードでは、Equatableプロトコルのメソッドである==も実装されています。

これはEquatableプロトコルを継承しているためであり、Comparableプロトコルに準拠する型(String型、Int型など)では==(lhs:rhs:)を実装する必要があり、それに加えて<(lhs:rhs:)を実装することで初めてComparableプロトコルに準拠したことになります。

結果として、Test型はComparableプロトコルに準拠しているため大小関係を比較できます。

***Array型***

配列を使用する際に必要になるArray型ではElement型がComparableプロトコルに準拠している場合に限り、sorted()などによる並び替えなどのメソッドで利用可能になります。

let array = [2, 1, 3]

let sortedArray = array.sorted() //[1,2,3]


操作系


IteratorProtocolプロトコル

このプロトコルは簡単にいうと繰り返しを行うためのプロトコルです。

そのためこれは、繰り返しのためのインターフェースを提供しており、next()メソッドを繰り返し呼ぶことで逐次要素を取り出します。

下記のコードを見るとnext()メソッドの戻り値はSelf.Elemen?型であり、次の要素が存在しなければnilを返すような仕組みになっています。つまり、連想型Elementがアクセス対象となる要素の型を表します。

public protocol IteratorProtocol {

associatedtype Element
public mutating func next() -> Self.Element?
}

使用例

struct IntIterator: IteratorProtocol {

var count = 0

mutating func next() -> Int? {
//falseの時に処理するguard文
guard count < 10 else{
return nil
}

//スコープを抜け出す際に処理するdefer文
defer {
count += 1
}
return count
}
}

var iterator = IntIterator()
var array = [Int]()

while let element = iterator.next() {
array.append(element)
}

array //[0、1、2、3、4、5、6、7、8、9]

上記の例は0から9までの要素を順次返すInteratorProtocolプロトコルの使用例です。return countで値を返すたびに、defer文で1足し、guard文でfalseになった時点でnilを返すような仕組みになっています。


Sequenceプロトコル

Sequenceプロトコルは要素の列挙を行うためのプロトコルです。このSequenceは列挙される連想型Elementと、先ほど記述したIteratorProtocolに準拠した連想型Iteratorを持ちます。

注意点としては、連想型Elementのインスタンスは連想型Iteratorによって逐次取り出されるため、連想型Elementは連想型Iterator.Elementと一致するという型制約が設けられていることに気をつけてください。

public protocol Sequence {

associatedtype Element where Self.Element == Self.Iterator.Element
associatedtype Iterator :IteratorProtocol
public func makeIterator() -> Self.Iterator
//省略
}

Sequenceプロトコルに準拠するためには、makeIterator()メソッドを実装し、IteratorProtocolプロトコルに準拠した型のインスタンスを返す必要があります。そして、このプロトコルの他に様々なメソッドがデフォルトで実装されているのでその一部を下記で紹介します。

forEach(_ :)メソッド

全ての要素に対してクロージャとして与えた処理を実行する

filter(_ :)メソッド

クロージャで指定した条件を満たす要素のみを含む新しいコレクションを返す

map(_ :)メソッド

それぞれの要素が、クロージャで指定した処理によって変換された新しいコレクションを返す

prefix(_ :)メソッド*

先頭n個分だけの要素を含むコレクションを返す

suffix(_ :)メソッド

末尾n個分だけの要素を含むコレクションを返す

使用例

struct IntIterator : InteratorProtocol {

var count = 0
mutating func next() -> Int? {
guard count < 10 else{
return nil
}
defer {
count += 1
}
return count
}
}

struct InSequence : Sequence {
func makeIterator() -> Interator {
return IntIterator()
}
}

struct IntSequence : Sequence {
func makeIterator() -> IntIterator {
return IntIterator()
}
}

let sequence = IntSequence()

var array = [Int]()
for element in sequence {
array.append(element)
}
array //[0,1,2,3,4,5,6,7,8,9]

let stringArray = sequence.map { element in
return "\(element)"
}

stringArray //["0","1","2","3","4","5","6","7","8","9"]

let filteredArray = sequence.filter { element in
return element % 2 == 0
}
filteredArray //[0,2,4,6,8]

上記のコードは、IntIterator型をIterator型とするSequenceプロトコルに準拠した型の実装例とSequenceプロトコルのメソッドの実装例です。foi-in文の中でIntSequence型の値が列挙可能になっていることがわかります。


Collectionプロトコル

Collectionプロトコルは、Sequenceプロトコルにサブスクリプトによる要素へのアクセス機能を加えたプロトコルです。要素へのアクセス機能のインタフェースはIndexableプロトコルで提供されており、CollectionプロトコルはIndexableプロトコルとSequenceプロトコルを継承しています。

public protocol Collection : Indexable, Sequence {

//省略
}

このCollectionプロトコルを準拠するには、先頭のインデックスを表すstartIndexプロパティ、末尾のインデックスを表すendIndexプロパティ、次のインデックスを返すindex(after:)メソッド、そしてサブスクリクトを実装する必要があります。

使用例

struct IntegerCollection : Collection {

var startIndex: Int {
return 0
}
var finishIndex: Int {
return 5
}
subscript(site :Int) -> Int {
return site
}
func index(after i: Int) -> Int {
return i + 1
}
}

let collection = IntegerCollection()
let element = collection[5] //5

let count = collection.count //5
let first = collection.first //0
let isEmpty = collection.isEmpty //false
let slice = collection[3...5] //Slice<IntegerCollection>

var array = [Int]()
for element in collection {
array.append(element)
}
array //[0,1,2,3,4,5]

上記のコードでは、CollectionプロトコルではSequenceプロトコルを継承しているので、for-in文を使用できています。

このCollectionプロトコルは標準ライブラリでは、Array型やDictionary型、Set型などがこのプロトコルに準拠しています。


エンコード・デコード系

データのエンコード・デコードを行う際にも、Swiftの標準プロトコルが使用されています。


Encodable、Decodable、Codableプロトコル

型をエンコードするためにはEncodableプロトコル、デコードはDecodableプロトコルを使用します。

public protocol Encodable {

public func encode(to encoder: Encoder) throws
}

public protocol Decodable {
public init(from decoder: Decoder) throws
}

次に型にエンコードとデコードどちらもできるようにするには、EncodableプロトコルとDecodableプロトコルを組み合わさったCodableプロトコルに準拠します。

public typealias Codable = Decodable & Encodable

このようにエンコード・デコード対応した方は、様々なエンコーダやデコーダに対応可能になります。

下記コードはJsonへのエンコード・デコードの記述になります。

使用例

import Foundation

struct Structs : Codable {
let value: Int
}

let someStruct = Structs(value: 5)

let jsonEncoder = JSONEncoder()
let encodedJSONData = try! jsonEncoder.encode(someStruct)
let encodedJSONString = String(data: encodedJSONData, encoding: .utf8)!
print("Encoded:", encodedJSONString)

let jsonDecoder = JSONDecoder()
let decodedSomeStruct = try! jsonDecoder
.decode(SomeStruct.self, from: endodedJSONData)
print ("Decoded:", decodedSomeStruct)

//実行結果
Encoded: {"value":1}
Decoded: SomeStruct(value: 1)

結果としてエンコードしてJSON型に、デコードしてStructs型になりました。