目次

目次

【Swift】プロトコル(protocol)の使い方

アバター画像
深山侑花
アバター画像
深山侑花
最終更新日2021/12/18 投稿日2021/12/18

この記事はレコチョク Advent Calendar 2021の18日目の記事となります。

自己紹介

はじめまして、iOSアプリの開発をしている新卒1年目の深山です! 邦ロック・ジャニーズ・ボカロが大好きで、たくさんのアーティストの情報を追うのに必死な毎日です!

そんな私が、10月に配属されてから2ヶ月の間でiOSアプリの開発に触れて学んだ プロトコルについてまとめたいと思います。


1. はじめに

Swiftには他言語でいうインタフェースに似た、 プロトコル(protocol)という機能があります。

この記事では、

  • プロトコルとは
  • プロトコルの使用例
  • プロパティ
  • エクステンションの使い方

といった内容について解説していきます。

2. プロトコルとは

Swiftのプロトコルとは、クラスや構造体が保持するプロパティやメソッドなどの決まり事を定めるものです。 プロトコルを定義することによって、複数の型で共通の機能を実装することができます。

型宣言の時にプロトコルに従い、定められた機能を実装することをプロトコルに「準拠する」と言います。

3. プロトコルの使用例

プロトコルには、具体的な処理内容は書きません。 プロトコルに準拠しているクラスや構造体は、プロトコルに定義されているプロパティとメソッドなどを必ず実装しなければなりません。

プロトコルは、 protocolキーワードで定義することができます。 次の例ではプロトコル ArtistProtocolを定義しています。このArtistProtocolは制約としてplay()の実装を要求します。

protocol ArtistProtocol {
  func play()
}

型が ArtistProtocolに準拠するには、ArtistProtocolが要求しているメソッドplay()を実装しなければなりません。

以下は、構造体をプロトコルに準拠させる例です。

型名: プロトコルでプロトコルを記述します。

protocol ArtistProtocol {
  func play() 
}

struct Singer: ArtistProtocol {
  func play() {
    print("美声を披露")
  }
}

struct Guitarist: ArtistProtocol {
  func play() {
    print("ギターソロ")
  }
}

上記の2つの型 SingerGuitaristは、ArtistProtocolに準拠していると言えます。 この記述により、 SingerGuitaristは共通のメソッドplay()を実装する制約を文法レベルで担保することができます。

そして、 play()を実装しないと、プロトコルの要求を満たしていないとして、コンパイルエラーになります。

// error: type 'Singer' does not conform to protocol 'ArtistProtocol'
// struct Singer: ArtistProtocol {

// note: protocol requires function 'play()' with type '() -> ()'; do you want to add a stub?
//  func play() 

ここまでを次のコードで実行してみましょう。

let singer = Singer()
let guitarist = Guitarist()

singer.play()
guitarist.play()

すると以下の結果が出力されます。

美声を披露
ギターソロ

Singer型のインスタンスであるsingerは、play()を呼び出すことで、Singer内のplay()で定義されているように、美声を披露と出力します。

また、 Guitarist型のインスタンスであるguitaristは、play()を呼び出すことで、Guitar内のplay()で定義されているように、ギターソロを出力します。

同じ play()を呼び出しても、それぞれの型で実装した内容が実行される仕組みとなっています。

また、1つの型は複数のプロトコルに準拠することができます。 複数のプロトコルを準拠するには、次のように、カンマ区切りで宣言することができます。

protocol ArtistProtocol {
  func play() 
}

protocol SongwriterProtocol {
  func make() 
}

// カンマ区切りでプロトコルを記述
struct Singer: ArtistProtocol, SongwriterProtocol {
  func play() {
    print("美声を披露")
  }

  func make() {
    print("曲を作成")
  }
}

4. プロパティ

プロトコルは、制約として読み取り・書き込みのできるプロパティを定義することができます。

プロパティを定義するには、プロパティ名の型の横に、 { get set }または{ get }を記述します。

protocol プロトコル名 {
  var プロパティ名: 型 { get set }
}

プロパティの値を取得する際に getメソッドが呼ばれ、プロパティに値を代入する際にsetメソッドが呼ばれます。 setメソッドのみの定義はできません。

次の例では、 ArtistProtocolgetメソッドを定義し、AgeProtocolgetメソッドとsetメソッドを定義しています。

protocol ArtistProtocol {
  var name: String { get } 
}

protocol AgeProtocol {
  var age: Int { get set }
}

struct Singer: ArtistProtocol, AgeProtocol {
  var name: String
  var age: Int
}

次のコードを実行してみましょう。

var singer = Singer(name: "Mary", age: 25)

print(singer.name)
print(singer.age)

すると以下の結果が出力されます。

Mary
25

また、 singerageの値に30を代入し、同じようにprint文で出力してみましょう。

singer.age = 30

print(singer.name)
print(singer.age)

すると以下の結果が出力され、 ageの値が更新されたことが確認できます。

Mary
30

5. エクステンションの使い方

プロトコルを使うにあたって、エクステンション(extension)には次の2つの役割があります。

  1. プロトコルの準拠
  2. プロトコルの拡張

これらついて詳しく紹介していきます。

5-1. プロトコルの準拠

3.の後半で、複数のプロトコルの準拠はカンマ区切りでできることを紹介しました。

しかし、カンマ区切りで複数のプロトコルを記述すると、どのプロパティやメソッドが、どのプロトコルに準拠させているかわかりづらいという点があります。

そこで、エクステンションを使ってプロトコルを準拠させてみましょう。 エクステンションを使うと、プロトコルを1つずつ準拠できるので、可読性があがるというメリットがあります。 次の例では、2つのプロトコルをそれぞれエクステンションを使って準拠させています。

protocol ArtistProtocol {
  func play() 
}

protocol SongwriterProtocol {
  func make() 
}

struct Singer {}

extension Singer: ArtistProtocol {
  func play() {
    print("美声を披露")
  }
}

extension Singer: SongwriterProtocol {
  func make() {
    print("曲を作成")
  }
}

カンマ区切りで定義するよりも明確にプロトコルが区別されているのがわかります。

5-2. プロトコルの拡張

プロトコルは、エクステンションを使って拡張させることができます。 これはインターフェースを拡張するのではなく、実装の処理を追加するのに使われ、メソッドのデフォルト実装を持つことができます。

次の例では、 ArtistProtocolにメソッドを追加しています。 準拠先では、追加されたメソッドを実行することができます。

protocol ArtistProtocol {
  var name: String { get }
}

// extension を記述してプロトコルを拡張します
extension ArtistProtocol {
  func say() {
    print("Hello, I am \(self.name)!")
  }
}

struct Singer: ArtistProtocol {
  var name: String
}

次のコードを実行してみましょう。

say()はプロトコルに実装済みなので実行することができます。

let singer = Singer(name: "John")

singer.say()

すると以下の結果が出力されます。

Hello, I am John!

また、下記のように protocol内にもsay()を定義することで、任意の型のsay()に異なる処理を入れたいときにも対応できるようになります。

protocol ArtistProtocol {
  var name: String { get }
  func say()
}

extension ArtistProtocol {
  func say() {
    print("Hello, I am \(self.name)!")
  }
}

struct Singer: ArtistProtocol {
  var name: String
}

struct Guitarist: ArtistProtocol {
  var name: String
  func say() { // say()に異なる処理を入れる
    print("Hello, I am a super guitarist, \(self.name)!")
  }
}

次のコードを実行してみましょう。

let singer = Singer(name: "John")
let guitarist = Guitarist(name: "Michael")

singer.say()
guitarist.say()

すると以下の結果が出力されます。

Hello, I am John!
Hello, I am a super guitarist, Michael!

最後まで読んでいただき、ありがとうございました。

明日のレコチョク Advent Calendar 2021は19日目「Androidアプリ開発2ヶ月弱の新卒が思う、初学者でも参考になったサイト8選」です。お楽しみに!

参考

アバター画像

深山侑花

目次