スウィフトのinit()
このSwiftのチュートリアルでは、重要な概念である「Swift init」または「Swiftの初期化」について話し合います。初期化は、ある型のインスタンスを作成する際に行われる処理です。
スウィフトの初期化(init)
イニシャライゼーションは、クラス、構造体、または列挙型のインスタンスを使用するために準備するプロセスです。このプロセスでは、そのインスタンスの各格納プロパティに初期値を設定し、新しいインスタンスが使用可能になる前に必要な他のセットアップや初期化を行います。
イニシャライザーは、Javaプログラミングのコンストラクタと似ています。タイプセーフな言語であるSwiftは、イニシャライザーに多くのルールを設けています。この概念をしっかりと把握していない限り、実装することは困難になる可能性があります。
Swiftのinit()の構文
init() {
// initialise the stored properties here.
}
以下のサンプルクラスを見てみましょう。
class A{
//Compilation error. No initializer is defined.
var a : Int
var b : String
var c : Int?
let website = "JournalDev"
}
上記のクラスはコンパイルされません。Swiftのコンパイラは、保存されたプロパティが初期化されていないことについて警告を出しています。保存されたプロパティは未確定の状態に保たれることはできません。これにより、2つの可能なオプションが残されます。
-
- プロパティの定義自体でデフォルトのプロパティ値を指定する。
- プロパティを初期化するために、イニシャライザinit()を使用する。
それぞれの手法を一つずつ見ていきましょう。 (Sorezore no shuhō wo hitotsu zutsu mite ikimashou.)
class A{
var a : Int = 5
var b : String = "Hello. How you're doing"
var c : Int?
let website = "JournalDev"
}
ここでは、各格納プロパティのデフォルト値が設定されているため、Swiftはデフォルトイニシャライザを暗黙的に提供します。クラスのインスタンスが初期化されると、ドット演算子を使ってすべてのプロパティと関数にアクセスすることができます。
var object = A()
object.a = 10
object.c = 2
以下のように、init()メソッドを使用して保存されたプロパティを初期化する方法が2つ目の方法です。
class A{
var a : Int
var b : String
var c : Int?
let website = "JournalDev"
init(a: Int, b: String) {
self.a = a
self.b = b
}
}
var object = A(a: 5, b: "Hello World")
注意:Swiftのオプショナルは、ストアドプロパティではありません。したがって、初期化する必要はありません。ストアドプロパティはinit()メソッド内でselfプロパティを使用してアクセスされます。注意:selfは、自分自身のインスタンスメソッド内で現在のインスタンスを参照するために使用されます(Javaのthisと同様)。上記のイニシャライザはクラスの主要なイニシャライザです。これはデザイネートイニシャライザとも呼ばれます(後で詳しく説明します)。イニシャライザを使用すると、定数プロパティも変更できます。
class A{
var a : Int
var b : String
var c : Int?
let website : String
init(a: Int, b: String, website: String) {
self.a = a
self.b = b
self.website = website
}
}
var object = A(a: 5,b: "Hello World", website: "JournalDev")
構造体のメンバーワイズなイニシャライザー
構造体は値の型であるため、必ずしも初期化子を定義する必要はありません。構造体型は、カスタムの初期化子を定義していない限り、自動的にメンバーワイズの初期化子を受け取ります。以下は、構造体を初期化するさまざまな方法を説明するコードスニペットです。
struct Rect{
var length : Int
var breadth : Int
}
var r = Rect(length: 5, breadth: 10)
struct Rect{
var length : Int = 5
var breadth : Int = 10
}
var r = Rect()
var r1 = Rect(length: 10, breadth: 5)
上記のスニペットでは、格納されたプロパティにデフォルト値を割り当てたため、メンバーの初期化なしにデフォルトイニシャライザが提供されます。
struct Rect{
var length : Int
var breadth : Int
init(length: Int, breadth: Int) {
self.length = length + 10
self.breadth = breadth + 10
}
}
var r = Rect(length: 10, breadth: 5)
上記の場合、私たちは独自のカスタムイニシャライザを定義しました。イニシャライザに外部名が必要ない場合、下記のようにアンダースコア「_」が使用され、同じことを示します。
class A{
var a : Int
var b : String
var c : Int?
let website = "JournalDev"
init(_ a: Int, _ b: String) {
self.a = a
self.b = b
}
}
var object = A(5,"Hello World")
struct Rect{
var length : Int
var breadth : Int
init(_ length: Int, _ breadth: Int) {
self.length = length + 10
self.breadth = breadth + 10
}
}
var r = Rect(10, 10)
Swiftのイニシャライザの種類
クラスのイニシャライザは、大まかに以下のタイプに分類することができます。
-
- 指定イニシャライザ: これはクラスの主要なイニシャライザです。スーパークラスのイニシャライザを呼び出す前に、クラスで導入されたすべてのプロパティを完全に初期化する必要があります。クラスは複数の指定イニシャライザを持つことができます。すべてのクラスは少なくとも1つの指定イニシャライザを持つ必要があります。
- 便利なイニシャライザ: これらはクラスの補助的な二次イニシャライザです。同じクラスの指定イニシャライザを呼び出す必要があります。これらはオプションで、カスタムの設定に使用することができます。書式は同じですが、initキーワードの前に便利さ修飾子が置かれます。
class Student{
var name : String
var degree : String
init(name : String, degree: String) {
self.name = name
self.degree = degree
}
convenience init()
{
self.init(name: "Unnamed", degree: "Computer Science")
}
}
var student = Student()
student.degree // "Computer Science"
student.name // "Unnamed"
コンビニエンスイニシャライザは、保存されたプロパティにデフォルト値を割り当てる際に役立ちます。
バリュータイプのためのスウィフトイニシャライザデリゲーション
同一の構造体内でイニシャライザを呼び出すことにより、コードの重複を避けることができます。構造体のような値型は継承をサポートしていません。したがって、イニシャライザを同じ構造体内から呼び出すことが唯一の可能な方法です。以下に例を示します。
struct Rect{
var length : Int
var breadth : Int
init(_ length: Int, _ breadth: Int) {
self.length = length
self.breadth = breadth
}
init(_ length: Int)
{
self.init(length, length)
}
}
var r = Rect(10, 5)
var r1 = Rect(15) //initialises the length and breadth to 15
参照型のためのスウィフトの初期化子の委譲
クラスは参照型であり、継承をサポートしています。そのため、初期化子はスーパークラスから他の初期化子を呼び出すこともでき、すべての値を適切に継承および初期化するための責任を追加することができます。初期化子の間の関係を処理するために定義された主なルールは次のとおりです。
- A designated initializer must call a designated initializer from its immediate superclass.
- A convenience initializer must call another initializer from the same class.
- A convenience initializer must ultimately call a designated initializer.
上記のルールは、以下のイラストで説明されています。
指定イニシャライザは常に上位に委譲しなければなりません。コンビニエンスイニシャライザは常に横断的に委譲しなければなりません。サブクラスのコンビニエンスイニシャライザでは、superキーワードを使用することはできません。
スイフトイニシャライザの継承とオーバーライド
Swiftのサブクラスは、特定の条件が満たされない限り、デフォルトではスーパークラスのイニシャライザを継承しません(自動的なイニシャライザの継承)。これは、サブクラスで未完成の初期化を防ぐために行われます。指定イニシャライザと便利なイニシャライザが継承される仕組みを見てみましょう。関連するサブクラスに継承されるVehicleの基本クラスを定義します。クラスの中でEnumを使用します。以下に基本クラスVehicleの定義を示します。
enum VehicleType : String {
case twoWheeler = "TwoWheeler"
case fourWheeler = "FourWheeler"
}
class Vehicle{
var vehicleType : VehicleType
init(vehicleType: VehicleType) {
self.vehicleType = vehicleType
print("Class Vehicle. vehicleType is \(self.vehicleType.rawValue)\n")
}
convenience init()
{
self.init(vehicleType: .fourWheeler)
}
}
var v = Vehicle(vehicleType: .twoWheeler)
ノート:コンビニエンスイニシャライザは、self.initを使用して、同じクラスの指定イニシャライザを呼び出さなければなりません。上記クラスのサブクラスを以下に示すように定義しましょう。
enum TwoWheelerType : String
{
case scooty = "Scooty"
case bike = "Bike"
}
class TwoWheeler : Vehicle{
var twoWheelerType : TwoWheelerType
var manufacturer : String
init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
self.twoWheelerType = twoWheelerType
self.manufacturer = manufacturer
print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
super.init(vehicleType: vType)
}
}
重要なポイントについて注意すること:
- The designated initializer of the subclass must initialize its own properties before calling the designated initializer of the superclass.
- A subclass can modify inherited properties of the superclass only after the super.init is called.
以下のコードはコンパイル時エラーを引き起こします。
class TwoWheeler : Vehicle{
var twoWheelerType : TwoWheelerType
var manufacturer : String
init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
self.twoWheelerType = twoWheelerType
self.manufacturer = manufacturer
self.vehicleType = vType //Won't compile
super.init(vehicleType: vType)
//self.vehicleType = .fourWheeler //This would work.
}
}
var t = TwoWheeler(twoWheelerType: .scooty, manufacturer: "Hero Honda", vType: .twoWheeler)
前述の通り、サブクラスには自動的にスーパークラスのイニシャライザは継承されないため、下記の初期化は失敗します。
var t = TwoWheeler(vehicleType: .twoWheeler) //manufacturer property isn't initialized.
サブクラスのイニシャライザをオーバーライドするためには、スーパークラスの指定されたイニシャライザと一致させる必要があります。この場合、イニシャライザにはoverrideキーワードが追加されます。
class TwoWheeler : Vehicle{
var twoWheelerType : TwoWheelerType
var manufacturer : String
init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
self.twoWheelerType = twoWheelerType
self.manufacturer = manufacturer
print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
super.init(vehicleType: vType)
}
override init(vehicleType: VehicleType)
{
print("Class TwoWheeler. Overriden Initializer. \(vehicleType.rawValue)")
self.twoWheelerType = .bike
self.manufacturer = "Not defined"
super.init(vehicleType: vehicleType)
}
以下のイニシャライザは、パラメータの名前が異なるため、スーパークラスのイニシャライザをオーバーライドしていません。
//This would give a compile-time error since the parameter v doesn't match with the superclass.
override init(v: VehicleType)
{
self.twoWheelerType = .bike
self.manufacturer = "Not defined"
super.init(vehicleType: v)
}
スーパークラスの初期化子をオーバーライドするために、コンビニエンスイニシャライザを使用する。
class TwoWheeler : Vehicle{
var twoWheelerType : TwoWheelerType
var manufacturer : String
init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
self.twoWheelerType = twoWheelerType
self.manufacturer = manufacturer
print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
super.init(vehicleType: vType)
}
override convenience init(vehicleType: VehicleType) {
self.init(twoWheelerType: .bike, manufacturer: "Not Defined", vType: .twoWheeler)
self.vehicleType = vehicleType
}
}
var t = TwoWheeler(twoWheelerType: .scooty, manufacturer: "Hero Honda", vType: .twoWheeler)
t = TwoWheeler(vehicleType: .twoWheeler)
//Output
Following gets printed on the console:
Class TwoWheeler. Scooty manufacturer is Hero Honda
Class Vehicle. vehicleType is TwoWheeler
Class TwoWheeler. Bike manufacturer is Not Defined
Class Vehicle. vehicleType is TwoWheeler
以下の文を日本語で言い換える(1つのオプションのみ):
便利イニシャライザーには、オーバーライドキーワードが追加されています。それは同じクラスの指定イニシャライザーを呼び出します。注意:キーワードの順序は、便利性とオーバーライドの両方の場合で問題ありません。
必要な初期化子
上記の2つのクラスのRequired Initializersの例を以下に示す。注意すべきキーワードをイニシャライザの前に書くことで、各サブクラスがそのイニシャライザを実装する必要があることを示します。また、必要な修飾子もそれぞれのサブクラスの実装に含まれている必要があります。
class Vehicle{
var vehicleType : VehicleType
required init(vehicleType: VehicleType) {
self.vehicleType = vehicleType
print("Class Vehicle. vehicleType is \(self.vehicleType.rawValue)\n")
}
convenience init()
{
self.init(vehicleType: .fourWheeler)
}
}
class TwoWheeler : Vehicle{
var twoWheelerType : TwoWheelerType
var manufacturer : String
init(twoWheelerType : TwoWheelerType, manufacturer : String, vType : VehicleType) {
self.twoWheelerType = twoWheelerType
self.manufacturer = manufacturer
print("Class TwoWheeler. \(self.twoWheelerType.rawValue) manufacturer is \(self.manufacturer)")
super.init(vehicleType: vType)
}
required init(vehicleType: VehicleType) {
self.manufacturer = "Not Defined"
self.twoWheelerType = .bike
super.init(vehicleType: vehicleType)
}
}
注意:必要な修飾子を追加すると、イニシャライザがオーバーライドされることを示します。したがって、上記の場合はオーバーライドキーワードを省略することができます。必要なイニシャライザを使用する場合、それと便利なイニシャライザは独立しており、一緒に使用することができます。requiredとconvenienceの修飾子を一緒に使用することを示すために、もう1つのVehicleのサブクラスを作成しましょう。
enum FourWheelerType : String
{
case car = "Car"
case bus = "Bus"
case truck = "Truck"
}
class FourWheeler : Vehicle
{
var fourWheelerType : FourWheelerType
var name : String
init(fourWheelerType : FourWheelerType, name: String, vehicleType: VehicleType) {
self.fourWheelerType = fourWheelerType
self.name = name
print("Class FourWheeler. \(self.fourWheelerType.rawValue) Model is \(self.name)")
super.init(vehicleType: vehicleType)
self.vehicleType = vehicleType
}
required convenience init(vehicleType: VehicleType) {
self.init(fourWheelerType: .bus, name: "Mercedes", vehicleType: vehicleType)
}
}
class Car : FourWheeler{
var model : String
init(model: String) {
self.model = model
print("Class Car. Model is \(self.model)")
super.init(fourWheelerType: .car, name: self.model, vehicleType: .fourWheeler)
}
required init(vehicleType: VehicleType)
{
self.model = "Not defined"
print("Class Car. Model is \(self.model)")
super.init(fourWheelerType: .car, name: self.model, vehicleType: vehicleType)
}
}
上記のコードスニペットで注意すべき重要なポイントは次の通りです。
- Convenience initializers are secondary initializers in a class.
- Setting a convenience initializer as required means that implementing it in the subclass is compulsory.
自動的なイニシャライザの継承
スーパークラスからサブクラスへ初期化子が自動的に継承されるケースは2つあります。
- Don’t define any designated initializers in your subclass.
- Implement all the designated initializers of the superclass. All the convenience initializers would be automatically inherited too.
以下のスニペットで行動の最初のルールが示されています。
class Name {
var name: String
init(n: String) {
self.name = n
}
}
class Tutorial: Name {
var tutorial : String? = "Swift Initialization"
}
var parentObject = Name(n: "Anupam")
var childObject = Tutorial(n: "JournalDev")
以下のスニペットで、行動の第2のルールが示されています。
class Name {
var name: String
init(n: String) {
self.name = n
}
convenience init()
{
self.init(n: "No name assigned")
}
}
class Tutorial: Name {
var tutorial : String? = "Swift Tutorial"
override init(n : String) {
super.init(n: n)
}
}
var parentObject = Name(n: "Anupam")
var childObject = Tutorial(n: "JournalDev")
var childObject2 = Tutorial()
print(childObject2.name) //prints "No name assigned
上記のコードでは、スーパークラスのコンビニエンスイニシャライザはサブクラスでも自動的に利用できます。
スウィフトの失敗可能なイニシャライザ
クラス、構造体、または列挙型で、「init?」キーワードを使用して失敗可能なイニシャライザを定義することができます。イニシャライザが失敗すると、失敗の原因にはさまざまなものがあります。無効なパラメータの値、外部ソースの不在などです。失敗可能なイニシャライザは、初期化するタイプのオプション値を作成します。初期化の失敗をトリガーするために、nilが返されます(ただし、init自体は何も返しません)。構造体の場合の失敗可能なイニシャライザとはどのようなものか。
struct SName {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
var name = SName(name: "JournalDev")
if name != nil {
print("init success") //this gets displayed
}
else{
print("init failed")
}
name = SName(name: "")
if name != nil {
print("init success")
}
else{
print("init failed") //this gets displayed
}
列挙型を使用した初期化が失敗する場合
enum CharacterExists {
case A, B
init?(symbol: Character) {
switch symbol {
case "A":
self = .A
case "B":
self = .B
default:
return nil
}
}
}
let ch = CharacterExists(symbol: "C")
if ch != nil {
print("Init failed. Character doesn't exist")
}
class CName {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
var name = CName(name: "")
if name != nil {
print("init success")
}
else{
print("init failed")
}
注意:失敗可能なイニシャライザと非失敗可能なイニシャライザは、同じパラメータのタイプと名前を持つことはできません。
失敗可能なイニシャライザーをオーバーライドすること。
サブクラスでは、失敗可能なイニシャライザをオーバーライドすることができます。失敗可能なイニシャライザは、非失敗イニシャライザでオーバーライドすることができますが、逆はできません。以下に失敗可能なイニシャライザを非失敗イニシャライザでオーバーライドする例が示されています。
class CName {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
var name = CName(name: "")
class SubName : CName{
var age : Int
override init(name: String)
{
self.age = 23
super.init(name: name)!
}
}
注意:強制アンラップは、サブクラスの非失敗イニシャライザの実装の一環として、スーパークラスの失敗イニシャライザを呼び出すために使用されます。これで、Swiftイニシャライザのチュートリアルは終了です。参考文献:Apple Docs