iOS
Protocol
Swift
SwiftDay 17

プロトコルの活用実例を観察する

More than 1 year has passed since last update.

EquatableやSequenceType、その他色々なプロトコルが用意されていますが、実際のコードでどのような使えばいいのかがいまいち思い描けないものもあったので、GitHubに上がっているコードを見て学習しようと思います。比較的よく使いそうなプロトコルに絞って見てみます。
プロトコル名にはApple公式ドキュメントのリンク、実例には該当のコードが載っているページのリンクを貼っているので、詳しく見たい方はリンクをクリックしてください。

Equatable

値が等しいかどうかの比較が可能な型を表すプロトコルです。定義すべき演算子はfunc == (lhs: Self, rhs: Self) -> Boolです。==を定義すると自動的に!=も使えるようになるそうです。

facebook-sdk-swiftの例

extension Photo: Equatable {
  public static func == (lhs: Photo, rhs: Photo) -> Bool {
    return lhs.sdkPhotoRepresentation == rhs.sdkPhotoRepresentation
  }
}

realm-cocoaの例

extension Schema: Equatable {}

public func == (lhs: Schema, rhs: Schema) -> Bool { // swiftlint:disable:this valid_docs
    return lhs.rlmSchema.isEqualToSchema(rhs.rlmSchema)
}

Comparable

大小比較が可能な型を表すプロトコルで、Equatableを継承しています。そのためEquatableの演算子==はもちろん利用できます。演算子<を定義すると自動的に>, <=, >=も利用できるようになります。func < (lhs: Self, rhs: Self) -> Boolfunc == (lhs: Self, rhs: Self) -> Boolは実装する必要があります。

SwiftyJSONの例

public enum JSONIndex:Comparable
{
    case array(Int)
    case dictionary(DictionaryIndex<String, JSON>)
    case null

    static public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool
    {
        switch (lhs, rhs)
        {
        case (.array(let left), .array(let right)):
            return left == right
        case (.dictionary(let left), .dictionary(let right)):
            return left == right
        case (.null, .null): return true
        default:
            return false
        }
    }

    static public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool
    {
        switch (lhs, rhs)
        {
        case (.array(let left), .array(let right)):
            return left < right
        case (.dictionary(let left), .dictionary(let right)):
            return left < right
        default:
            return false
        }
    }
}

SwiftDateの例

public struct DateTimeInterval : Comparable {
    public static func ==(lhs: DateTimeInterval, rhs: DateTimeInterval) -> Bool {
        return lhs.start == rhs.start && lhs.duration == rhs.duration
    }

    public static func <(lhs: DateTimeInterval, rhs: DateTimeInterval) -> Bool {
        return lhs.compare(rhs) == .orderedAscending
    }
}

CustomStringConvertible

selfを表す文字列表現を返す時に使います。NSObjectを継承しているオブジェクトであればdescription関数をprintすると何かしら出力されますが、カスタムクラスだとそれがないので自分でdescription関数を用意する場合はこれを使うことになります。

facebook-sdk-swiftの例

extension AppEventName: CustomStringConvertible {
  public var description: String {
    return rawValue
  }
}

ExpressibleByStringLiteral

ExpressibleByExtendedGraphemeClusterLiteralを継承し、それがさらにExpressibleByUnicodeScalarLiteralを継承しています。任意の長さの文字列リテラルを使用して初期化できます。String, StaticStringはこのプロトコルを継承しています。

realm-cocoaの例

extension SortDescriptor: ExpressibleByStringLiteral {

    public typealias UnicodeScalarLiteralType = StringLiteralType
    public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType

    public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
        self.init(property: value)
    }

    public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
        self.init(property: value)
    }

    public init(stringLiteral value: StringLiteralType) {
        self.init(property: value)
    }
}

IteratorProtocol

シーケンスの値を1つずつ供給するプロトコルなので、Sequenceプロトコルと密接に関係しています。Sequenceはイテレータを作成することによって要素にアクセスできます。Swiftはシーケンスまたはコレクションのイテレータを内部的に使用することによってfor-inループを可能にしています。
※Swift3で名前がGeneratorTypeからIteratorProtocolになりました。

realm-cocoaの例

public final class RLMIterator: IteratorProtocol {
    private let iteratorBase: NSFastEnumerationIterator

    internal init(collection: RLMCollection) {
        iteratorBase = NSFastEnumerationIterator(collection)
    }

    public func next() -> RLMObject? {
        return iteratorBase.next() as! RLMObject?
    }
}

Sequence

for-inで繰り返しが可能な型を表すプロトコルです。
※Swift3で名前がSequenceTypeからSequenceになりました。
※繰り返しのためにインスタンス自体を変化させる可能性があるので安全な繰り返しにはCollectionTypeを使った方が良いようです。

Surgeの例

extension Matrix: Sequence {
    public func makeIterator() -> AnyIterator<ArraySlice<Element>> {
        let endIndex = rows * columns
        var nextRowStartIndex = 0

        return AnyIterator {
            if nextRowStartIndex == endIndex {
                return nil
            }

            let currentRowStartIndex = nextRowStartIndex
            nextRowStartIndex += self.columns

            return self.grid[currentRowStartIndex..<nextRowStartIndex]
        }
    }
}

Kingfisherの例

struct BytesSequence: Sequence {
    let chunkSize: Int
    let data: [UInt8]

    func makeIterator() -> BytesIterator {
        return BytesIterator(chunkSize: chunkSize, data: data)
    }
}

Collection

Sequence、Indexableを継承しています。Sequenceプロトコルから継承するメソッドに加え、特定の位置にある要素にアクセスできるメソッドも使うことができるようになります。
※Swift3で名前がCollectionTypeからCollectionになりました。

AudioKitの例

internal struct MIDIDestinations: Collection {
    typealias Index = Int

    init() { }

    var startIndex: Index {
        return 0
    }

    var endIndex: Index {
        return MIDIGetNumberOfDestinations()
    }

    subscript (index: Index) -> MIDIEndpointRef {
      return MIDIGetDestination(index)
    }

    func index(after index: Index) -> Index {
      return index + 1
    }
}