忘備録-Swiftの構造体

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。

## 参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

- [詳解 Swift 第5版](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3Swift-%E7%AC%AC5%E7%89%88-%E8%8D%BB%E5%8E%9F-%E5%89%9B%E5%BF%97/dp/481560407X)

##構造体の初期化
カスタムイニシャライザ : 構造体ごとに定義できる独自のイニシャライザ
カスタムイニシャライザが定義されてないとき、自動的にシンプルなイニシャライザが使える。
######既定イニシャライザ : 構造体の各プロパティに初期値が指定されている場合、型名(構造体の名前)()と書くことでインスタンスを生成できる。
```swift
struct Book {
    var name = "hoge book"
    var page = 125
}
var fuga = Book()
print(fuga.name, fuga.page)    // hoge book 125
```
######全項目イニシャライザ :  各プロパディ名を引数として書き連ねる。
```swift
var dogBook = Book(name: "dog book")
print(dogBook.name, dogBook.page)    // dog book 125
var oneBook = Book(page: 1)
print(oneBook.name, oneBook.page)    // hoge book 1
```
全項目イニシャライザを使用する場合はプロパティに初期値を設定する必要がない。
その代わりに初期値が設定されていないプロパティは必ず引数として値を渡す必要がある。

```swift
struct Book {
    var name : String
    var page = 125
}
var fuga = Book(name: "hoge book")    // hoge book 125
print(fuga.name, fuga.page)
var dogBook = Book(name: "dog book")    // dog book 125
print(dogBook.name, dogBook.page)
var oneBook = Book(page: 1)    // error
```
##定数に構造体を代入した場合
定数に代入された構造体のインスタンスは、各プロパディの値を変更できない。
この制約は構造体が値型であるためであり、クラスのインスタンスとは違う。(クラスのインスタンスは後ほど勉強します。)

```swift
struct Book {
    var name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"
print(hoge.name, hoge.page)    // fuga book 125
let fuga = Book(name: "hoge book")
fuga.name = "fuga book"    // error
```

##構造体に定数プロパティを宣言する場合
最初の一回しか値をセットできない。

```swift
struct Book {
    let name : String = "book"
    var page = 125
}
var hoge = Book(name: "hoge book")    // error
```
```swift
struct Book {
    let name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"    //error
```
##カスタムイニシャライザ
initを使って宣言

```swift
struct Book {
    var name : String
    var page : Int
    init() {
        name = " hoge book"    //全てのプロパディに対して値を設定しないとエラー
        page = 125
    }
}
var hoge = book()
print(hoge.name, hoge.page)
```

##複数個のイニシャライザ
引数やラベル名を変えて複数定義できる。

```swift
struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
var hoge = book()
print(hoge.name, hoge.page)     //hoge book 125
var dogBook = book(n: "dog book", p: 1)
print(dogBook.name, dogBook.page)    //dog book 1
```

##ネスト型(付属型)
ネスト型 : ある構造体、(クラス、列挙型)を構成、使用するために、その構造体と密接に関連する型(構造体)を定義する場合、構造体の一部として宣言できる。

```swift
struct SimpleBook {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
struct SellBook {
    typealias Book = SimpleBook    //上の構造体
    var book : Book
    var price : Int
    init(name: String, page: Int, price: Int) {
        book = Book(n: name, p: page)
        self.price = price    //プロパティ名と引数ラベルの名前が被っているのでself必須
    }
}
var hoge = SellBook(name: "hoge book", page: 125, price:50000)
print(hoge.book.name, hoge.book.page, hoge.price)    //hoge book 125 50000
```
##構造体のメソッド
構造体にもメソッドが定義できる。

```swift
struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
    func toMessage() -> String{
        "\(name) は \(String(page)) ページです。"
    }
}
var hoge = Book()
print(hoge.toMessage())    //hoge book は 125 ページです。
```

##構造体の内容を変更するメソッド
Swiftでは同じ構造体のインスタンスを出来るだけメモリで共有しておく方法を採用しているため、構造体の要素が変更されると共有方法も変える必要がある。
構造体の要素を変更するメソッドを定義する際には「mutating」というキーワードをfuncの前に置く。
コンパイラが要素を変更する処理を事前に知ることで、定数に格納されたインスタンに対しての呼び出し禁止、効率のいい実行コードの作成ができる。

```swift
struct Book {
    var name : String
    var page : Int
    var price : Int
    init() {
        name = "hoge book"
        page = 125
        price = 10000
    }
    init(name: String, page: Int, price: Int) {
        self.name = name
        self.page = page
        self.price = price
    }
    mutating func sell(b: Bool) {
        if b {
            price = Int(Double(price) * 0.8)
        } else {
            price = Int(Double(price) * 1.1)
        }
    }
}
var hoge = Book()
print(hoge.price)
hoge.sell(b: true)
print(hoge.price)

let fuga = Book()
fuga.sell(b: true)    //error
```


##タイプメソッド
これまで定義してきたメソッドはインスタンスに対してのみ使用できる。「インスタンスメソッド」に相当するものである。


```swift
struct Hoge{
  func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // 5
print(Hoge.add(3,2))    // error
```

これに対して、クラスメソッドに相当するメソッドを「タイプメソッド」という。
宣言するときはfunc の前にstaticをつける

```swift
struct Hoge{
  static func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // error
print(Hoge.add(3,2))    // 5 
```

##イニシャライザの中でメソッドを使いたい
イニシャライザの中ではプロパティの初期設定が全て完了していないと、インスタンスメソッドは使えない。

```swift
struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = self.toMessage(n: name, p: page)    // error
  }
  
  func toMessage(n name:String, p page:Int) -> String{
    "\(name) は \(String(page)) ページです。"
  }
}
```

一つの方法としてタイプメソッドで定義すれば実装できる。

```swift
struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = Self.toMessage(n: name, p: page)    //Book.toMessageとも書ける
  }
  
  static func toMessage(n name:String, p page:Int) -> String{
    "\(name) は \(String(page)) ページです。"
  }
}
var hoge = Book()
print(hoge.message)    //hoge book は 125 ページです。
```

##構造体のプロパティ
Swiftのプロパティの種類
格納型プロパティ : 定数や変数が機能を提供する
計算型プロパティ : 値の参照と更新機能手続で構成
タイププロパティ(静的プロパティ) : 型(構造体名)に関連する情報を示すプロパティ(どんなインスタンスにも共通の値をセットするといい。メモリの節約になる。)


##タイププロパティ


```swift
struct Book {
    static var name : String = "hoge book"
    static var page = 125
}
let hoge = Book()
print(hoge.name, hoge.page)    // error
print(Book.name, Book.page)    //hoge book 125
```

##格納型プロパティの初期値を式で設定
インスタンスを生成するたびにnumberプロパティがインクリメントされる。

```swift
var serialNomber: Int = 0
struct Book {
  var name : String
  var page : Int
  let nomber : Int = Book.setNo()
  init() {
    name = "hoge book"
    page = 125
  }
  
  static func setNo() -> Int{
    serialNomber += 1
    return serialNomber
  }
}

var book1 = Book()
print(book1.nomber)    // 1
var book2 = Book()
print(book2.nomber)    // 2
var book3 = Book()
print(book3.nomber)    // 3
```

##計算型プロパティ
他のプロパティの値を参照、計算して返す、代入することができる。

```swift
struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    get{
      Int(Double(price) * 1.1)
    }
    set{
      self.price = newValue
    }
  }
}

var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.tax = 3400
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740
```

setを省略する場合getの記述を省略してかける。
getだけを記述することは出来ない。

```swift
struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
      Int(Double(price) * 1.1)
  }
}
var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.price = 3400    //hoge.taxは使えなくなるので注意
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740
```
##特殊な設定
getにmutatingを記述 : プロパティの値を変えることができる。

```swift
struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  mutating get{    //getでプロパティ値を変えるときに記述
    price = Int(Double(price) * 1.1)
  }
  set{    
    self.price = newValue
  }
}
```

setにnonmutating : 背tで値を変更しないとき、構造体を定数に入れててもsetを呼べるようにする。

```swift
struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  get{
    Int(Double(price) * 1.1)
  }
  nonmutating set{
     print(newValue)
  }
}
let hoge = Book(price: 2500)
hoge.tax = 3400    //構造体が定数に格納されててもsetを呼べる
```

##プロパティオブザーバ
プロパティオブザーバ : 格納型プロパティの値が変更されるのをきっかけに
起動できる処理

```swift
struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    willSet{
      print("new : \(newValue)")    // new : 108
    }
    didSet{
      print("old : \(oldValue)")    // old : 110
    }
  }
}

var hoge = Book(price: 100, tax: 110)
hoge.tax = 108

```

##添字付け
添字付け：複数のプロパテがあるときに配列のように添字を使ってアクセスできるようにする方法

```swift
struct BookShelf {
    var book = ["hoge", "fuga", "inu"]
    var comic = ["JUMP", "oni"]
    var count = 5
    subscript(i: Int) -> String {
            return i < 3 ? book[i] : comic[i-3]
    }
}
let list = BookShelf()
for i in 0 ..< list.count {
print(list[i],terminator: " ")    // hoge fuga inu JUMP oni
}
```