Saya cenderung hanya menempatkan kebutuhan (properti yang disimpan, inisialisasi) ke dalam definisi kelas saya dan memindahkan semua yang lain ke dalam mereka sendiri extension
, semacam seperti extension
per blok logis yang akan saya kelompokkan // MARK:
juga.
Untuk subkelas UIView misalnya, saya akan berakhir dengan ekstensi untuk hal-hal yang berhubungan dengan tata letak, satu untuk berlangganan dan menangani acara dan sebagainya. Dalam ekstensi ini, saya pasti harus mengganti beberapa metode UIKit, misalnya layoutSubviews
. Saya tidak pernah melihat ada masalah dengan pendekatan ini - sampai hari ini.
Ambil hierarki kelas ini sebagai contoh:
public class C: NSObject {
public func method() { print("C") }
}
public class B: C {
}
extension B {
override public func method() { print("B") }
}
public class A: B {
}
extension A {
override public func method() { print("A") }
}
(A() as A).method()
(A() as B).method()
(A() as C).method()
Outputnya adalah A B C
. Itu tidak masuk akal bagi saya. Saya membaca tentang ekstensi protokol yang dikirim secara statis, tetapi ini bukan protokol. Ini adalah kelas reguler, dan saya berharap pemanggilan metode secara dinamis dikirim saat runtime. Jelas panggilan pada C
setidaknya harus secara dinamis dikirim dan diproduksi C
?
Jika saya menghapus warisan dari NSObject
dan membuat C
kelas root, kompiler mengeluh mengatakan declarations in extensions cannot override yet
, yang sudah saya baca. Tetapi bagaimana memiliki NSObject
sebagai kelas dasar mengubah banyak hal?
Pindah kedua menimpa ke deklarasi kelas mereka menghasilkan A A A
seperti yang diharapkan, bergerak hanya B
's menghasilkan A B B
, bergerak hanya A
' s menghasilkan C B C
, yang terakhir yang membuat benar-benar tidak masuk akal bagi saya: bahkan tidak satu statis diketik untuk A
menghasilkan A
-Output lagi!
Menambahkan dynamic
kata kunci ke definisi atau override tampaknya memberi saya perilaku yang diinginkan 'dari titik itu dalam hierarki kelas ke bawah' ...
Mari kita ubah contoh kita menjadi sesuatu yang sedikit kurang dibangun, apa yang sebenarnya membuat saya memposting pertanyaan ini:
public class B: UIView {
}
extension B {
override public func layoutSubviews() { print("B") }
}
public class A: B {
}
extension A {
override public func layoutSubviews() { print("A") }
}
(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()
Sekarang kita dapatkan A B A
. Di sini saya tidak dapat membuat layoutSubviews UIView menjadi dinamis dengan cara apa pun.
Memindahkan kedua penimpaan ke dalam deklarasi kelas mereka membuat kita A A A
lagi, hanya A atau hanya B yang masih membuat kita A B A
. dynamic
lagi memecahkan masalah saya.
Secara teori saya bisa menambahkan dynamic
semua yang override
pernah saya lakukan tetapi saya merasa seperti melakukan sesuatu yang salah di sini.
Apakah benar salah menggunakan extension
kode pengelompokan seperti yang saya lakukan?
sumber
Jawaban:
Ekstensi tidak bisa / tidak boleh diganti.
Tidak mungkin untuk menimpa fungsionalitas (seperti properti atau metode) dalam ekstensi seperti yang didokumentasikan dalam Panduan Swift Apple.
Panduan Pengembang Swift
Kompiler memungkinkan Anda mengganti ekstensi untuk kompatibilitas dengan Objective-C. Tapi itu sebenarnya melanggar arahan bahasa.
😊Itu mengingatkan saya pada " Tiga Hukum Robot " karya Isaac Asimov 🤖
Ekstensi ( gula sintaksis ) mendefinisikan metode independen yang menerima argumen mereka sendiri. Fungsi yang dipanggil yaitu
layoutSubviews
tergantung pada konteks yang diketahui kompiler ketika kode dikompilasi. UIView mewarisi dari UIResponder yang mewarisi dari NSObject sehingga penggantian dalam ekstensi diizinkan tetapi tidak boleh .Jadi tidak ada yang salah dengan pengelompokan tetapi Anda harus menimpa di kelas bukan di ekstensi.
Catatan Petunjuk
Anda hanya dapat
override
menggunakan metode superclass yaituload()
initialize()
dalam ekstensi subclass jika metode ini kompatibel dengan Objective-C.Oleh karena itu kita dapat melihat mengapa ini memungkinkan Anda untuk mengkompilasi menggunakan
layoutSubviews
.Semua aplikasi Swift mengeksekusi di dalam runtime Objective-C kecuali untuk saat menggunakan kerangka kerja Swift-only murni yang memungkinkan untuk runtime hanya Swift.
Seperti yang kami temukan runtime Objective-C umumnya memanggil dua metode utama kelas
load()
daninitialize()
secara otomatis ketika menginisialisasi kelas dalam proses aplikasi Anda.Mengenai
dynamic
pengubahDari Perpustakaan Pengembang Apple (archive.org)
Anda dapat menggunakan
dynamic
pengubah untuk meminta agar akses ke anggota dikirimkan secara dinamis melalui runtime Objective-C.Ketika Swift API diimpor oleh runtime Objective-C, tidak ada jaminan pengiriman dinamis untuk properti, metode, subskrip, atau inisialisasi. Kompilator Swift mungkin masih dapat devirtualize atau akses anggota sebaris untuk mengoptimalkan kinerja kode Anda, melewati runtime Objective-C. 😳
Jadi
dynamic
dapat diterapkan kelayoutSubviews
-> AndaUIView Class
karena diwakili oleh Objective-C dan akses ke anggota tersebut selalu digunakan menggunakan runtime Objective-C.Itu sebabnya kompiler memungkinkan Anda untuk menggunakan
override
dandynamic
.sumber
Salah satu tujuan Swift adalah pengiriman statis, atau lebih tepatnya pengurangan pengiriman dinamis. Namun Obj-C adalah bahasa yang sangat dinamis. Situasi yang Anda lihat berasal dari hubungan antara 2 bahasa dan cara mereka bekerja sama. Seharusnya tidak benar-benar kompilasi.
Salah satu poin utama tentang ekstensi adalah ekstensi, bukan untuk penggantian / penggantian. Jelas dari nama dan dokumentasi bahwa ini adalah niatnya. Memang jika Anda mengambil tautan ke Obj-C dari kode Anda (hapus
NSObject
sebagai superclass) itu tidak akan dikompilasi.Jadi, kompiler sedang mencoba untuk memutuskan apa yang dapat dikirim secara statis dan apa yang harus dikirim secara dinamis, dan itu jatuh melalui celah karena tautan Obj-C dalam kode Anda. Alasan
dynamic
'berhasil' adalah karena memaksa Obj-C menghubungkan semua hal sehingga semuanya selalu dinamis.Jadi, tidak salah menggunakan ekstensi untuk pengelompokan, itu bagus, tetapi salah untuk menimpa ekstensi. Timpa apa pun harus di kelas utama itu sendiri, dan memanggil poin ekstensi.
sumber
supportedInterfaceOrientations
diUINavigationController
(untuk tujuan menunjukkan pandangan yang berbeda dalam orientasi yang berbeda), Anda harus menggunakan kelas kustom bukan perpanjangan? Banyak jawaban menyarankan menggunakan ekstensi untuk menggantisupportedInterfaceOrientations
tetapi akan senang klarifikasi. Terima kasih!Ada cara untuk mencapai pemisahan yang bersih dari tanda tangan kelas dan implementasi (dalam ekstensi) sambil mempertahankan kemampuan untuk menimpa subclass. Caranya adalah dengan menggunakan variabel di tempat fungsi
Jika Anda memastikan untuk menentukan setiap subclass dalam file sumber swift yang terpisah, Anda dapat menggunakan variabel yang dikomputasi untuk penggantian sambil menjaga implementasi terkait terorganisir rapi dalam ekstensi. Ini akan menghindari "aturan" Swift dan akan membuat API / tanda tangan kelas Anda diatur dengan rapi di satu tempat:
...
...
Setiap ekstensi kelas dapat menggunakan nama metode yang sama untuk implementasi karena mereka bersifat pribadi dan tidak terlihat satu sama lain (selama mereka berada di file yang terpisah).
Seperti yang Anda lihat pewarisan (menggunakan nama variabel) berfungsi dengan baik menggunakan super.variablename
sumber
Jawaban ini tidak ditujukan pada OP, selain dari fakta bahwa saya merasa terinspirasi untuk menanggapi dengan pernyataannya, "Saya cenderung hanya menempatkan kebutuhan (properti yang disimpan, inisialisasi) ke dalam definisi kelas saya dan memindahkan semua yang lain ke ekstensi mereka sendiri. .. " Saya terutama seorang programmer C #, dan di C # satu dapat menggunakan kelas parsial untuk tujuan ini. Misalnya, Visual Studio menempatkan hal-hal terkait UI dalam file sumber terpisah menggunakan kelas parsial, dan membiarkan file sumber utama Anda tidak berantakan sehingga Anda tidak memiliki gangguan itu.
Jika Anda mencari "kelas parsial swift" Anda akan menemukan berbagai tautan di mana pengikut Swift mengatakan bahwa Swift tidak memerlukan kelas parsial karena Anda dapat menggunakan ekstensi. Menariknya, jika Anda mengetik "ekstensi cepat" ke dalam bidang pencarian Google, saran pencarian pertamanya adalah "penggantian ekstensi cepat", dan saat ini pertanyaan Stack Overflow ini adalah hit pertama. Saya menganggap itu berarti bahwa masalah dengan (kurangnya) kemampuan menimpa adalah topik yang paling dicari terkait dengan ekstensi Swift, dan menyoroti fakta bahwa ekstensi Swift tidak dapat mengganti kelas parsial, setidaknya jika Anda menggunakan kelas turunan dalam pemrograman.
Lagi pula, untuk mempersingkat pengantar yang bertele-tele, saya mengalami masalah ini dalam situasi di mana saya ingin memindahkan beberapa metode boilerplate / bagasi dari file sumber utama untuk kelas Swift yang dihasilkan oleh program C # -to-Swift saya. Setelah mengalami masalah tidak ada penggantian yang diizinkan untuk metode ini setelah memindahkannya ke ekstensi, saya akhirnya menerapkan solusi sederhana berikut ini. File sumber Swift utama masih mengandung beberapa metode rintisan kecil yang memanggil metode nyata dalam file ekstensi, dan metode ekstensi ini diberi nama unik untuk menghindari masalah penggantian.
.
.
.
.
Seperti yang saya katakan dalam pengantar saya, ini tidak benar-benar menjawab pertanyaan OP, tapi saya berharap solusi sederhana ini dapat membantu orang lain yang ingin memindahkan metode dari file sumber utama ke file ekstensi dan menjalankan no. -mengatasi masalah.
sumber
Gunakan POP (Pemrograman Berorientasi Protokol) untuk mengabaikan fungsi dalam ekstensi.
sumber