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 tentang topik ini menyebabkan kesalahan yang membuat frustrasi sehingga seseorang ingin segera memperbaikinya tanpa mempelajari detailnya. menyeluruh 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 tidak selalu diperlukan dalam inisialisasi yang ditunjuk super.init Mengapa semua bidang subkelas harus diisi sebelum memanggil super.init Cara mengakses semua inisialisasi induk dengan penggantian minimal di subkelas Kapan tepatnya inisialisasi dibutuhkan required Mengapa selalu dipanggil tanpa parameter, tetapi dan ditimpa UIView.init() init(frame:) init(coder:) ...dan masih banyak lagi. Namun mari kita bahas langkah demi langkah. Daftar isi Dasar-dasar Struktur Inisialisasi berdasarkan anggota Opsional vs var let Mempertahankan inisialisasi anggota Kelas Inisialisasi yang ditunjuk Inisialisasi kenyamanan Mempertahankan inisialisasi kenyamanan superkelas Meminimalkan jumlah penggantian Bantuan penyusun inisialisasi : generik, protokol, , required Self() final tanpa parameter UIView() Penghargaan terhormat Inisiasi yang gagal Enumerasi Ringkasan Tautan relevan Dasar-dasar Panduan Apple (yang, omong-omong, sangat rinci untuk inisialisasi) menyatakan: Bahasa Pemrograman Swift (6) 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. Inisialisasi Anda menerapkan proses inisialisasi ini dengan mendefinisikan , 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. inisialisasi 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 yang dihasilkan oleh kompiler. Ini . inisialisasi berdasarkan anggota hanya berfungsi untuk struktur Dengan memilih , Anda dapat melihat tampilannya: Refactor → Generate memberwise initializer 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 tidak diisi, ini akan mengakibatkan kesalahan kompilasi karena . isBlocked semua properti struktur harus diisi dalam inisialisasi vs Opsional, var let Satu-satunya kasus di mana suatu kolom tidak perlu diisi adalah ketika kolom tersebut merupakan ( ) ( ). Dalam kasus seperti itu, kolom tersebut akan secara default bernilai : secara eksplisit variabel opsional ? var 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 ini memiliki dua tujuan: inisialisasi yang ditetapkan Memastikan semua bidang terisi 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 memanggil . Hal ini karena inisialisasi superkelas dapat memanggil metode yang digantikan oleh subkelas, yang dapat mengakses properti subkelas yang belum diisi. harus diisi sebelum super.init 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 , kita akan mengalami kesalahan runtime karena inisialisasi akan memanggil metode yang diganti dari kelas . Metode ini mencoba mengakses properti , yang belum diisi. self.breed = breed Animal getInfo() Dog breed 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 . Tidak seperti inisialisasi yang ditetapkan, kelas tidak membuat objek dari awal, tetapi menyederhanakan proses inisialisasi dengan menggunakan kembali logika inisialisasi lainnya. inisialisasi praktis 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. 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 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) // ✅ Mudah dilihat bahwa, dengan cara ini, untuk menggunakan inisialisasi praktis di subkelas berikutnya, Anda perlu mengganti inisialisasi. dua 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) // ✅ 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) // ✅ Sekarang kita hanya memiliki 2 inisialisasi yang ditetapkan secara eksplisit dalam setiap subkelas. init yang ditunjuk , bukannya . Perhatikan bagaimana inisialisasi pengesampingan praktis memanggil self super.init Trik ini dijelaskan secara menyeluruh dalam Bab 5 dari oleh Tjeerd in 't Veen, buku yang sangat saya rekomendasikan. Swift in Depth Ringkasan menengah memastikan semua properti terisi dan memanggil . Inisialisasi yang ditunjuk super.init() menyederhanakan inisialisasi dengan memanggil inisialisasi yang ditunjuk. Inisialisasi praktis tidak lagi tersedia bagi subkelas jika mereka mendeklarasikan properti baru. Inisialisasi praktis Untuk mengembalikan superkelas, semua inisialisasi yang ditunjuk harus ditimpa. inisialisasi praktis Untuk meminimalisir jumlah penggantian, dapat digunakan. inisialisasi penggantian praktis 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 ( tanpa argumen), maka inisialisasi yang dideklarasikan secara eksplisit di subkelas memanggil . Dalam kasus ini, kompiler Swift panggilan ke yang tersedia tanpa argumen. init() tidak perlu super.init() secara otomatis memasukkan super.init() class Base { init() { } } class Subclass: Base { let secondValue: Int init(secondValue: Int) { self.secondValue = secondValue // ✅ without explicit super.init() } } Kode dikompilasi karena dipanggil secara implisit. Hal ini penting untuk beberapa contoh berikut. super.init() Diperlukan Inisialisasi digunakan dalam semua kasus di mana subkelas harus memiliki inisialisasi yang sama dengan kelas dasar. Subkelas juga harus memanggil . Berikut adalah contoh di mana inisialisasi diperlukan. required super.init() required Obat Generik Memanggil pada tipe generik hanya dimungkinkan dengan mendeklarasikannya sebagai . init 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 tidak mengetahui apa pun tentang subkelas . Meskipun dalam kasus khusus ini, memiliki tanpa parameter, bayangkan jika ia memperkenalkan kolom baru: Factory Base Subclass init() class Subclass: Base { let value: Int init(value: Int) { self.value = value } } Di sini, tidak lagi memiliki yang kosong, jadi harus dideklarasikan sebagai . init 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 di , kompiler membuatnya untuk kami. Hal ini dibahas dalam . secara otomatis diwarisi dan disebut . required init Subclass Compiler Assistance required init 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 tidak , subkelas tidak berkewajiban untuk menggantinya dan dapat menentukan inisialisasinya sendiri. init required 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 tidak . Base.init() 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 dalam metode statis. Self() 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 adalah untuk memaksakan penerapan inisialisasi pada subkelas, tentu saja, pelarangan pewarisan menggunakan kata kunci menghilangkan keperluan untuk menandai inisialisasi sebagai . required final 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 tanpa parameter, ia dipanggil secara otomatis dalam inisialisasi subkelas. init() Inisialisasi diperlukan untuk menjamin keberadaannya di subkelas untuk digunakan dalam generik, protokol, dan . required Self() Tampilan UI() Penyebutan singkat tentang inisialisasi tanpa parameter, yang tidak dapat ditemukan dalam dokumentasi tetapi secara misterius digunakan di mana-mana. UIView() Alasannya adalah karena mewarisi dari , yang memiliki tanpa parameter. , inisialisasi ini tidak dideklarasikan secara eksplisit dalam antarmuka , namun masih tersedia: UIView NSObject init() Oleh karena itu UIView @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 saat diinisialisasi dalam kode atau saat diinisialisasi melalui Interface Builder. Hal ini terjadi karena menyediakan implementasinya sendiri dari , yang dapat dikonfirmasi oleh fakta bahwa mengembalikan alamat yang berbeda untuk dan . init(frame:) init(coder:) UIView NSObject.init() method_getImplementation NSObject.init() 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 gratis init?(rawValue:) 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 opsional default ke . var nil Struktur menerima secara gratis. inisialisasi anggota ketika inisialisasi kustom ditetapkan. Inisialisasi anggota akan hilang memastikan semua bidang terisi dan memanggil . Inisialisasi yang ditunjuk super.init() menyederhanakan inisialisasi dengan memanggil inisialisasi yang ditunjuk. Inisialisasi praktis Inisialisasi praktis selalu berada pada ( ), dan inisialisasi yang ditunjuk berada pada ( ). posisi horizontal self.init posisi vertikal super.init menjadi tidak tersedia bagi subkelas jika mereka mendeklarasikan properti baru. Inisialisasi praktis Untuk mengembalikan superkelas, semua inisialisasi yang ditunjuk harus ditimpa. inisialisasi praktis Untuk meminimalisir jumlah penggantian, dapat digunakan. inisialisasi penggantian praktis Jika suatu subkelas tidak memperkenalkan parameter baru, maka subkelas tersebut secara otomatis mewarisi semua inisialisasi dari superkelasnya. Jika superkelas hanya memiliki tanpa parameter, ia secara otomatis dipanggil dalam inisialisasi subkelas. init() memastikan keberadaannya di subkelas untuk digunakan dalam generik, protokol, dan . Inisialisasi yang diperlukan Self() memanggil atau . UIView.init() UIView.init(frame:) UIView.init(coder:) mengembalikan suatu opsional. Inisialisasi yang gagal Enum dengan nilai mentah mendapatkan gratis. init?(rawValue:) 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 Bahasa Pemrograman Swift (6) / Inisialisasi Swift in Depth oleh Tjeerd di 't Veen Telegram - @kfamyn