paint-brush
Swift init(), Sekali dan untuk Selamanyaoleh@kfamyn
Sejarah baru

Swift init(), Sekali dan untuk Selamanya

oleh Kiryl Famin19m2025/03/21
Read on Terminal Reader

Terlalu panjang; Untuk membaca

Artikel ini membahas segala hal yang penting mengenai inisialisasi Swift: berdasarkan anggota, yang ditunjuk, kemudahan, penggantian kemudahan; kasus penggunaan yang diperlukan; inisialisasi UIView() tanpa parameter; bantuan kompiler; init yang dapat gagal, init enum, dan banyak lagi.
featured image - Swift init(), Sekali dan untuk Selamanya
Kiryl Famin HackerNoon profile picture
0-item
1-item

Perkenalan

Halo! Nama saya Kiryl Famin, dan saya seorang pengembang iOS.


Hari ini, saya ingin membahas secara mendalam topik sederhana seperti inisialisasi di Swift. Meskipun tampak sederhana, terkadang kurangnya pemahaman menyeluruh tentang topik ini menyebabkan kesalahan yang membuat frustrasi sehingga seseorang ingin segera memperbaikinya tanpa mempelajari detailnya.


Dalam artikel ini, kami akan membahas segala hal yang berhubungan dengan inisialisasi, termasuk:


  • Cara mempertahankan inisialisasi anggota struktur saat mendefinisikan yang khusus

  • Mengapa tidak selalu perlu menulis inisialisasi di kelas

  • Mengapa pemanggilan super.init tidak selalu diperlukan dalam inisialisasi yang ditunjuk

  • Mengapa semua bidang subkelas harus diisi sebelum memanggil super.init

  • Cara mengakses semua inisialisasi induk dengan penggantian minimal di subkelas

  • Kapan tepatnya inisialisasi required dibutuhkan

  • Mengapa UIView.init() selalu dipanggil tanpa parameter, tetapi init(frame:) dan init(coder:) ditimpa


...dan masih banyak lagi. Namun mari kita bahas langkah demi langkah.

Daftar isi

Dasar-dasar

Struktur

  • Inisialisasi berdasarkan anggota
  • Opsional
  • var vs let
  • Mempertahankan inisialisasi anggota

Kelas

  • Inisialisasi yang ditunjuk
  • Inisialisasi kenyamanan
  • Mempertahankan inisialisasi kenyamanan superkelas
  • Meminimalkan jumlah penggantian
  • Bantuan penyusun
  • inisialisasi required : generik, protokol, Self() , final
  • UIView() tanpa parameter

Penghargaan terhormat

  • Inisiasi yang gagal
  • Enumerasi

Ringkasan

Tautan relevan

Dasar-dasar

Panduan Apple Bahasa Pemrograman Swift (6) (yang, omong-omong, sangat rinci untuk inisialisasi) menyatakan:


Inisialisasi adalah proses menyiapkan contoh kelas, struktur, atau enumerasi untuk digunakan. Proses ini melibatkan pengaturan nilai awal untuk setiap properti yang tersimpan pada contoh tersebut dan melakukan pengaturan atau inisialisasi lain yang diperlukan sebelum contoh baru siap digunakan.


Anda menerapkan proses inisialisasi ini dengan mendefinisikan inisialisasi , yang seperti metode khusus yang dapat dipanggil untuk membuat contoh baru dari tipe tertentu. Tidak seperti inisialisasi Objective-C, inisialisasi Swift tidak mengembalikan nilai. Peran utamanya adalah untuk memastikan bahwa contoh baru dari suatu tipe diinisialisasi dengan benar sebelum digunakan untuk pertama kalinya.


Baiklah, saya rasa saya tidak perlu menambahkan apa pun di sini.

Struktur

Mari kita mulai dengan membahas inisialisasi struktur. Ini cukup sederhana karena tidak ada pewarisan, tetapi masih ada beberapa aturan yang harus Anda ketahui.

Inisialisasi berdasarkan anggota

Mari kita tulis struktur sederhana:

 struct BankAccount { let amount: Double let isBlocked: Bool } let bankAccount = BankAccount(amount: 735, isBlocked: Bool)


Perhatikan bahwa kita dapat menginisialisasi struktur tanpa mendeklarasikan inisialisasi secara eksplisit. Hal ini terjadi karena struktur menerima inisialisasi berdasarkan anggota yang dihasilkan oleh kompiler. Ini hanya berfungsi untuk struktur .


Dengan memilih Refactor → Generate memberwise initializer , Anda dapat melihat tampilannya:


Membuat inisialisasi berdasarkan anggota di Xcode


 init(amount: Double, isBlocked: Bool) { self.amount = amount self.isBlocked = isBlocked }


Dari tanda tangannya, mudah untuk melihat bahwa kegagalan dalam memberikan nilai untuk semua parameter akan mengakibatkan kesalahan kompilasi:

 let bankAccount = BankAccount(amount: 735) // ❌ Missing argument for parameter 'isBlocked' in call


Namun, jika Anda ingin mengurangi jumlah argumen yang diperlukan, Anda dapat menentukan inisialisasi khusus:

 init(amount: Double, isBlocked: Bool = false) { self.amount = amount isBlocked = isBlocked } let bankAccount = BankAccount(amount: 735) // ✅


Perhatikan bahwa jika isBlocked tidak diisi, ini akan mengakibatkan kesalahan kompilasi karena semua properti struktur harus diisi dalam inisialisasi .

Opsional, var vs let

Satu-satunya kasus di mana suatu kolom tidak perlu diisi secara eksplisit adalah ketika kolom tersebut merupakan variabel opsional ( ? ) ( var ). Dalam kasus seperti itu, kolom tersebut akan secara default bernilai nil :

 struct BankAccount { let amount: Double var isBlocked: Bool? init(amount: Double) { self.amount = amount } } let bankAccount = BankAccount(amount: 735) // ✅


Namun, jika kita mencoba menggunakan inisialisasi memberwise dalam kasus ini, kita akan mendapatkan kesalahan kompilasi:

 let bankAccount = BankAccount( amount: 735, isBlocked: false ) // ❌ Extra argument 'isBlocked' in call

Mempertahankan inisialisasi anggota

Hal ini terjadi karena mendeklarasikan inisialisasi kustom akan menghapus inisialisasi berdasarkan anggota. Hal ini masih memungkinkan untuk mendefinisikannya secara eksplisit, tetapi tidak akan tersedia secara otomatis.


Namun, ada sedikit trik untuk mempertahankan inisialisasi anggota: nyatakan inisialisasi kustom dalam extension .

 struct BankAccount { let amount: Double var isBlocked: Bool? } extension BankAccount { init(amount: Double) { self.amount = amount } } let barclaysBankAccount = BankAccount(amount: 735) // ✅ let revolutBankAccount = BankAccount(amount: 812, isBlocked: false) // ✅ print(barclaysBankAccount.isBlocked) // nil print(barclaysBankAccount.isBlocked) // false


Ringkasan untuk struktur

  • Semua bidang harus diisi dalam inisialisasi
  • Bidang var opsional default ke nil
  • Struktur menerima inisialisasi anggota secara gratis
  • Inisialisasi anggota akan hilang jika inisialisasi kustom dideklarasikan
  • Untuk mempertahankan inisialisasi anggota struktur, tentukan yang khusus dalam extension

Kelas

Inisialisasi yang ditunjuk

Inisialisasi utama untuk suatu kelas adalah inisialisasi yang ditetapkan . Inisialisasi ini memiliki dua tujuan:

  1. Memastikan semua bidang terisi
  2. Jika kelas diwarisi, ia memanggil inisialisasi superkelas
 class Animal { var name: String init(name: String) { self.name = name } } class Dog: Animal { var breed: String var name: String init(breed: String, name: String) { self.breed = breed super.init(name: name) } }


Semua kolom harus diisi sebelum memanggil super.init . Hal ini karena inisialisasi superkelas dapat memanggil metode yang digantikan oleh subkelas, yang dapat mengakses properti subkelas yang belum diisi.

 class Animal { var age: Int init(age: Int) { self.age = age getInfo() } func getInfo() { print("Age: ", age) } } class Dog: Animal { var breed: String init(breed: String, age: Int) { self.breed = breed // imagine we haven't done this super.init(age: age) } override func getInfo() { print("Age: ", age, ", breed: ", breed) } }


Jadi, jika kita tidak menetapkan self.breed = breed , kita akan mengalami kesalahan runtime karena inisialisasi Animal akan memanggil metode getInfo() yang diganti dari kelas Dog . Metode ini mencoba mengakses properti breed , yang belum diisi.


Tidak seperti struktur, kelas tidak menerima inisialisasi anggota secara implisit. Jika ada properti yang tidak diinisialisasi, kesalahan kompilasi akan terjadi:

 class Animal { // ❌ Class 'Animal' has no initializers var age: Int }
 class Animal { // ✅ var age: Int = 0 }
 class Animal { // ✅ var age: Int? }
 class Animal { } // ✅

Inisialisasi kenyamanan

Kelas juga dapat memiliki inisialisasi praktis . Tidak seperti inisialisasi yang ditetapkan, kelas tidak membuat objek dari awal, tetapi menyederhanakan proses inisialisasi dengan menggunakan kembali logika inisialisasi lainnya.

 class Rectangle { var width: Double var height: Double init(width: Double, height: Double) { self.width = width self.height = height } convenience init(side: Double) { self.init(width: side, height: side) // uses a designated initializer of self } }


Inisialisasi praktis dapat memanggil inisialisasi yang ditunjuk atau inisialisasi praktis lainnya. Pada akhirnya, inisialisasi yang ditunjuk akan selalu dipanggil.

Kenyamanan, inisialisasi yang ditunjuk dan superkelas


Inisialisasi praktis selalu berada pada posisi horizontal (self.init), dan inisialisasi yang ditunjuk berada pada posisi vertikal (super.init).

Mempertahankan Inisialisasi Kenyamanan Superclass

Begitu subkelas mendeklarasikan properti baru, ia kehilangan akses ke semua inisialisasi praktis superkelas.

 class Animal { var age: Int var name: String init(age: Int, name: String) { self.age = age self.name = name } convenience init(age: Int) { self.init(age: age, name: "Default") } convenience init(name: String) { self.init(age: 0, name: name) } } class Dog: Animal { var breed: String init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } let dog = Dog(age: 3) // ❌ Missing arguments for parameters 'breed', 'name' in call 


Hirarki inisialisasi saat ini, hanya satu init yang dideklarasikan secara eksplisit


Hal ini dapat diperbaiki dengan mengesampingkan semua inisialisasi yang ditetapkan pada superkelas .

 class Dog: Animal { // ... override init(age: Int, name: String) { self.breed = "Mixed" super.init(age: age, name: name) } } let dog = Dog(age: 3) // ✅ 


Inisialisasi kenyamanan sekarang diperoleh kembali, tetapi dua inisialisasi dideklarasikan secara eksplisit


Mudah dilihat bahwa, dengan cara ini, untuk menggunakan inisialisasi praktis di subkelas berikutnya, Anda perlu mengganti dua inisialisasi.

 class GuideDog: Dog { var isTrained: Bool override init(age: Int, name: String) { self.isTrained = false super.init(age: age, name: name) } override init(age: Int, name: String, breed: String) { self.isTrained = false super.init(age: age, name: name, breed: breed) } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 


Inisialisasi kenyamanan diperoleh kembali, tetapi GuideDog menentukan tiga inisialisasi secara eksplisit


Meminimalkan jumlah penggantian

Namun, hal ini dapat dihindari dengan menggunakan inisialisasi pengesampingan praktis .

 class Dog: Animal { var breed: String convenience override init(age: Int, name: String) { self.init(age: age, name: name, breed: "Mixed") // self, not super } init(age: Int, name: String, breed: String) { self.breed = breed super.init(age: age, name: name) } } class GuideDog: Dog { var isTrained: Bool // override init(age: Int, name: String) { // self.isTrained = false // // super.init(age: age, name: name, breed: "Mixed") // } convenience override init(age: Int, name: String, breed: String) { self.init(age: age, name: name, breed: breed, isTrained: false) // self, not super } init(age: Int, name: String, breed: String, isTrained: Bool) { self.isTrained = isTrained super.init(age: age, name: name, breed: breed) } } let dog = GuideDog(age: 3) // ✅ 


Dua inisialisasi kenyamanan Hewan didapatkan kembali, init(umur:, nama:) kenyamanan Anjing didapatkan kembali dan hanya dua inisialisasi GuideDog yang ditentukan secara eksplisit


Sekarang kita hanya memiliki 2 inisialisasi yang ditetapkan secara eksplisit dalam setiap subkelas.

Perhatikan bagaimana inisialisasi pengesampingan praktis memanggil init yang ditunjuk self , bukannya super.init .

Trik ini dijelaskan secara menyeluruh dalam Bab 5 dari Swift in Depth oleh Tjeerd in 't Veen, buku yang sangat saya rekomendasikan.

Ringkasan menengah

  • Inisialisasi yang ditunjuk memastikan semua properti terisi dan memanggil super.init() .
  • Inisialisasi praktis menyederhanakan inisialisasi dengan memanggil inisialisasi yang ditunjuk.
  • Inisialisasi praktis tidak lagi tersedia bagi subkelas jika mereka mendeklarasikan properti baru.
  • Untuk mengembalikan inisialisasi praktis superkelas, semua inisialisasi yang ditunjuk harus ditimpa.
  • Untuk meminimalisir jumlah penggantian, inisialisasi penggantian praktis dapat digunakan.

Bantuan penyusun

Kita telah membahas bahwa jika sebuah subkelas tidak memperkenalkan parameter baru, maka subkelas tersebut secara otomatis mewarisi semua inisialisasi superkelas.

 class Base { let value: Int init() { value = 0 } init(value: Int) { self.value = value } } class Subclass: Base { } let subclass = Subclass() // ✅ let subclass = Subclass(value: 3) // ✅


Namun, ada poin penting lainnya: jika superkelas hanya memiliki satu inisialisasi yang ditetapkan dan tidak memiliki parameter ( init() tanpa argumen), maka inisialisasi yang dideklarasikan secara eksplisit di subkelas tidak perlu memanggil super.init() . Dalam kasus ini, kompiler Swift secara otomatis memasukkan panggilan ke super.init() yang tersedia tanpa argumen.

 class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } }


Kode dikompilasi karena super.init() dipanggil secara implisit. Hal ini penting untuk beberapa contoh berikut.

Diperlukan

Inisialisasi required digunakan dalam semua kasus di mana subkelas harus memiliki inisialisasi yang sama dengan kelas dasar. Subkelas juga harus memanggil super.init() . Berikut adalah contoh di mana inisialisasi required diperlukan.

Obat Generik

Memanggil init pada tipe generik hanya dimungkinkan dengan mendeklarasikannya sebagai required init .

 class Base { } class Subclass: Base { } struct Factory<T: Base> { func initInstance() -> T { // ❌ Constructing an object of class T() // type 'T' with a metatype value } // must use a 'required' initializer }


Kode ini tidak dapat dikompilasi karena Factory tidak mengetahui apa pun tentang subkelas Base . Meskipun dalam kasus khusus ini, Subclass memiliki init() tanpa parameter, bayangkan jika ia memperkenalkan kolom baru:

 class Subclass: Base { let value: Int init(value: Int) { self.value = value } }


Di sini, tidak lagi memiliki init yang kosong, jadi harus dideklarasikan sebagai required .

 class Base { required init() { } } class Subclass: Base { } struct Factory<T: Base> { static func initInstance() -> T { // ✅ T() } } let subclass = Factory<Subclass>.initInstance()


Perhatikan bahwa meskipun kami tidak secara eksplisit mendeklarasikan required init di Subclass , kompiler membuatnya untuk kami. Hal ini dibahas dalam Compiler Assistance . required init secara otomatis diwarisi dan disebut super.init() .

 class Subclass: Base { required init() { super.init() } }

Protokol

Semua inisialisasi yang dideklarasikan dalam protokol harus required :

 protocol Initable { init() } class InitableObject: Initable { init() { // ❌ Initializer requirement 'init()' can only // be satisfied by a 'required' initializer } // in non-final class 'InitableObject' }


Sekali lagi, hal ini diperlukan agar kompiler memastikan bahwa subkelas mengimplementasikan inisialisasi protokol. Seperti yang telah kita ketahui, hal ini tidak selalu terjadi—jika init tidak required , subkelas tidak berkewajiban untuk menggantinya dan dapat menentukan inisialisasinya sendiri.

 class IntValue: InitableObject { let value: Int init(value: Int) { self.value = value } } let InitableType: Initable.Type = IntValue.self let initable: Initable = InitableType.init()


Tentu saja, kode berikut tidak akan dikompilasi karena Base.init() tidak required .

 class InitableObject: Initable { required init() { } // ✅ } class IntValue: InitableObject { let value: Int required init() { self.value = 0 } init(value: Int) { self.value = value } }

Diri sendiri()

Situasi serupa terjadi saat memanggil inisialisasi Self() dalam metode statis.

 class Base { let value: Int init(value: Int) { self.value = value } static func instantiate() -> Self { Self(value: 3) // ❌ Constructing an object of class type 'Self' } // with a metatype value must use a 'required' initializer }


Seperti biasa, masalahnya terletak pada pewarisan:

 class Subclass: BaseClass { let anotherValue: Int init(anotherValue: Int) { self.anotherValue = anotherValue } } let subclass = Subclass.instantiate() // ❌
 class BaseClass { let value: Int required init(value: Int) { // ✅ self.value = value } static func instantiate() -> Self { Self(value: 3) } }

Menyingkirkan required : final

Karena tujuan dari required adalah untuk memaksakan penerapan inisialisasi pada subkelas, tentu saja, pelarangan pewarisan menggunakan kata kunci final menghilangkan keperluan untuk menandai inisialisasi sebagai required .

 protocol Initable { init() } final class InitableObject: Initable { } // ✅
 protocol ValueInitable { init(value: Int) } final class ValueInitableObject: ValueInitable { init(value: Int) { } // ✅ }

Ringkasan menengah

  • Jika suatu subkelas tidak memperkenalkan parameter baru, maka subkelas tersebut secara otomatis mewarisi semua inisialisasi dari superkelasnya.
  • Jika superkelas hanya memiliki init() tanpa parameter, ia dipanggil secara otomatis dalam inisialisasi subkelas.
  • Inisialisasi required diperlukan untuk menjamin keberadaannya di subkelas untuk digunakan dalam generik, protokol, dan Self() .

Tampilan UI()

Penyebutan singkat tentang inisialisasi UIView() tanpa parameter, yang tidak dapat ditemukan dalam dokumentasi tetapi secara misterius digunakan di mana-mana.


Alasannya adalah karena UIView mewarisi dari NSObject , yang memiliki init() tanpa parameter. Oleh karena itu , inisialisasi ini tidak dideklarasikan secara eksplisit dalam antarmuka UIView , namun masih tersedia:

 @available(iOS 2.0, *) @MainActor open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate { open class var layerClass: AnyClass { get } public init(frame: CGRect) public init?(coder: NSCoder) open var isUserInteractionEnabled: Bool // no init()


Namun, di balik layar, penginisialisasi ini memanggil init(frame:) saat diinisialisasi dalam kode atau init(coder:) saat diinisialisasi melalui Interface Builder. Hal ini terjadi karena UIView menyediakan implementasinya sendiri dari NSObject.init() , yang dapat dikonfirmasi oleh fakta bahwa method_getImplementation mengembalikan alamat yang berbeda untuk NSObject.init() dan UIView.init() .

Penghargaan terhormat

Inisiasi yang dapat gagal

Inisiatif yang dapat gagal hanyalah yang mengembalikan opsi

 final class Failable { let positiveValue: Int init?(value: Int) { guard value > 0 else { return nil } positiveValue = value } }

Enum

Enum dengan nilai mentah mendapatkan init?(rawValue:) gratis

 enum Direction: String { case north case west case south case east } let north = Direction(rawValue: "north")

Anda juga dapat membuat init khusus untuk enum. Semua init enum harus menetapkan self .

 enum DeviceType { case phone case tablet init(screenWidth: Int) { self = screenWidth > 800 ? .tablet : .phone } }

Ringkasan akhir

Kami telah membahas semua aspek penting inisialisasi di Swift:


  • Dalam inisialisasi, semua bidang harus diisi.

  • Properti var opsional default ke nil .

  • Struktur menerima inisialisasi anggota secara gratis.

  • Inisialisasi anggota akan hilang ketika inisialisasi kustom ditetapkan.

  • Inisialisasi yang ditunjuk memastikan semua bidang terisi dan memanggil super.init() .

  • Inisialisasi praktis menyederhanakan inisialisasi dengan memanggil inisialisasi yang ditunjuk.

  • Inisialisasi praktis selalu berada pada posisi horizontal ( self.init ), dan inisialisasi yang ditunjuk berada pada posisi vertikal ( super.init ).

  • Inisialisasi praktis menjadi tidak tersedia bagi subkelas jika mereka mendeklarasikan properti baru.

  • Untuk mengembalikan inisialisasi praktis superkelas, semua inisialisasi yang ditunjuk harus ditimpa.

  • Untuk meminimalisir jumlah penggantian, inisialisasi penggantian praktis dapat digunakan.

  • Jika suatu subkelas tidak memperkenalkan parameter baru, maka subkelas tersebut secara otomatis mewarisi semua inisialisasi dari superkelasnya.

  • Jika superkelas hanya memiliki init() tanpa parameter, ia secara otomatis dipanggil dalam inisialisasi subkelas.

  • Inisialisasi yang diperlukan memastikan keberadaannya di subkelas untuk digunakan dalam generik, protokol, dan Self() .

  • UIView.init() memanggil UIView.init(frame:) atau UIView.init(coder:) .

  • Inisialisasi yang gagal mengembalikan suatu opsional.

  • Enum dengan nilai mentah mendapatkan init?(rawValue:) gratis.

  • Semua inisialisasi enum harus menetapkan self .


Saya harap Anda menemukan sesuatu yang bermanfaat dalam artikel ini. Jika ada yang masih belum jelas atau Anda menemukan ketidakakuratan, jangan ragu untuk menghubungi saya untuk mendapatkan penjelasan gratis di Telegram: @kfamyn .

Tautan relevan

  1. Bahasa Pemrograman Swift (6) / Inisialisasi
  2. Swift in Depth oleh Tjeerd di 't Veen
  3. Telegram - @kfamyn