【Swift】構造体(struct)とは?クラスとの違いと使い方

この記事からわかること

  • Swift構造体とは?
  • 構造体の使い方
  • イニシャライザの実装方法
  • Memberwise Initializerとは?
  • 値型参照型
  • クラスと構造体の違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

Swiftの構造体の使い方についてまとめていきます。

構造体(struct)とは

Swiftには構造体と呼ばれるクラスと似たような構造、性質を持つデータ構造があります。

使い道はデータを決まった形式で保持するために使います。

基本的にはクラスと同じような扱いをすることができ、変数や関数を定義したり、インスタンス化する要領で変数に格納したりすることも可能です。

これもクラスと同様ですが構造体の中に定義した変数はプロパティ、関数はメソッドと呼ばれます。動作は一緒ですが呼び方が異なるので注意してください。

使い方と定義方法

クラス(class)と同じような形式で、構造体はstructで定義します。

struct 構造体名: プロトコル {
  let プロパティ
  var プロパティ

  init  () {
  }

  func メソッド () -> {
    return 戻り値
  }
}

構造体の中には定数、変数、イニシャライザ、関数が定義可能です。

おすすめ記事:【Swift】プロパティの種類!ストアドプロパティやタイププロパティとは?

構造体名はPascal記法(UpperCamelCase記法)、プロパティやメソッドはlowerCamelCase記法を用いることが推奨されています。

記法 説明
CamelCase記法 先頭は小文字で単語ごとに大文字 $myData
アンダースコア記法 小文字で単語の間をアンダースコア(_)で繋ぐ $my_data
Pascal記法 先頭と単語の区切りが大文字 $MyData

構造体型のデータを作成する

実際に定義した構造体に準じたデータを作成してみます。Xcodeでは構造体を定義すると入力補完が効いてくれるので構造体名を入力するだけで必要なプロパティを表示してくれます。

Swift/Xcodeで構造体を定義し入力補完が効いてくれている様子

定義した構造体に倣ったデータを扱うには変数の中に格納します。生徒の各教科の点数を保持させるStudent構造体を定義してみます。

インスタンス化してプロパティにアクセス

struct Student {
    var name:String
    var english:Int     // 英語
    var society:Int     // 社会
    var science:Int     // 理科
}

var user = Student(name :"ame" , english : 50 , society : 80 , science : 75)
print(user.name) // ame
print("英語:\(user.english)点") // 英語:50点
user.english += 49
print("英語:\(user.english)点") // 英語:99点

構造体を変数に格納するとプロパティやメソッドにはドットシンタックスを使ってアクセスできるようになります。変数に格納することをクラス同様にインスタンス化と呼びます。

プロパティの初期値

構造体を定義する際に初期値を与えることも可能です。初期値を与えることでインスタンス化時に、値を渡すことを省略することができます。

一方クラスではそもそも初期値を与えないとエラーになってしまいます。構造体では初期値を記述しなくてもエラーにはならないのでこれがクラスと構造体の違いの1つになります。

この理由は後述してありますのでそちらをご覧ください。

初期値を与える

struct Student {
    var name:String
    var english = 0     // 英語
    var society = 0     // 社会
    var science = 0    // 理科
}

初期値を与えた場合は型を指定しなくても格納された値に基づいた型を自動で定義してくれます。これを型推論と呼びます。

プロパティの種類による初期値

プロパティにはストアドプロパティ(stored property)コンピューテッドプロパティ(computed property)の2種類があります。ストアドプロパティは値を保持し、コンピューテッドプロパティは計算式を保持し、結果を返します。

struct Person {
  let name: String // ストアドプロパティ(stored property)
  var age: Int {   // コンピューテッドプロパティ(computed property)
    return 20
  }

  init(name: String) {
    self.name = name
    // ageの初期化は不要
  }
}

イニシャライザを使用する場合プロパティ全てに値を格納しないといけませんがコンピューテッドプロパティは値を格納する必要はありません。

構造体のプロパティをメソッドから変更する

構造体に定義しているメソッドからプロパティの値を変化させようとするとエラーが発生してしまいます。

エラーを発生させるコード

struct Student {
    var name:String
    var english:Int     // 英語
    var society:Int     // 社会
    var science:Int     // 理科

    func addScore(){
      self.english + 10
    }
}
Left side of mutating operator isn't mutable: 'self' is immutable
Mark method 'mutating' to make 'self' mutable

これを防ぐにはメソッドの前にmutatingをつけます。

mutatingで解決したコード

struct Student {
    var name:String
    var english:Int     // 英語
    var society:Int     // 社会
    var science:Int     // 理科

    mutating func addScore(){
      self.english += 10
    }
}

var user = Student(name :"ame" , english : 50 , society : 80 , science : 75)
user.addScore()
print("英語:\(user.english)点")  // 英語:60点

Swift UIを使用する場合では@Stateをつけることで解決することができます。両者の違いについては下記記事を参考にしてください。

イニシャライザ(init)

構造体には初期化を行うために作成時に自動実行される処理イニシャライザ(init)を実装することができます。イニシャライザの中では構造体で定義したプロパティにself.プロパティ名でアクセスできます。引数には各プロパティ名と型を列挙して構造体のプロパティに明示的に入れ込みます(初期化)。

struct Student {
    
    var name:String
    var english:Int     // 英語
    var society:Int     // 社会
    var science:Int     // 理科
    var sumScore:String // 英語と社会の合計
    
    init(name:String,english:Int, society:Int,science:Int){
        self.name = name
        self.english = english
        self.society = society
        self.science = science
        if(english+society)>130{
            self.sumScore = "A判定"
        }else{
            self.sumScore = "B判定"
        }
    }
}

let user = Student(name :"ame" , english : 50 , society : 80 , science : 75)
print(user.sumScore)  // B判定 

イニシャライザには定義しているプロパティ全てに何かしらの初期値を与えないと以下のようなエラーになってしまいます。なのでインスタンス化時の引数としてを入れ込んでいないself.sumScoreには自力で値を格納しないといけません。

Return from initializer without initializing all stored properties
// 翻訳:保存されているすべてのプロパティを初期化せずに初期化子から戻る

Memberwise Initilaizer

クラスは初期値の指定が必須でしたが構造体では指定しなくてもエラーになりません

これは構造体のみに実装されている「Memberwise Initializer」と呼ばれるイニシャライザによるものです。

引用:公式リファレンス:Initialization

Structure types automatically receive a memberwise initializer if they don’t define any of their own custom initializers.

リファレンスを見てみると上記のように明記されています。要約すると「構造体は、独自のイニシャライザを定義していない場合、メンバー(プロパティ)ごとの初期値を自動的に受け取る」ということになります。

Memberwise Initializerにより暗黙的に実装されている

struct Student {
    var name:String
    var english:Int     // 英語
    var society:Int     // 社会
    var science:Int     // 理科

//  暗黙的に自動で初期値を格納するイニシャライザが実行される
//     init(name:String,english:Int, society:Int,science:Int){
//         self.name = name
//         self.english = english
//         self.society = society
//         self.science = science
//     }
}

初期値を指定しない場合はインスタンス化時の引数で値を指定しないと以下のようなエラーになります。

Missing arguments for parameters 'name', 'english', 'society', 'science' in call

クラスと構造体の違い

クラスとの違いとポイント

両者の大きな違いはclassは参照型で構造体は値型であるところです。変数や引数などで操作した際に参照型はオリジナルのデータの格納場所が渡されるが、値型は複製された値が渡されます。

この挙動はインスタンス変数の値を変更すると分かりやすいです。

struct SName {
    var name:String
}

class CName {
    var name:String
}

var s = SName() // 構造体のインスタンス化
var c = CName() // クラスのインスタンス化

var sA = s // 変数に格納 値渡し
var cA = c // 変数に格納 参照渡し

s.name = "ame" // 元のインスタンスを変更
c.name = "ame" // 元のインスタンスを変更

print(sA.name) // ""    値型は元が変更されても関係なし
print(cA.name) // "ame" 参照型は元が変更されると更新

sA.name = "foo" // コピーしたインスタンスを変更
cA.name = "foo" // コピーしたインスタンスを変更

print(s.name) // "ame" 値型の元のインスタンスは変更されない
print(c.name) // "foo" 参照型は変更されると更新

値型

その時時点のデータをそのままコピーするだけなので、元のインスタンスとはリンクしていない

参照型

データが格納されているメモリの参照を渡すだけなので、「s」も「sA」も読みにいく場所は同一になるので変更が影響し合う

構造体にプロトコルを準拠させる

構造体にはプロトコルを指定することもできます。指定した場合はその構造体にはプロトコルに乗っ取ったプロパティやメソッドの定義が義務付けられます。

protocol human {
    var name: String { get set }
    var age: Int { get set }
    func selfIntroduction()
}

// humanプロトコルに準じた構造体を生成
struct TargetPerson: human {
    var name = "ame"
    var age = 15
    func selfIntroduction(){
        print("Hi! My Name is ame.")
    }
}

プロトコルに準じた構造体ではない場合は以下のようなエラーが発生します。

Type 'ContentView' does not conform to protocol 'View'
Do you want to add protocol stubs?

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index