LoginSignup
76
53

More than 5 years have passed since last update.

Swiftのプロトコル

Last updated at Posted at 2017-12-18

プロトコルを使うメリット

  • 複数適用できる
  • 柔軟性が高い
  • 抽象化できる

基本文法

  • 多言語でいうインターフェース
  • classやstructやenumなどで使える。
  • 複数適用可能
  • @objc optional なメソッドを定義できる
  • デリゲートで使う

ドキュメント

Extensionで読みやすく

protocol SomeProtocol {
    var computedA: String { get }
    func methodA(_ str: String)
    func methodB(a: Int, b: Int) -> Int
}
class someClass {}

extension someClass: SomeProtocol {
    var computedA: String {
        return "a"
    }

    func methodA(_ str: String) {
        print(str)
    }

    func methodB(a: Int, b: Int) -> Int {
        return a+b
    }
}

let s = someClass()

s.computedA  // a
s.methodA("hello world") // hello world
s.methodB(a: 2, b: 5) // 7

こんな感じで書けるので凄く見やすい、てかほとんどこれがしたいだけです。
Xcodeだと補完がサクサク出てくるので楽チンです。
tableViewのDelegateとかもこうやって分けて書きます。

プロトコルは型なので、配列やメソッドの引数に使うことができます

protocol SomeProtocol {}

extension SomeProtocol {    
    func className() {
        print(String(describing: type(of: self)))
    }
}

class someClassA: SomeProtocol {}
class someClassB: SomeProtocol {}
class someClassC: SomeProtocol {}

let a = someClassA()
let b = someClassB()
let c = someClassC()

let someArray: [SomeProtocol] = [a,b,c]

someArray.forEach { $0.className() }
someClassA
someClassB
someClassC

Protocol Extension

プロトコルはそれに準拠する型に対して、特定のメソッド、 initializer, subscript, and computed property を実装することができる。

に準拠する各型でそれぞれ実装せずに、プロトコル自身に実装することができる。

protocol SomeProtocol { }

extension SomeProtocol {
    func sayHoge() {
        print("hoge")
    }
}

class someClass: SomeProtocol {}

let s = someClass()
s.sayHoge() // hoge

someClassはsomeProtocolをに準拠しており、SomeProtocolがsayHogeメソッドを実装しているので、someClassのインスタンスは"hoge"って言ってくれます。

Providing Default Implementations

protocol SomeProtocol { }

extension SomeProtocol {
    var someString: String {
       return "some"
    }
}

class someClass: SomeProtocol {}

let s = someClass()
s.someString // some

SomeProtocolでsomeStringをデフォルト実装しました、
SomeProtocolに準拠する型でsomeStringを実装した場合、デフォルト実装の代わりに、その型で実装したものが実行されます。

class someClass: SomeProtocol {
    var someString: String {
       return "conforming type implementation"
    }
}

let s = someClass()
s.someString // conforming type implementation

Adding Constraints to Protocol Extensions 型制約

where句特定の型に対して、Extensionを定義できます

protocol SomeProtocol {}

extension SomeProtocol {
    func sayHello() {
        print("hello")
    }
}

extension SomeProtocol where Self: SomeClass  {

    func someMethod(){
        print("instance of SomeClass can use this method")
    }
}
class SomeClass: SomeProtocol {}
class OtherClass: SomeProtocol {}

let s = SomeClass()
s.sayHello() //hello
s.someMethod() //instance of SomeClass can use this method

let o = OtherClass()
o.sayHello() //hello
o.someMethod() // error: 'OtherClass' is not a subtype of 'SomeClass'

SomeClassのインスタンスもOhterクラスのインスタンスも、SomeProtocolに準拠しているので、sayHelloメソッドを使えますが、someMethodは、SomeClassにのみ使うことができます。

特定の型が準拠することができるProtocol

Swift5から以下の二つは同じである。

protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ } 

下の書き方はSwift4.2だとランタイムエラーになったりした。
ので仕方なく下のように型制約をつけて書いてた。

protocol MyView {}

extension MyView where Self: UIView {

}

Swift5から型制約をつけずに以下のように書けるようになった。

protocol MyView: UIView { /*...*/ }

extension MyView {
    func setWhiteBackgroundColor {
        backgroundColor = UIColor.white
    }
}

associatedtype

標準ライブラリのプロトコル

Equatable

Hashable

CustomStringConvertible

プロトコルの実践的な使い方

プロトコルってどうゆう時に使うの?何が便利なのという疑問が

Reusable

ReusableプロトコルはreuseIdentifierをもち、デフォルト実装でクラス名の文字列返すようになっています。

protocol Reusable {
    static var reuseIdentifier: String { get }
}

extension Reusable {
    static var reuseIdentifier: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable {}

extension UITableView {

    func register<T: UITableViewCell>(_ cell: T.Type) {
        register(cell, forCellReuseIdentifier: cell.reuseIdentifier)
    }

    func dequeueReusableCell<T: UITableViewCell>(_ cell: T.Type, for indexPath: IndexPath) -> T {
        guard let cell = dequeueReusableCell(withIdentifier: cell.reuseIdentifier, for: indexPath) as? T else {
            fatalError("missing")
        }
        return cell
    }
}

tableView.register(CustomeCell.self)
tableView.dequeueReusableCell(CustomeCell.self, for indexPath)

APIクライアント

NSObjectを拡張してみる

 .swift
protocol Extensible {
    associatedtype ExtensibleType
    static var ex: Extension<ExtensibleType>.Type { get }
    var ex: Extension<ExtensibleType> { get }
}

extension Extensible {
    public static var ex: Extension<Self>.Type {
            return Extension<Self>.self
    }
    public var ex: Extension<Self> {
        return Extension(self)
    }
}

public struct Extension<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

extension NSObject: Extensible {}

NSObjectを継承する全てのクラスはexプロパティを持つようになりました。
型制約をつけて拡張することで、特定のクラスに対してexで区切ってメソッドなどを実装できるようになります。

extension Extension where Base: UIImageView {
    func load(from urlString: String) {
      // load image from urlString 
    }
}
let imageView = UIImageView()
imageView.ex.load(from: "url")

疎結合

テスト

クラスをプロトコルを使った抽象に依存させることでテストがしやすくなります。

protocol DataStoreProtocol {
    func getItem() -> Item
}

final class ViewModel {
   private var dataStore: DataStoreProtocol

    init(dataStore: DataStoreProtocol) {
       self.dataStore = dataStore
   }

   func getItem() -> Item {
        return dataStore.getItem()
   }
}
struct DataStoreStub: DataStoreProtocol {
    func getItem() -> Item {
      return Item()
    }
}
let viewModel = ViewModel(dataStore: DataStore())

swiftが柔軟なためほぼ共通処理はBaseClassのようなものを作らなくてもプロトコルで十分対応できますので気が向いたら使って見てください。
個人的にはクラスに武器を装備させるような感じで楽しいです。
授業中暇なので書いてみました。

(追記)
自分への戒めとして適当な投稿をしたのを消さずに残していたのですが、
やっぱり時間のある時にコンテンツを改善していこうと思います。

doc

76
53
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
76
53