Jadi saya memperbarui Xcode 6 beta 5 hari ini dan memperhatikan bahwa saya menerima kesalahan di hampir semua subkelas saya di kelas Apple.
Status kesalahan:
Kelas 'x' tidak menerapkan anggota superclass yang diperlukan
Inilah salah satu contoh yang saya pilih karena kelas ini saat ini cukup ringan sehingga akan mudah diposkan.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Jadi pertanyaan saya adalah, mengapa saya menerima kesalahan ini, dan bagaimana saya bisa memperbaikinya? Apa yang tidak saya laksanakan? Saya memanggil penginisialisasi yang ditunjuk.
sumber
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.init(collection:MPMediaItemCollection!)
. Itu akan memungkinkaninit(coder:)
untuk melewati nol. Tapi kemudian saya menyadari: tidak, sekarang Anda hanya menipu kompiler. Melewati nol tidak dapat diterima, jadi lemparkanfatalError
dan lanjutkan. :)Ada dua bagian informasi Swift khusus yang sangat krusial yang hilang dari jawaban yang ada yang menurut saya membantu menjernihkan hal ini sepenuhnya.
required
kata kunci Swift .init
metode.The tl; dr adalah ini:
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:
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 2 tidak terlalu relevan dengan percakapan ini karena
SKSpriteNode
ituinit(coder: NSCoder)
tidak mungkin menjadi metode kenyamanan.Jadi,
InfoBar
kelas Anda mewarisirequired
initializer hingga titik yang Anda tambahkaninit(team: Team, size: CGSize)
.Jika Anda adalah untuk tidak disediakan ini
init
metode dan bukannya membuat AndaInfoBar
'sifat s menambahkan opsional atau memberikan mereka dengan nilai-nilai default, maka Anda akan masih telah mewarisiSKSpriteNode
' sinit(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:
Yang menyajikan kesalahan berikut:
Jika ini Objective-C, tidak akan ada masalah mewarisi. Jika kita menginisialisasi a
Bar
denganinitWithFoo:
di Objective-C,self.bar
properti akan menjadinil
. 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 bisanil
.Sekali lagi, satu-satunya cara kami mewarisi inisialisasi adalah dengan tidak menyediakan inisialisasi kami. Jadi jika kita mencoba untuk mewarisi dengan menghapus
Bar
'sinit(foo: String, bar: String)
, seperti:Sekarang kita kembali ke pewarisan (semacam), tetapi ini tidak dapat dikompilasi ... dan pesan kesalahan menjelaskan mengapa kita tidak mewarisi
init
metode superclass :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 begiturequired
?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 membutuhkaninit
metode khusus (sepertiNSCoding
) mengharuskan kelas menandaiinit
metode tersebutrequired
.Pertimbangkan contoh ini:
Ini tidak dikompilasi. Ini menghasilkan peringatan berikut:
Ia ingin saya membuat
init(foo: Int)
inisialisasi diperlukan. Saya juga bisa membuatnya bahagia dengan membuat kelasfinal
(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 denganInitProtocol
. 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 denganinit
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 menerapkaninit(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
metoderequired
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-CinstanceType
). Tetapi untuk melakukan ini, saya benar-benar perlu menggunakanrequired
metode penginisialisasi.Ini menghasilkan kesalahan:
Ini pada dasarnya masalah yang sama. Jika kita subkelas
Box
, subkelas kita akan mewarisi metode kelasfactory
. Jadi kita bisa meneleponSubclassedBox.factory()
. Namun, tanparequired
kata kunci padainit(size:)
metode,Box
's subclass tidak dijamin untuk mewarisiself.init(size:)
yangfactory
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 memilikirequired
metode penginisialisasi dan kita akan mengalami masalah yang sama persis dengan yang Anda temui di sini denganNSCoding
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 mewarisirequired
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:Ini gagal dikompilasi.
Pesan kesalahan yang diberikannya sedikit menyesatkan:
Tapi intinya adalah,
Bar
tidak mewarisi apapunFoo
'sinit
metode karena belum puas salah satu dari dua kasus khusus untuk mewarisiinit
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.sumber
Mengapa masalah ini muncul? Yah, fakta yang jelas adalah bahwa itu selalu penting (yaitu di Objective-C, sejak hari saya mulai memprogram Cocoa kembali di Mac OS X 10.0) untuk berurusan dengan inisialisasi yang tidak siap ditangani oleh kelas Anda. Dokumen selalu cukup jelas tentang tanggung jawab Anda dalam hal ini. Tetapi berapa banyak dari kita yang bersusah payah untuk memenuhi mereka, sepenuhnya dan untuk surat itu? Mungkin tidak ada dari kita! Dan kompiler tidak menegakkan mereka; itu semua murni konvensional.
Misalnya, dalam subkelas pengontrol tampilan Objective-C saya dengan penginisialisasi yang ditunjuk ini:
... sangat penting bagi kita untuk melewati koleksi item media yang sebenarnya: instance tidak dapat muncul tanpa satu. Tetapi saya tidak menulis "stopper" untuk mencegah seseorang menginisialisasi saya dengan tulang kosong
init
. Saya seharusnya menulis satu (sebenarnya, dengan benar, saya seharusnya menulis implementasi dariinitWithNibName:bundle:
, initializer yang diwarisi ditunjuk); tapi aku terlalu malas untuk repot, karena aku "tahu" aku tidak akan salah menginisialisasi kelasku dengan cara itu. Ini meninggalkan lubang menganga. Di Objective-C, seseorang dapat memanggil tulang-telanjanginit
, membiarkan ivar saya tidak diinisialisasi, dan kami sampai di sungai tanpa dayung.Swift, luar biasa, menyelamatkan saya dari diri saya sendiri dalam banyak kasus. Segera setelah saya menerjemahkan aplikasi ini ke Swift, seluruh masalah hilang. Swift secara efektif menciptakan penghenti bagi saya! Jika
init(collection:MPMediaItemCollection)
satu-satunya inisialisasi yang dideklarasikan dinyatakan di kelas saya, saya tidak dapat diinisialisasi dengan memanggil bare-boneinit()
. Ini keajaiban!Apa yang terjadi pada seed 5 hanyalah bahwa kompiler telah menyadari bahwa mukjizat tidak bekerja dalam kasus
init(coder:)
, karena secara teori contoh kelas ini dapat berasal dari nib, dan kompiler tidak dapat mencegah itu - dan ketika nib memuat,init(coder:)
akan dipanggil. Jadi kompiler membuat Anda menulis penghenti secara eksplisit. Dan benar juga.sumber
fatalError
penghenti yang dijelaskan dalam stackoverflow.com/a/25128815/341994 . Buat saja Cuplikan Kode pengguna dan mulai sekarang Anda bisa memasukkannya di tempat yang diperlukan. Butuh setengah detik.Menambahkan
sumber