備忘録
#はじめに
ふわっとしか理解していないのでQiitaに書こうと思いました。
#protocol
日本語に訳すと規定
や約束事
という意味合いになります。
Swiftにおいては、プロパティやメソッドを定義
することができて
クラスや構造体に準拠されること前提
で使われます。
protocol HogeProtocol {
var hoge: String { get }
func doHoge()
}
###・使い方
protocolは冒頭でも述べたようにクラスや構造体などに
準拠されること前提で使われます。
なので使用する場合は準拠することから始まります。
そして、protocolで定義したものは必ず実装しなければなりません
。
class Hoge: HogeProtocol {
// 実装しなければエラーが発生する
// var hoge: String = ""
func doHoge() {
print("doHoge")
}
}
###・複数準拠できる
クラスの場合、複数の継承はエラーが発生してしまいます。
ですが、protocolでは複数準拠
が可能です。
protocol FugaProtocol {
var fuga: String { get }
func doFuga()
}
class HogeFuga: HogeProtocol, FugaProtocol {
var hoge: String = ""
var fuga: String = ""
func doHoge() {
print("doHoge")
}
func doFuga() {
print("doFuga")
}
}
###・protocolもprotocolを準拠できる
タイトル通り、protocol自身もprotocolを準拠できます
。
// 先ほど定義したProtocol達をまとめる
protocol HogeFugaProtocol: HogeProtocol, FugaProtocol {
var hogeFuga: String { get }
}
class HogeFuga: HogeFugaProtocol {
var hogeFuga: String = ""
var hoge: String = ""
var fuga: String = ""
func doHoge() {
print("doHoge")
}
func doFuga() {
print("doFuga")
}
}
###・protocol extension
基本的にprotocolはデフォルト実装ができません
。
なので準拠したクラスなどで具体的な処理内容を記述しなければいけません。
ですが例外があり、extention
によってデフォルト実装が可能
になります。
// デフォルト実装を追加する
extension HogeProtocol {
func doHoge() {
print("doHoge")
}
}
// デフォルト実装しているので実装しなくてもエラーが発生しない
class HogeFuga: HogeFugaProtocol {
var hogeFuga: String = ""
var hoge: String = ""
var fuga: String = ""
// func doHoge() {
// print("doHoge")
// }
func doFuga() {
print("doFuga")
}
}
let hogeFuga = HogeFuga()
hogeFuga.doHoge() // "doHoge"
#protocolのメリット
protocolについてよく分かったけど
結局何がいいの?どこで使うの??って感じですよね。
protocolのメリットを備忘録を含めて
つらつらと書いていこうと思います。
###・共通化できる
例えば、各動物の走ったり泳いだりなどの速さを出力したいとします。
####◯protocolを使わない場合
まずは大元のAnimalクラス
を作ります。
// 動物クラス
class Animal {
var runningSpeed: Int?
var swimmingSpeed: Int?
func run() {
guard let runnningSpeed = runnningSpeed else { return }
print("走るスピードは\(String(describing: runnningSpeed))km/h です")
}
func swim() {
guard let swimmingSpeed = swimmingSpeed else { return }
print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です")
}
}
そして、Humanクラス
を作って
Animalクラスを継承します。
// 人間クラス
class Human: Animal {
override init() {
super.init()
runningSpeed = 36
swimmingSpeed = 8
}
}
では他にも、色んな動物クラスを追加していきましょう。
// 鳥クラス
class Birds: Animal {
}
// 犬クラス
class Dog: Animal {
}
新たに作ったBirdsクラス
では飛ぶ速さを出力したいと思います。
ということは飛ぶ速さを出力するメソッド等を
大元のAnimalクラス
に追加しないといけませんね。
ついでにBirdsクラス
・Dogクラス
で速さが出力できるようにしていきましょう。
// 動物クラス
class Animal {
var runningSpeed: Int?
var swimmingSpeed: Int?
var flyingSpeed: Int?
func run() {
guard let runnningSpeed = runnningSpeed else { return }
print("走るスピードは\(String(describing: runnningSpeed))km/h です")
}
func swim() {
guard let swimmingSpeed = swimmingSpeed else { return }
print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です")
}
func fly() {
guard let flyingSpeed = flyingSpeed else { return }
print("飛ぶスピードは\(String(describing: flyingSpeed))km/h です")
}
}
// 鳥クラス
class Birds: Animal {
override init() {
super.init()
flyingSpeed = 340
}
}
// 犬クラス
class Dog: Animal {
override init() {
super.init()
runningSpeed = 50
swimmingSpeed = 4
}
}
これで出力できそうですね。
ただこれらのコードだと、いくつか欠点があります。
本来、呼び出す必要がないメソッド等が
外部で呼び出せてしまいます
。
let human = Human()
let birds = Birds()
let dog = Dog()
human.run() // 走るスピードは36km/h です
human.fly() // 何も出力されない
birds.fly() // 飛ぶスピードは340km/h です
dog.fly() // 何も出力されない
後はAnimalクラス
がどんどん肥大化
してしまうという点です。
様々なクラスに対応させるために大元のクラスに色々、詰め込んでしまっては膨大なコード量
になってしまって
可読性も良くない
、あるクラスでは必要ないプロパティやメソッド
などが大量生産されてしまいます。
####◯protocolを使った場合
では、protocolで実装していきます。
protocol RunProtocol {
var runningSpeed: Int { get set }
func run()
}
// 同じ処理の部分はextentionでデフォルト実装していく
extension RunProtocol {
func run() {
print("走るスピードは\(String(describing: runningSpeed))km/h です")
}
}
protocol SwimProtocol {
var swimmingSpeed: Int { get set }
func swim()
}
extension SwimProtocol {
func swim() {
print("走るスピードは\(String(describing: swimmingSpeed))km/h です")
}
}
protocol FlyProtocol {
var flySpeed: Int { get set }
func fly()
}
extension FlyProtocol {
func fly() {
print("走るスピードは\(String(describing: flySpeed))km/h です")
}
}
そして、先ほどのクラス達に準拠していきます。
// 動物クラス
class Animal {
}
// 人間クラス
class Human: Animal, RunProtocol, SwimProtocol {
var runningSpeed: Int = 36
var swimmingSpeed: Int = 8
}
// 鳥クラス
class Birds: Animal, FlyProtocol {
var flySpeed: Int = 340
}
// 犬クラス
class Dog: Animal, RunProtocol, SwimProtocol {
var runningSpeed: Int = 50
var swimmingSpeed: Int = 4
}
let human = Human()
let birds = Birds()
let dog = Dog()
human.run() // 走るスピードは36km/h です
human.fly() // エラー
birds.fly() // 走るスピードは340km/h です
dog.fly() // エラー
見て分かるようにprotocolに定義されていないものはエラー
となります。
そして、protocolでは定義したものは必ず実装しなければいけないルールなので
どのプロパティに初期化が必要なのか
を考慮しなくてもよくなります。
これで必要ないものが外部で呼び出されることはなくなりました
。
このように部分的に処理を共通化させる事によって
必要な処理のみを実装できて可読性が向上
します。
###・ファイル間の依存関係を薄くできる
例えば、TableViewCellをタップした時に
そのCellに付随する値を違うVCに受け渡し、そのVCに画面遷移するとします。
####◯protocolを使わない場合
単純に実装すれば以下のようになると思います。
class SecondViewController: UIViewController {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 受け渡す値
let model = models[indexPath.row]
let firstVC = FirstViewController()
// ここで他のVCに値を受け渡す
firstVC.text = model.text
// 画面遷移
self.navigationController?.popViewController(animated: true)
}
}
かなり雑ですが、こんな感じで他のVCに値を受け渡すことが出来ます。
ただこのコードだとSecondViewController
は
FirstViewController
が存在していないとエラーになります。
いわゆる密結合
になっている状態です。
つまり、VC間の依存関係が濃い
訳ですね。
####◯protocolを使った場合
ここでprotocolを使ったDelegateで依存関係を薄く
していきます。
protocol ToPassDataDelegate: AnyObject {
func didSelectData(model: Model)
}
class SecondViewController: UIViewController {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = models[indexPath.row]
// cellをタップした時に値を渡して、それ以降の処理は任せる
delegate?.didSelectData(model: model)
self.navigationController?.popViewController(animated: true)
}
}
どこにもFirstViewController
が記述されていないことが分かりますね。
もちろん、存在していなくてもエラーになることはありません
。
このように依存関係を薄くすることによって
たとえ違うVCに変更したとしても影響がなく、改修しやすい
ものとなります。
これがいわゆる疎結合
ですね。
先ほど出てきたUITableViewクラスも内部でprotocolを使ったDelegateを持っています。
依存関係が薄いからこそ、私たちは様々な処理ができるようになっている訳ですね。
###・構造体や列挙型に準拠できる
構造体や列挙型はクラスのように継承
が出来ません。
ですが、protocolを使うことで
構造体や列挙型でも同じようなことができます。
protocol HogeProtocol {
var hoge: String { get }
}
struct Fuga: HogeProtocol {
var fuga: String
var hoge: String
}
#おわりに
Swiftは唯一の「Protocol指向
」のプログラミング言語だそうです。
何か間違っている部分や気になる部分があればコメントして下さると有り難いです。
#参考資料
mixi-inc/iOSTraining
Swiftのプロトコルについて
iOSアプリの基本設計を考える:疎結合の概念から構造化、MVVM、RxSwiftまで
Swiftのプロトコルとは?役割・使いどころ・存在意義やメリット。