Edited at

【swift】イラストで分かる!classとstructの違いについて【初心者向け】


概要

👨‍🏫「ここってstructで書いてるけど、意味とかあって書いた?」

🙍‍♀️「!」

🙍‍♀️「定数を並べただけのものだったので、そういうのはstructの方がいいかなって..」

👨‍🏫「ほーん」

🙍‍♀️「正直言うと意味なくstructにしました」

👨‍🏫「ほーん」

ということがあったので、

改めてそれぞれどういう違いがあるのかを改めて整理してみました٩( 'ω' )و


それぞれの特徴

先に、それぞれについての特徴をパッと簡単に確認。

イメージは@loveeさんの記事を参考にしています。

ポイントとなるのは参照型か値型か、ってところですね🤔

また、コードを書いてみると、


struct

struct test {

let cat: String
let dog: String
}
/// -> 怒られない


class

class test {

let cat: String
let dog: String
}
/// -> 💥怒られる: Class 'test' has no initializers

クラスの方は初期化して!と怒られます。


実装して違いを見てみよう

class Cat {

var like: String = "魚"
}
struct Dog {
var like: String = "肉"
}

クラスで作られた魚好きの🐱とストラクトで作られた肉好きの🐶。

let tama: cat = Cat()

let pochi: dog = Dog()

func initWithData(_ data: Cat) {
var data = data
data.like = "マタタビ"
}

func initWithData2(_ data: Dog) {
var data = data
data.like = "マタタビ"
}

initWithData(tama)
initWithData2(pochi)

それぞれを連れてきてマタタビをあげてみると、

print(tama.like) // →"マタタビ"

print(pochi.like) // →"肉"

という結果に。🐱は好物が魚からマタタビに書き換わりました。

この実行結果から見ても、

class🐱は参照渡しなので、

var data = dataで、cat()そのものが渡されており、

var tama: cat = cat()自体が書き換わっているのに対して、

struct🐶は値渡しなので、

var data = dataで、cat()のコピーが作られ、

var pochi: dog = dog()自体が書き換わることはありません٩( 'ω' )و

ご存知の通り?initで初期値を入れることもできます。

class Cat {

var like: String
init(like: String) {
self.like = like
}
}
struct Dog {
var like: String
init(like: String) {
self.like = like
}
}
let tama: cat = Cat(like: "魚")
let pochi: dog = Dog(like: "肉")

ただし、その場合は、

let tama: cat = Cat(like: "魚")

tama.like = "肉" // →"肉"
let pochi: dog = Dog(like: "肉")
pochi.like = "魚" // ERROR

となります。struct🐶は好き嫌いが激しいです。

これはstructは値渡しなので、例えて言うと、

実体(=参照型class🐱)に直接手を加えることはできるけど、

写真(=値型struct🐶)に直接手を加えることはできない(写真の加工とかは置いておいて..)

といったイメージで大丈夫だと思います٩( 'ω' )و


公式ドキュメント

Appleの公式ドキュメントにstructは以下の場面で使い、それ以外はclassを使用するのが良いという内容が書かれていました。(意訳)


  • structの目的は比較的単純なデータ値をカプセル化すること

  • インスタンスを代入または受け渡しするときは、カプセル化された値が参照ではなくコピーされる

  • structに格納されているプロパティはすべて値型であり、参照ではなくコピーされる

  • structは、他の既存の型からプロパティや動作を継承する必要はありません。


ついでに

ちなみにそもそも発端となったコードは以下のようなものでした。

/// カラーの拡張(各所で、'let xxx = UIColor.mainGreenColor()'などと呼ぶ)

extension UIColor {
// UIColorへの変換
private func hexColor(_ str: String) -> UIColor {
// (略)ここでカラーコード→UIColorに変える
return UIColor(red: x, green: x, blue: x, alpha: 1.0)
}
// メインカラー
@objc static func mainGreenColor() -> UIColor {
return self.hexColor(ColorCode.mainGreenColor)
}
// バッチカラー
@objc static func badgeColor() -> UIColor {
return self.hexColor(ColorCode.badgeColor)
}
// カラーコード
struct ColorCode { // ←⭐️ここ
static let mainGreenColor: String = "#219E62"
static let badgeColor: String = "#FF3300"
}
}

色々調べてみた結果としては、

「staticな変数だけをまとめたものだから、メモリを無駄に増やさないようにっていう観点でもclassを使うのがベターだな」

っていうのが理解できました😇


最後に

ちゃんと調べる前は、チームによってとか人によって結構好みの問題もあるんじゃないかとかって思っていたのですが、

ちゃんと意味を持った使い分けも出来るんだな、と改めて理解できました٩( 'ω' )و


最後の最後

delegate=selfとか諸々すごく端折っているのですが、

以下のコードのようなCatViewDataというデータを常に更新して保持するクラスを作り持ち回すという構成で書いたときに、

あまり考えずにstructを使った結果、データが全く更新(保持)されずで小一時間悩んで「んあぁぁああああ😭!!!!!」ってなったことがあったので、

結構動作に影響する場面があったりもするのでそいう意味でもちゃんと考えて使っていきたいところです。


/// データ保持用クラス
// ↓structにすると、データを書き換えても値型でコピーされるだけなので、呼び直すタイミングで消えてしまう
class CatViewData {
var name: String
init(name: String) {
self. name = name
}
}

/// VC&Table用クラス
class TableViewDataSource: UIViewController, UITableViewDelegate, UITableViewDataSource {
var viewData: CatViewData

// ここでテーブルを更新
func reloadTable(viewData: CatViewData) {
tableView.reloadData()
self.initWithData(viewData: viewData)
}

// ここでデータを更新
func initWithData(viewData: CatViewData) {
self.viewData = viewData
}

// ~~略~~

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CatCell") as! CatCell
cell.setSettingCell(viewData: viewData)
return cell
}

func reloadTable(viewData: CatViewData) {

}
}

/// 表示用のセルクラス
protocol delegate {
func reloadTable(viewData: CatViewData)
}
class CatCell: UITableViewCell {
var viewData: CatViewData
func setSettingCell(viewData: viewData) {
self.viewData = viewData
}
@IBAction func buttonTapped(sender:AnyObject) {
self.viewData = "(textViewから入力したデータ)"
delegate.reloadTable(viewData: self.viewData)
}
}

簡単ですが以上です。

誤っている箇所等ありましたらコメントいただけると幸いです。