77
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftのプロトコル

Last updated at Posted at 2017-12-18

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

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

基本文法

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

ドキュメント

Extensionで読みやすく

.swift
protocol SomeProtocol {
    var computedA: String { get }
    func methodA(_ str: String)
    func methodB(a: Int, b: Int) -> Int
}
.swift
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とかもこうやって分けて書きます。

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

.swift
protocol SomeProtocol {}

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

.swift
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() }
.swift
someClassA
someClassB
someClassC

Protocol Extension

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

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

.swift
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

.swift
protocol SomeProtocol { }

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

class someClass: SomeProtocol {}

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

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

.swift
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を定義できます

.swift
protocol SomeProtocol {}

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

extension SomeProtocol where Self: SomeClass  {
 
    func someMethod(){
        print("instance of SomeClass can use this method")
    }
}
.swift
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から以下の二つは同じである。

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

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

.swift
protocol MyView {}

extension MyView where Self: UIView {

}

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

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

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

associatedtype

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

Equatable

Hashable

CustomStringConvertible

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

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

Reusable

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

.swift
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
    }
}

.swift
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で区切ってメソッドなどを実装できるようになります。

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

疎結合

テスト

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

.swift
protocol DataStoreProtocol {
    func getItem() -> Item
}
.swift

final class ViewModel {
   private var dataStore: DataStoreProtocol
   
    init(dataStore: DataStoreProtocol) {
       self.dataStore = dataStore
   }

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

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

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

doc

77
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
77
53

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?