この記事はレコチョク 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つの型Singer
とGuitarist
は、ArtistProtocol
に準拠していると言えます。
この記述により、Singer
とGuitarist
は共通のメソッド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
メソッドのみの定義はできません。
次の例では、ArtistProtocol
でget
メソッドを定義し、AgeProtocol
でget
メソッドと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
また、singer
のage
の値に30を代入し、同じようにprint文で出力してみましょう。
singer.age = 30
print(singer.name)
print(singer.age)
すると以下の結果が出力され、age
の値が更新されたことが確認できます。
Mary
30
5. エクステンションの使い方
プロトコルを使うにあたって、エクステンション(extension)には次の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選」です。お楽しみに!
参考
この記事はレコチョクエンジニアブログの記事を転載したものとなります。