はじめに
なんとなく使ってるprotocol
enum
struct
class
についてガチガチに調べました
目次
- protocolに関して
- enumに関して
- structに関して
- classに関して
-
値型
と参照型
の違い - structとclassの使い分け
- 用語
protocol
に関して
参考にしたサイトでは「Swiftのプロトコルは、型のインターフェースを定義するものです。」と書いてありました。
-
型のインターフェイスとは?
たとえば、ある型(クラスや構造体)がどんな機能や性質をもっているか説明するもの
よくわからないので実装例を見ていきます。
protocol Playable {
func play()
}
struct Toy: Playable {
func play() {
print("おもちゃで遊ぶよん")
}
}
さっき書いた説明に当てはめると「構造体(Toy
)がplay()
という機能/性質を持ってるよ」ってことです。
- でもこれ
Playable
つくんなくて良くない?
おっしゃる通りだと思います。
しかし、処理が増えてくるとprotocolを作らないとめんどくさくなります
例えばprotocol
なしだと
struct Toy {
func play() {
print("おもちゃで遊ぶよん")
}
}
struct Game {
func play() {
print("ゲームで遊ぶよん")
}
}
// `Toy`型専用の関数
func startPlayingToy(toy: Toy) {
toy.play()
}
// `Game`型専用の関数
func startPlayingGame(game: Game) {
game.play()
}
let toy = Toy()
let game = Game()
startPlayingToy(toy: toy) // 出力: おもちゃで遊ぶよん
startPlayingGame(game: game) // 出力: ゲームで遊ぶよん
startPlayingToy()
とstartPlayingGamestartPlayingGame
の2つ関数を作らないといけないのですがprotocol
を使うと
protocol Playable {
func play()
}
struct Toy: Playable {
func play() {
print("おもちゃで遊ぶよん")
}
}
struct Game: Playable {
func play() {
print("ゲームで遊ぶよん")
}
}
func startPlaying(item: Playable) {
item.play()
}
let toy = Toy()
let game = Game()
startPlaying(item: toy) // 出力: おもちゃで遊ぶよん
startPlaying(item: game) // 出力: ゲームで遊ぶよん
1つの関数で済むようになります。
- その他、
ObservableObject
やDecodable
もprotocolの1つですね。
enum
に関して
列挙型とは、値型の一種で、関連する値のグループに共通の型を定義してまとめたもの
これは例を見れば簡単です
enum Game {
case monsterHunter
case dragonQuest
case smashBros
case splatoon
}
こんな感じでGame
の中にモンハン、ドラクエ、スマブラ、スプラトゥーンが入ってる感じですね。
func printGameMessage(game: Game) {
switch game {
case .monsterHunter:
print("モンスターハンターで狩りをしよう!")
case .dragonQuest:
print("ドラゴンクエストのRTAが待っている!")
case .smashBros:
print("スマッシュブラザーズでバトルしていらつこう!")
case .splatoon:
print("スプラトゥーンで味方にイラつこう!")
}
}
使う時はこんな感じでGame.(caseで決めた値)
のように設定します。
struct
に関して
簡単にいうと継承できない値渡しの、変数や関数をまとめた固まりみたいなもの
class
に関して
継承できる参照渡しの、変数や関数をまとめた固まりみたいなもの
値型
と参照型
の違い
ここまで読んでくれてる人に「値渡しやら参照渡しやらうっとうしいわ」みたいなことを言われそうなのでまずこちらを解説していきます。
まずは例から
値型
struct Woman {
var name: String
var isCute: Bool
}
var woman1 = Woman(name: "長濱ねる", isCute: true)
var woman2 = woman1
woman2.name = "フワちゃん"
woman2.isCute = false
print("woman1")
print(woman1)
print("woman2")
print(woman2)
上の例で実行すると
woman1
Woman(name: "長濱ねる", isCute: true)
woman2
Woman(name: "フワちゃん", isCute: false)
と出力されます。
参照型
一方で
class Woman {
var name: String
var isCute: Bool
init(name: String, isCute: Bool) {
self.name = name
self.isCute = isCute
}
}
var woman1 = Woman(name: "長濱ねる", isCute: true)
var woman2 = woman1
woman2.name = "フワちゃん"
woman2.isCute = false
print("woman1: \(woman1.name), isCute: \(woman1.isCute)")
print("woman2: \(woman2.name), isCute: \(woman2.isCute)")
とstruct
で書いていたところをclass
で書き直すと
woman1: フワちゃん, isCute: false
woman2: フワちゃん, isCute: false
とprintされます。
まとめ
違いが分かりましたでしょうか?
structはvar woman2 = woman1
のタイミングでwoman1
のコピーを渡してる感じです。
コピーされた紙に書いたものはコピーする前の紙には反映されないですよね?
それと同じことをしてます。
classはvar woman2 = woman1
のタイミングでコピーではなくそのものを渡してます。
コピー取らずにそのままの紙に書き込んでるイメージです。
なので
woman2.name = "フワちゃん"
woman2.isCute = false
の時点でwoman1も書き換わり、お前は偉くないので⚪︎んでくださーいされます。
struct
とclass
の使い分け
classを使うとき
- ViewModel・・・理由は、画面の状態やデータを複数のViewで共有したり、変更を他の場所に通知する必要があるため
- Usecase・・・理由は、ビジネスロジックを持ち、状態の変更や複数箇所で共有が必要になるためclassが適している
- Repository・・・理由は、APIやデータベースとのやり取りを行い、同じインスタンスでデータの管理が必要なことが多いためclassが適している
- APIClient・・・理由は、APIとの通信を管理し、単一のインスタンスでネットワーク接続やリクエストの管理を行う必要があるためclassが適している
structを使うとき
- Model・・・理由は、データを保持するだけの役割が多く、軽量なデータ構造を扱うため、コピーで扱えるstructが適している
あくまで一般的になので状況に応じて臨機応変に変えていくことが大事(丸投げ)
用語
参考文献
protocol
https://swift.codelly.dev/guide/%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB/
enum
https://swift.codelly.dev/guide/%E5%88%97%E6%8C%99%E5%9E%8B/
struct
https://qiita.com/yuinchirn/items/98b568d595650eca3334
https://qiita.com/motoki418/items/156182ec8750e7b567b9
https://zenn.dev/egg_head/articles/fd48844e86ad7b
値型
と参照型
の違い