【Swift】プロトコルの使い方とメリットとは?実装を任意にする方法
この記事からわかること
- Swiftのプロトコルとは?
- メリットと使い方
- SwiftUIのViewプロトコル
- someの意味
- メソッドの実装を義務にしない方法
- optionalと@objcとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
Swiftのプロトコルに関することについてまとめていきます。
プロトコルとは?
Swiftにはプロトコルと呼ばれる規格を定義できる機能が存在します。プロトコルという言葉自体はSwiftに限ったものではなく、IT業界では「規約」と言った意味のニュアンスで使われています。
日常の中でもURLの接頭辞になっている「http」やファイル送信をするための「FTP」などデータをやり取りする際のフォーマット規約として目にすることも多いかと思います。
一方Swiftでのプロトコルは構造体やクラスなどを定義する際の構造規格の意味を持っています。Swiftにおけるプロトコルという概念は前身である「Objective-C」から実装されている仕様であり、同じような使い方でSwiftにも用いられています。
Objective-Cでのプロトコル
@protocol Character
- (void) openStatus;
@end
@interface brave:NSObject {}
@end
@implementation brave
- (void) openStatus{printf("ステータス");}
@end
使い方と特徴とメリット
プロトコルの使う手順は以下の通りです。
- プロトコルを定義する
- 構造体(クラス)の宣言時にプロトコルを指定する
プロトコルは構造体やクラスに指定して使用します。定義しただけでは意味がありません。
Swiftのプロトコルの大きな特徴は宣言したプロトコルに準拠した構造体やクラスを作成する際にそのプロトコルが定義しているプロパティとメソッドの実装が必須になることです。
実装し忘れるとエラーを吐いてしまうので、そもそもコンパイルできず動作が停止してしまいます。
プロトコルに準拠していない時のエラー
Type 'TargetPerson' does not conform to protocol 'human'
この仕様がプロトコルの大きなメリットでもあります。プロトコルを定義するだけで実装して欲しい処理などを強制することができ、自分以外の人が実装する場合でも実装漏れをなくすことができます。
特徴とメリット
- プロトコルは構造体(クラス)に指定して使う
- 変数の型としても指定可能
- 定義したプロパティとメソッドの実装を義務付け
- 実装漏れを防げる
Swiftにおけるプロトコルの定義
Swiftでプロトコルを定義するにはprotocol
をつけて以下のように宣言します。
protocol human {
var name: String { get set }
var age: Int { get set }
func selfIntroduction()
}
プロパティを定義する際は型の指定とともに{ get set }
も記述しておきます。プロトコル内には特定の値や処理は記述しなくてもOKです。
{ get set }を記述していない場合のエラー
Property in protocol must have explicit { get } or { get set } specifier
準拠した構造体を作成する
定義したプロトコルに準拠させた構造体を作成するには構造体名の後に:プロトコル名
形式で記述します。指定された構造体はプロトコルの持つプロパティなどの定義が必須になります。クラスなどに準拠させる場合も形式は変わりません。
// humanプロトコルに準じた構造体を生成
struct TargetPerson: human {
var name = "ame"
var age = 15
func selfIntroduction(){
print("Hi! My Name is ame.")
}
}
複数のプロトコルに準拠させる
// Characterプロトコルが定義されているとする
struct TargetPerson: human , Character {
var name = "ame"
var age = 15
func selfIntroduction(){
print("Hi! My Name is ame.")
}
}
クラスの場合はスーパークラスの後にプロトコル
クラスの場合、スーパークラスを指定することもありますがその際は先にスーパクラスを指定してから、後続に続いてプロトコルを指定します。
// NSObjectクラスをスーパークラスとする
class TargetPerson: NSObject, human {
var name = "ame"
var age = 15
func selfIntroduction(){
print("Hi! My Name is ame.")
}
}
Swift UIでのViewプロトコル
Swift UIの心臓部分でもあるViewプロトコル
を例として見てみます。Viewプロトコル
は読み取り専用のコンピューテッドプロパティであるbodyプロパティ
のみを持つプロトコルなのでbodyプロパティ
の定義が必須になります。
struct ContentView: View {
var body: some View {
}
}
someの意味
プロトコルの前についているsome
の意味が分からなかったので調べてみました。まずは定義を確認してみます。
Viewプロトコルの定義(command + クリックで表示)
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
associatedtype
は型を確定せずに準拠して決めることを定義する際に使用できるキーワードです。
それでもよく分からなかったのでググってみると以下の記事を参考にするとなんとなく分かった気がします。
つまりvar body: some View
のsome
はその型に準じた別の形を隠蔽できる記法のようです。上記の記事に恐ろしくわかりやすく解説されていました。
オプショナルメソッド(実装を必須としないようにする)
プロトコルの醍醐味である「実装を義務付ける仕様」ですが状況に応じては「必須にはしたく無いけどプロトコルとして定義したい」場合もあると思います。
これを解決するのはoptional
キーワードと@objc
修飾子です。この2つを以下のように付与するとこの場合はselfIntroduction
メソッドの定義は義務ではなくなります。
@objc protocol human {
var name: String { get set }
var age: Int { get set }
@objc optional func selfIntroduction()
}
optional
は言葉通り、nil
を許容できるようにしてます。@objc
の役割は指定したプロトコルやメソッドをObjective-Cとして認識させる意味合いがあるようです。
しかしこれは諸刃の剣のようで@objcを使うとSwiftの構造体への指定ができなくなってしまいました。クラスや変数への指定は問題なく可能でした。
@objcを使ったプロトコルを構造体に指定した場合のエラー
Non-class type 'TargetPerson' cannot conform to class protocol 'human'
この方法はタブーなのかと思いきやSwiftでは実際にoptional
が使用されたプロトコルが定義されていました。ですが@objc
は見当たらなかったので少し見当違いかも?
public protocol CLLocationManagerDelegate : NSObjectProtocol {
@available(iOS 6.0, *)
optional func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
}
ちなみに@available
はiOSのバージョンによって処理を分岐するための修飾子です。
extensionを使って実装不要メソッドを定義する
そもそも実装不要のメソッドを定義したい場合の別の解決方法もありました。
構造体やクラスなどでも使えるextension(拡張)
キーワードを使用すればプロトコルとして宣言しつつも実装を任意にすることが可能です。
protocol human {
var name: String { get set }
var age: Int { get set }
}
// 拡張して定義する
extension human {
func selfIntroduction(){}
}
// 拡張されているhumanプロトコルに準拠
struct TargetPerson: human {
var name = "ame"
var age = 15
// func selfIntroduction() 実装不要
}
プロトコルへのジェネリックプログラミング
Swiftのプロトコルに対してジェネリックプログラミングな実装をするために用意されているassociatedtype
キーワードが用意されています。
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。