目次
はじめに
Swiftには構造体、クラス、列挙型という3つの型の種類があり、それらにはプロパティやメソッドなどの共通した仕様があります。その一方で、三者それぞれに固有の使用も多く存在します。そこで今回は3つの型のうち、構造体とクラスの固有の使用について説明したいと思います。(列挙型についてはこちらをご覧ください)
構造体
構造体は値型の一種であり、ストアドプロパティの組み合わせによって1つの値を表します。標準ライブラリで提供されている多くの方は構造体であり、数値型、String型、Bool型などの基本的な型はすべて構造体です。
定義方法
構造体の定義にはstructキーワードを使用します。
struct 構造体名 {
構造体の定義
}
プロパティやメソッド、イニシャライザなどの型を構成する要素は構造体ですべて利用可能で、{ }内に定義できます。
struct Person {
let name: String
let age: Int
let height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
func printHeight() {
print(height)
}
}
let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.printHeight() // 170.5
ストアドプロパティの変更による値の変更
冒頭で述べたように構造体はストアドプロパティの組み合わせで1つの値を表す値型です。構造体のストアドプロパティを変更することは、構造体を別の値に変更することであり、構造体が入っている変数や定数への再代入を必要とします。
定数のストアドプロパティは変更できない
構造体のストアドプロパティの変更は再代入を必要とするため、定数に代入された構造体のストアドプロパティは変更できません。
struct Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1 = Person(name: "Taro Suzuki")
person1.name = "Hanako Yamada"
let person2 = Person(name: "Taro Suzuki")
person2.name = "Hanako Yamada" // 定数letに代入しているため再代入できず、コンパイルエラー
ちなみにクラスの場合は値型ではなく参照型であるため、エラーとなりません。
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1 = Person(name: "Taro Suzuki")
person1.name = "Hanako Yamada"
let person2 = Person(name: "Taro Suzuki")
person2.name = "Hanako Yamada" // 定数letに代入していてもOK。
メソッド内のストアドプロパティの変更にはmutaitingキーワードが必要
構造体のストアドプロパティの変更は再代入を必要とするため、ストアドプロパティの変更を含むメソッドにはmutatingキーワードが必要です。
struct Person {
var name: String
init(name: String) {
self.name = name
}
mutating func chageHanakoYamada() {
name = "Hanako Yamada"
}
}
var person1 = Person(name: "Taro Suzuki")
person1.chageHanakoYamada()
person1.name // "Hanako Yamada"
構造体のメソッド内のストアドプロパティの変更はコンパイラによってチェックされ、ストアドプロパティの変更を含んでいるにもかかわらずmutatingキーワードが付いていない場合はコンパイルエラーとなります。
struct Person {
var name: String
init(name: String) {
self.name = name
}
func chageHanakoYamada() {
name = "Hanako Yamada". // mutatingが付いていないのでコンパイルエラー
}
}
構造体のイニシャライザ
メンバーワイズイニシャライザ
型のインスタンスは初期化後にすべてのプロパティが初期化されている必要があります。そこで独自にイニシャライザを定義して初期化の処理を行うこともできますが、構造体では自動的に定義されるメンバーワイズイニシャライザ(memberwise initializer)というイニシャライザを利用できます。
メンバーワイズイニシャライザは、型が持っている各ストアドプロパティと同名の引数を取るイニシャライザです。
struct Person {
let name: String
let age: Int
let height: Double
// 以下と同等のイニシャライザが自動的に定義される
// init(name: String, age: Int, height: Double) {
// self.name = name
// self.age = age
// self.height = height
// }
}
let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.name // "Taro Suzuki"
person1.age // 28
person1.height // 170.5
デフォルトイニシャライザ
プロパティが存在しない場合や、すべてのプロパティが初期値を持っている場合、暗黙的にイニシャライザinit()が定義されます。
struct Person {
let name: String = "Taro Suzuki"
let age: Int = 28
let height: Double = 170.5
// 以下と同等のイニシャライザが自動的に定義される
// init() {}
}
let person1 = Person()
クラス
クラスは構造体と同様の構造を持つ型です。構造体との大きな違いは2つあり、一つは参照型であること、もう一つは継承が可能であることです。構造体や列挙型と比べると、プロパティやメソッドの文法、初期化のフローなどに違いがありますが、これは継承を考慮する必要があるためです。Cocoaのほとんどの型はクラスとして定義されています。
定義方法
クラスの定義にはclassキーワードを使用します。
class クラス名 {
クラスの定義
}
構造体と同様に、プロパティやメソッド、イニシャライザなどの型を構成する要素は構造体ですべて利用可能で、{ }内に定義できます。
class Person {
let name: String
let age: Int
let height: Double
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}
func printHeight() {
print(height)
}
}
let person1 = Person(name: "Taro Suzuki", age: 28, height: 170.5)
person1.printHeight() // 170.5
継承
継承とは、新たなクラスを定義するときに、他のクラスのプロパティ、メソッド、イニシャライザなどの型を再利用する仕組みです。継承先のクラスでは継承元のクラスと共通する動作をあらためて定義する必要がなく、継承元のクラスとの差分のみを定義すれば済みます。
継承先のクラスは継承元のクラスのサブクラス、継承元のクラスは継承先のクラスのスーパークラスと言います。
Swiftでは、複数のクラスから継承する多重継承は禁止されています。
定義方法
クラスに継承関係を定義するには、クラス名の後に「:」とスーパークラス名を記述します。
class クラス名: スーパークラス名 {
クラスの定義
}
オーバーライド
スーパークラスで定義されているプロパティやメソッドなどの要素は、サブクラスで定義できます(スタティックプロパティはできません)。これをオーバーライドと言います。
オーバーライドを行うには、overrideキーワードを使用してスーパークラスで定義されている要素を再定義します。
class クラス名: スーパークラス名 {
override func メソッド名(引数) -> 戻り値の型 {
メソッド呼び出し時に実行される文
}
override var プロパティ名: 型名 {
get {
return文によって値を返す処理
superキーワードでスーパークラスの実装を利用できる
}
set {
値を更新する処理
superキーワードでスーパークラスの実装を利用できる
}
}
}
オーバーライドしたプロパティやメソッドの中でsuperキーワードを使用することで、スーパークラスの実装を呼び出すこともできます。
次の例では、Userクラスで定義されているmessageプロパティとprintProfile()メソッドをRegisteredUserでオーバーライドしており、printProfile()メソッドでは、super.printProfile()によってスーパークラスの実装を呼び出しています。
class User {
let id: Int
var message: String { return "Hello." }
init(id: Int) {
self.id = id
}
func printProfile() {
print("id: \(id)")
print("message: \(message)")
}
}
class RegisteredUser: User {
let name: String
override var message: String { return "Hello, my name is \(name)." }
init(id: Int, name: String) {
self.name = name
super.init(id: id)
}
override func printProfile() {
super.printProfile()
print("name: \(name)")
}
}
let user = User(id: 1)
user.printProfile()
print("-------------")
let registeredUser = RegisteredUser(id: 2, name: "Yusei Nishiyama")
registeredUser.printProfile()
// 以下、実行結果
id: 1
message: Hello.
-------------
id: 2
message: Hello, my name is Yusei Nishiyama.
name: Yusei Nishiyama
finalキーワード
オーバーライド可能な要素の前にfinalキーワードを記述することで、その要素がサブクラスでオーバーライドされることを禁止できます。
次の例では、overridableMethod()メソッドはオーバーライドできますが、finalMethod()メソッドはfinalキーワードとともに定義されているため、オーバーライドしようとするとコンパイルエラーとなります。
class SuperClass {
func overridableMethod() {}
final func finalMethod() {}
}
class SubClass: SuperClass {
override func overridableMethod() {}
// オーバーライド不可能なためコンパイルエラー
override func finalMethod() {}
}
またクラス自体にfinalキーワードを付与することで、そのクラスを継承したクラスを定義することを禁止できます。次の例では、InheritableClasクラスは継承できますが、FinalClassクラスはfinalキーワードとともに定義されているため、継承しようとするとコンパイルエラーとなります。
class InheritableClass {}
class ValidSubClass: InheritableClass {}
final class FinalClass {}
// 継承不可能なためコンパイルエラー
class InvalidSubClass: FinalClass {}
クラスのイニシャライザ
指定イニシャライザ
指定イニシャライザ(designated initializer)はクラスの主となるイニシャライザで、このイニシャライザの中ですべてのストアドプロパティが初期化される必要があります。
class Mail {
let from: String
let to: String
let title: String
// 指定イニシャライザ
init(from: String, to: String, title: String) {
self.from = from
self.to = to
self.title = title
}
}
コンビニエンスイニシャライザ
コンビニエンスイニシャライザ(convenience initializer)は指定イニシャライザを中継するイニシャライザで、内部で引数を組み立てて指定イニシャライザを呼び出す必要があります。
コンビニエンスイニシャライザはconvenienceキーワードを追加することで定義できます。
class Mail {
let from: String
let to: String
let title: String
// 指定イニシャライザ
init(from: String, to: String, title: String) {
self.from = from
self.to = to
self.title = title
}
// コンビニエンスイニシャライザ
convenience init(from: String, to: String) {
self.init(from: from, to: to, title: "Hello, \(to).")
}
}
必須イニシャライザ
イニシャライザにrequiredキーワードを記述することで、クラスを継承する際、サブクラスが必ずそのイニシャライザを実装することを示すことができます。
class User {
let id: Int
required init(id: Int) {
self.id = id
}
}
class RegisteredUser: User {
required init(id: Int) {
super.init(id: id)
}
}
デフォルトイニシャライザ
構造体と同様にプロパティが存在しない場合や、すべてのプロパティが初期値を持っている場合、暗黙的に指定イニシャライザinit()が定義されます。
class User {
let id = 0
let name = "Taro"
// 以下と同等のイニシャライザが自動的に定義される
// init() {}
}
let user = User()
参考
・Swift実践入門 (https://gihyo.jp/book/2020/978-4-297-11213-4)
・公式リファレンス (https://docs.swift.org/swift-book/)