Ada dua bagian informasi Swift khusus yang sangat krusial yang hilang dari jawaban yang ada yang menurut saya membantu menjernihkan hal ini sepenuhnya.
- Jika protokol menentukan penginisialisasi sebagai metode yang diperlukan, penginisialisasi itu harus ditandai menggunakan
required
kata kunci Swift .
- Swift memiliki seperangkat aturan pewarisan khusus mengenai
init
metode.
The tl; dr adalah ini:
Jika Anda menerapkan inisialisasi apa pun, Anda tidak lagi mewarisi inisialisasi yang ditunjuk oleh superclass.
Inisialisasi hanya, jika ada, yang akan Anda warisi, adalah inisialisasi kenyamanan kelas super yang menunjuk ke inisialisasi yang ditunjuk yang kebetulan Anda timpa.
Jadi ... siap untuk versi panjang?
Swift memiliki seperangkat aturan pewarisan khusus mengenai init
metode.
Saya tahu ini adalah yang kedua dari dua poin yang saya buat, tetapi kami tidak dapat memahami poin pertama, atau mengapa required
kata kunci itu ada sampai kami memahami poin ini. Setelah kita memahami hal ini, yang lain menjadi sangat jelas.
Semua informasi yang saya bahas di bagian jawaban ini berasal dari dokumentasi Apple yang ditemukan di sini .
Dari dokumen Apple:
Tidak seperti subclass di Objective-C, Swift subclass tidak mewarisi inisialisasi kelas supernya. Pendekatan Swift mencegah situasi di mana penginisialisasi sederhana dari superclass diwarisi oleh subclass yang lebih khusus dan digunakan untuk membuat instance baru dari subclass yang tidak sepenuhnya atau benar diinisialisasi.
Tekankan milikku.
Jadi, langsung dari dokumen Apple di sana, kita melihat bahwa subclass Swift tidak akan selalu (dan biasanya tidak) mewarisi init
metode superclass mereka .
Jadi, kapan mereka mewarisi dari superclass mereka?
Ada dua aturan yang menentukan kapan subkelas mewarisi init
metode dari induknya. Dari dokumen Apple:
Aturan 1
Jika subkelas Anda tidak menentukan inisialisasi yang ditunjuk, subisialisasi Anda secara otomatis akan mewarisi semua inisialisasi yang ditentukan oleh superclass.
Aturan 2
Jika subclass Anda memberikan implementasi dari semua inisialisasi yang ditentukan oleh superclass-baik dengan mewarisi mereka sesuai aturan 1, atau dengan memberikan implementasi kustom sebagai bagian dari definisi-maka secara otomatis mewarisi semua inisialisasi kenyamanan superclass.
Aturan 2 tidak terlalu relevan dengan percakapan ini karena SKSpriteNode
itu init(coder: NSCoder)
tidak mungkin menjadi metode kenyamanan.
Jadi, InfoBar
kelas Anda mewarisi required
initializer hingga titik yang Anda tambahkan init(team: Team, size: CGSize)
.
Jika Anda adalah untuk tidak disediakan ini init
metode dan bukannya membuat Anda InfoBar
'sifat s menambahkan opsional atau memberikan mereka dengan nilai-nilai default, maka Anda akan masih telah mewarisi SKSpriteNode
' s init(coder: NSCoder)
. Namun, ketika kami menambahkan inisialisasi kustom kami sendiri, kami berhenti mewarisi inisialisasi yang ditunjuk superclass kami (dan inisialisasi kenyamanan yang tidak menunjuk ke inisialisasi yang kami terapkan).
Jadi, sebagai contoh sederhana, saya menyajikan ini:
class Foo {
var foo: String
init(foo: String) {
self.foo = foo
}
}
class Bar: Foo {
var bar: String
init(foo: String, bar: String) {
self.bar = bar
super.init(foo: foo)
}
}
let x = Bar(foo: "Foo")
Yang menyajikan kesalahan berikut:
Argumen untuk 'bar' parameter tidak ada dalam panggilan.
Jika ini Objective-C, tidak akan ada masalah mewarisi. Jika kita menginisialisasi a Bar
dengan initWithFoo:
di Objective-C, self.bar
properti akan menjadi nil
. Ini mungkin tidak besar, tetapi itu adalah sempurna berlaku keadaan untuk objek yang akan di. Ini bukan keadaan sempurna berlaku untuk objek Swift berada di. self.bar
Bukan opsional dan tidak bisa nil
.
Sekali lagi, satu-satunya cara kami mewarisi inisialisasi adalah dengan tidak menyediakan inisialisasi kami. Jadi jika kita mencoba untuk mewarisi dengan menghapus Bar
's init(foo: String, bar: String)
, seperti:
class Bar: Foo {
var bar: String
}
Sekarang kita kembali ke pewarisan (semacam), tetapi ini tidak dapat dikompilasi ... dan pesan kesalahan menjelaskan mengapa kita tidak mewarisi init
metode superclass :
Masalah: Kelas 'Bar' tidak memiliki inisialisasi
Fix-It: 'bar' properti tersimpan tanpa inisialisasi mencegah inisialisasi disintesis
Jika kami telah menambahkan properti tersimpan di subkelas kami, tidak ada cara Swift yang memungkinkan untuk membuat instance yang valid dari subkelas kami dengan inisialisasi superclass yang tidak mungkin tahu tentang properti tersimpan subkelas kami.
Oke, well, mengapa saya harus menerapkan init(coder: NSCoder)
sama sekali? Kenapa begitu required
?
init
Metode Swift dapat dimainkan oleh seperangkat aturan pewarisan khusus, tetapi kesesuaian protokol masih diwariskan ke bawah rantai. Jika kelas induk sesuai dengan protokol, subkelasnya harus sesuai dengan protokol itu.
Biasanya, ini bukan masalah, karena sebagian besar protokol hanya memerlukan metode yang tidak dimainkan oleh aturan pewarisan khusus di Swift, jadi jika Anda mewarisi dari kelas yang sesuai dengan protokol, Anda juga mewarisi semua metode atau properti yang memungkinkan kelas untuk memenuhi kepatuhan protokol.
Namun, ingat, init
metode Swift bermain dengan seperangkat aturan khusus dan tidak selalu diwariskan. Karena itu, kelas yang sesuai dengan protokol yang membutuhkan init
metode khusus (seperti NSCoding
) mengharuskan kelas menandai init
metode tersebut required
.
Pertimbangkan contoh ini:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Ini tidak dikompilasi. Ini menghasilkan peringatan berikut:
Masalah: Persyaratan inisialisasi 'init (foo :)' hanya dapat dipenuhi oleh inisialisasi 'wajib' di kelas non-final 'ConformingClass'
Fix-It: Masukkan wajib
Ia ingin saya membuat init(foo: Int)
inisialisasi diperlukan. Saya juga bisa membuatnya bahagia dengan membuat kelas final
(artinya kelas tidak dapat diwarisi dari).
Jadi, apa yang terjadi jika saya subkelas? Dari titik ini, jika saya subkelas, saya baik-baik saja. Jika saya menambahkan inisialisasi, saya tiba-tiba tidak lagi mewarisi init(foo:)
. Ini bermasalah karena sekarang saya tidak lagi sesuai dengan InitProtocol
. Saya tidak bisa subkelas dari kelas yang sesuai dengan protokol dan kemudian tiba-tiba memutuskan saya tidak ingin lagi sesuai dengan protokol itu. Saya mewarisi kesesuaian protokol, tetapi karena cara Swift bekerja dengan init
pewarisan metode, saya tidak mewarisi bagian dari apa yang diperlukan untuk menyesuaikan dengan protokol itu dan saya harus mengimplementasikannya.
Oke, ini semua masuk akal. Tetapi mengapa saya tidak bisa mendapatkan pesan kesalahan yang lebih bermanfaat?
Dapat diperdebatkan, pesan kesalahan mungkin lebih jelas atau lebih baik jika ditentukan bahwa kelas Anda tidak lagi sesuai dengan NSCoding
protokol yang diwarisi dan bahwa untuk memperbaikinya Anda perlu menerapkan init(coder: NSCoder)
. Tentu.
Tapi Xcode tidak bisa menghasilkan pesan itu karena itu sebenarnya tidak akan selalu menjadi masalah aktual dengan tidak menerapkan atau mewarisi metode yang diperlukan. Setidaknya ada satu alasan lain untuk membuat init
metode required
selain kepatuhan protokol, dan itulah metode pabrik.
Jika saya ingin menulis metode pabrik yang tepat, saya perlu menentukan tipe pengembalian menjadi Self
(Swift's setara dengan Objective-C instanceType
). Tetapi untuk melakukan ini, saya benar-benar perlu menggunakan required
metode penginisialisasi.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Ini menghasilkan kesalahan:
Membangun objek tipe kelas 'Mandiri' dengan nilai metatype harus menggunakan penginisialisasi 'wajib'
Ini pada dasarnya masalah yang sama. Jika kita subkelas Box
, subkelas kita akan mewarisi metode kelas factory
. Jadi kita bisa menelepon SubclassedBox.factory()
. Namun, tanpa required
kata kunci pada init(size:)
metode, Box
's subclass tidak dijamin untuk mewarisi self.init(size:)
yang factory
memanggil.
Jadi kita harus membuat metode itu required
jika kita ingin metode pabrik seperti ini, dan itu berarti jika kelas kita mengimplementasikan metode seperti ini, kita akan memiliki required
metode penginisialisasi dan kita akan mengalami masalah yang sama persis dengan yang Anda temui di sini dengan NSCoding
protokol.
Pada akhirnya, itu semua bermuara pada pemahaman dasar bahwa inisialisasi Swift bermain dengan seperangkat aturan pewarisan yang sedikit berbeda yang berarti Anda tidak dijamin mewarisi inisialisasi dari superclass Anda. Ini terjadi karena inisialisasi superclass tidak dapat mengetahui tentang properti tersimpan baru Anda dan mereka tidak dapat membuat objek Anda menjadi keadaan yang valid. Tetapi, karena berbagai alasan, sebuah superclass mungkin menandai inisialisasi sebagai required
. Ketika hal itu terjadi, kita dapat menggunakan salah satu skenario yang sangat spesifik yang dengannya kita benar-benar mewarisi required
metode ini, atau kita harus mengimplementasikannya sendiri.
Poin utama di sini adalah bahwa jika kita mendapatkan kesalahan yang Anda lihat di sini, itu berarti bahwa kelas Anda sebenarnya tidak menerapkan metode sama sekali.
Sebagai mungkin satu contoh terakhir untuk menelusuri fakta bahwa subclass Swift tidak selalu mewarisi init
metode orang tua mereka (yang saya pikir sangat penting untuk memahami sepenuhnya masalah ini), pertimbangkan contoh ini:
class Foo {
init(a: Int, b: Int, c: Int) {
// do nothing
}
}
class Bar: Foo {
init(string: String) {
super.init(a: 0, b: 1, c: 2)
// do more nothing
}
}
let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)
Ini gagal dikompilasi.
Pesan kesalahan yang diberikannya sedikit menyesatkan:
Argumen ekstra 'b' dalam panggilan
Tapi intinya adalah, Bar
tidak mewarisi apapun Foo
's init
metode karena belum puas salah satu dari dua kasus khusus untuk mewarisi init
metode dari kelas induknya.
Jika ini Objective-C, kami akan mewarisinya init
tanpa masalah, karena Objective-C sangat senang tidak menginisialisasi properti objek (meskipun sebagai pengembang, Anda seharusnya tidak senang dengan ini). Di Swift, ini tidak akan berhasil. Anda tidak dapat memiliki status yang tidak valid, dan mewarisi inisialisasi kelas super hanya dapat menyebabkan status objek yang tidak valid.
init(collection:MPMediaItemCollection)
. Anda harus menyediakan koleksi item media nyata; itulah poin dari kelas ini. Kelas ini tidak bisa dipakai tanpa satu. Ini akan menganalisis koleksi dan menginisialisasi selusin variabel instan. Itulah inti dari ini menjadi satu-satunya inisialisasi yang ditunjuk! Dengan demikian,init(coder:)
MPMediaItemCollection untuk memasok di sini tidak memiliki makna (atau bahkan tidak berarti); hanyafatalError
pendekatannya yang benar.