Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
18
Help us understand the problem. What is going on with this article?
@Howasuto

【Swift】構造体とクラスの使い分け

構造体とクラス

Swiftで用いられるクラスと構造体は関数や変数など、プログラムを組む上で非常に便利な機能です。
どちらも表現力が豊かで、クラスで実現可能な事の大半は構造体でも再現可能だったりします。
それではどのような状況の時にクラスを用い、どんな時に構造体を使う選択をしていけば良いのでしょうか???

今回はそのクラスと構造体の使い方についてまとめていけたらと思います。

構造体の利点

まず以下のclassで実装したコードをみてください。
注目する点はPersonクラスをオブジェクト化したTomとMargaretのfavaritefoodのfood変数を呼び出しているところです。
本来であればTomがCake、MargaretがOrangeと別々の値を出力して欲しいのですが、Tomを生成してから変数の値を変えたにも関わらず、結果として同じ値が出力されています。
このようにクラスは参照型であるため、favaritefood変数を参照し、オブジェクトごとに値は保持せずにどのオブジェクトも共通の値を参照しにいきます。そのため、以下のようなプログラムにはクラスは向いていません。


class FavariteFood {
  var food: String = nil
}

class Person {
  var favaritefood: FavariteFood

  init(favaritefood: FavariteFood) {
    self.favaritefood = favaritefood
  }
}

let favaritefood = FavariteFood()
favaritefood.food = "Cake"
let Tom = Person(favaritefood: favaritefood)
favaritefood.food = "Orange"
let Margaret = Person(favaritefood: favaritefood)
Tom.favaritefood.food //Orange
Margaret.favaritefood.food //Orange

続いて今度は構造体で先ほどのプログラムを組んでみます。
すると先ほどとは違い、Personのオブジェクトごとに値が異なっています。
これは構造体が参照型ではなく、値型であるためそれぞれのPerson型のインスタンスが別々のFavariteFood型を保持しているためです。


struct FavariteFood {
  var food: String = nil
}

struct Person {
  var favaritefood: FavariteFood
}

let favaritefood = FavariteFood()
favaritefood.food = "Cake"
let Tom = Person(favaritefood: favaritefood)
favaritefood.food = "Orange"
let Margaret = Person(favaritefood: favaritefood)
Tom.favaritefood.food //Cake
Margaret.favaritefood.food //Orange

コピーオンライト

構造体にはコピーオンライトという機能がついています。
この機能は先ほどのように構造体を複数のオブジェクトとして生成した場合、そのオブジェクトが保持する値のコピーを必要になるまで行わないようにしてくれます。
以上の説明では分かりにくいと思うので噛み砕いて説明していきます。
下記のコードを見ると、二つ目に定義されている配列list2にlist1が代入されています。ここで勘違いされがちなのが、この時点で配列の内容がコピーされているという事です。実はこの時点では配列の値はコピーされておらず、その次の3行目でlistに新しい値が追加されて、値の内容に違いが生じた時に初めてコピーされます。
なぜこのような機能があるのかというと、Array型やDictionary型などのコレクションを表す型を使用する場合サイズの大きなデータを扱う可能性があるため、代入するたびにコピーを行なっていてはパフォーマンスの低下に繋がります。そのため、そのコピーの作業を値が異なった際に行う事で、その時点時点で必要のないコピー作業を省きパフォーマンスを向上させているのです。


var list = ["dog", "cat"]
var list2 = list
list.append("elephant")
list //["dog", "cat", "elephatn"]
list2 //["dog", "cat"]

クラスの利点

参照の共有

クラスの利点の一つとして値を参照して共有することが挙げられます。
以下のコードを見ると、クラスと構造体のオブジェクトごとにcountの値が異なっています。
これは参照型か値型かの違いによるものです。まず構造体型は変数などの値を参照せず、各オブジェクトごとに別々の値を保持しています。
そのため実行結果でcountを刻み出力していても、その値がもともとcount0を保持しているfirstExampleに影響を与え値が変更されるということはありません。
次にクラスをみていくと構造体とは異なり、secondExampleの値にも影響を与え値が変更されています。これはクラスが参照型であるため、同じ名称の変数の値を参照したためになります。
このように構造体とクラスには違いがあり、今回のケースであると数えたcountを保持して使用したい場合はクラスの方を使用するべきだと言えます。


protocol Example {
  var text: String {get set}
  var count: Int {get set}
  mutating func action()
}

extension Example {
  mutating func action() {
    count += 1
    print("text: \(text), count: \(count)")
  }
}

struct FirstExample : Example {
  var text = "Hello World"
  var count = 0

  init() {}
}

class SecondExample : Example {
  var text = "Hello ParallelWorld"
  var count = 0
}

Struct Counter {
  var example: Example

  mutating func start(){
    for _ in 0..<5 {
      example.action()
    }
  }
}

let firstExample : Example = FirstExample()
var counter1 = Counter(example: firstExample)
counter1.start()
firstExample.count // 0

let secondExample : Example = SecondExample()
var counter2 = Counter(example: secondExample)
counter2.start()
secondExample.count // 5

//実行結果
text: HellWorld, count: 1
text: HellWorld, count: 2
text: HellWorld, count: 3
text: HellWorld, count: 4
text: HellWorld, count: 5
text: HellParallelWorld, count: 1
text: HellParallelWorld, count: 2
text: HellParallelWorld, count: 3
text: HellParallelWorld, count: 4
text: HellParallelWorld, count: 5

インスタンスのライフサイクルでの処理

以下のコードを見るとインスタンス化後のdataの値は"a data"だが、インスタンスの値をnilにした後にはdataがnilになっています。
これは構造体にないクラスの特徴のデイニシャライザが影響しています。デイニシャライザはdenitにより使用でき、インスタンスが破棄された際に実行され、その時に一時ファイルも削除されます。そのため以下のコードのように初期化の際にはinitが実行され、破棄された際にはdeinitが実行されこのような結果になりました。
そのためインスタンスのライフサイクルに合わせて処理を行いたい場合は構造体よりもクラスを使用するべきだと言えます。


var data: String?

class SomeClass {
  init() {
    print("Build a data")
    data = "a data"
  }
  deinit {
    print("Reset adata")
    data = nil
  }
}

var someClass: SomeClass? = SomeClass()
data //a data

someClass = nil
data  //nil

//実行結果
Build a data
Reset a data

18
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Howasuto
iOSエンジニア
tresInnovation
当社は新しいアイディアから社会的意義のある新たな価値を創造し、社会に大きな変化をもたらす組織、社会の幅広い変革に貢献できるような会社を創る事をモットーにしております。VR事業、HR事業、EC事業、クリエイティブ事業、広告事業と多岐に渡る事業内容で世の中にワクワクを創造している会社です!

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
18
Help us understand the problem. What is going on with this article?