Saya memiliki masalah ketika menerapkan pola-MVC di iOS. Saya telah mencari di internet tetapi sepertinya tidak menemukan solusi yang bagus untuk masalah ini.
Banyak UITableViewController
implementasi tampaknya agak besar. Kebanyakan contoh yang saya lihat memungkinkan UITableViewController
implementasi <UITableViewDelegate>
dan <UITableViewDataSource>
. Implementasi ini adalah alasan utama mengapa UITableViewController
semakin besar. Salah satu solusinya adalah membuat kelas terpisah yang mengimplementasikan <UITableViewDelegate>
dan <UITableViewDataSource>
. Tentu saja kelas-kelas ini harus memiliki referensi ke UITableViewController
. Apakah ada kekurangan menggunakan solusi ini? Secara umum saya pikir Anda harus mendelegasikan fungsionalitas ke kelas "Pembantu" lainnya atau serupa, menggunakan pola delegasi. Apakah ada cara yang mapan untuk memecahkan masalah ini?
Saya tidak ingin model mengandung terlalu banyak fungsi, atau tampilan. Saya percaya bahwa logika harus benar-benar berada di kelas controller, karena ini adalah salah satu landasan pola MVC. Namun pertanyaan besarnya adalah:
Bagaimana seharusnya Anda membagi pengontrol implementasi-MVC menjadi bagian-bagian yang lebih kecil yang dapat dikelola? (Berlaku untuk MVC di iOS dalam kasus ini)
Mungkin ada pola umum untuk menyelesaikan ini, meskipun saya secara khusus mencari solusi untuk iOS. Tolong beri contoh pola yang baik untuk memecahkan masalah ini. Berikan argumen mengapa solusi Anda luar biasa.
UITableViewController
mekanika tampaknya cukup aneh bagi saya, jadi saya bisa mengaitkannya dengan masalah. Aku Aku digunakan benar-benar senangMonoTouch
, karenaMonoTouch.Dialog
secara khusus membuat yang lebih mudah untuk bekerja dengan tabel di iOS. Sementara itu, saya ingin tahu apa yang mungkin disarankan orang lain yang lebih berpengetahuan di sini ...Jawaban:
Saya menghindari penggunaan
UITableViewController
, karena menempatkan banyak tanggung jawab ke dalam satu objek. Oleh karena itu saya memisahkanUIViewController
subclass dari sumber data dan mendelegasikan. Tanggung jawab pengontrol tampilan adalah menyiapkan tampilan tabel, membuat sumber data dengan data, dan menyatukan semuanya. Mengubah cara tampilan tabel diwakili dapat dilakukan tanpa mengubah pengontrol tampilan, dan memang pengontrol tampilan yang sama dapat digunakan untuk beberapa sumber data yang semuanya mengikuti pola ini. Demikian pula, mengubah alur kerja aplikasi berarti perubahan pada pengontrol tampilan tanpa khawatir tentang apa yang terjadi pada tabel.Saya sudah mencoba memisahkan protokol
UITableViewDataSource
danUITableViewDelegate
menjadi objek yang berbeda, tetapi yang biasanya berakhir dengan pembelahan palsu karena hampir setiap metode pada delegasi perlu menggali ke dalam sumber data (misalnya pada seleksi, delegasi perlu tahu objek apa yang diwakili oleh baris yang dipilih). Jadi saya berakhir dengan satu objek yang merupakan sumber data dan delegasi. Objek ini selalu menyediakan metode di-(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
mana sumber data dan aspek pendelegasian perlu mengetahui apa yang sedang mereka kerjakan.Itu pemisahan "tingkat 0" saya. Level 1 bertunangan jika saya harus mewakili objek dari berbagai jenis dalam tampilan tabel yang sama. Sebagai contoh, bayangkan Anda harus menulis aplikasi Kontak — untuk satu kontak, Anda mungkin memiliki baris yang mewakili nomor telepon, baris lain yang mewakili alamat, yang lain mewakili alamat email, dan sebagainya. Saya ingin menghindari pendekatan ini:
Dua solusi telah muncul sejauh ini. Pertama adalah membangun pemilih secara dinamis:
Dalam pendekatan ini, Anda tidak perlu mengedit
if()
pohon epik untuk mendukung tipe baru - cukup tambahkan metode yang mendukung kelas baru. Ini adalah pendekatan yang bagus jika tampilan tabel ini adalah satu-satunya yang perlu mewakili objek-objek ini, atau perlu menyajikannya dengan cara khusus. Jika objek yang sama akan diwakili dalam tabel yang berbeda dengan sumber data yang berbeda, pendekatan ini rusak karena metode pembuatan sel perlu dibagi di seluruh sumber data — Anda bisa menentukan superclass umum yang menyediakan metode ini, atau Anda bisa melakukan ini:Kemudian di kelas sumber data Anda:
Ini berarti bahwa setiap sumber data yang perlu menampilkan nomor telepon, alamat, dll. Bisa saja bertanya objek apa pun yang diwakili untuk sel tampilan tabel. Sumber data itu sendiri tidak perlu lagi tahu apa-apa tentang objek yang ditampilkan.
"Tapi, tunggu," aku mendengar kata sela hipotetis, "bukankah itu merusak MVC? Apakah kamu tidak memasukkan detail tampilan ke dalam kelas model?"
Tidak, itu tidak merusak MVC. Anda dapat menganggap kategori dalam hal ini sebagai penerapan Dekorator ; begitu
PhoneNumber
juga kelas model tetapiPhoneNumber(TableViewRepresentation)
merupakan kategori tampilan. Sumber data (objek pengontrol) memediasi antara model dan tampilan, sehingga arsitektur MVC masih berlaku.Anda dapat melihat penggunaan kategori ini sebagai hiasan dalam kerangka kerja Apple juga.
NSAttributedString
adalah kelas model, memegang beberapa teks dan atribut. AppKit menyediakanNSAttributedString(AppKitAdditions)
dan UIKit menyediakanNSAttributedString(NSStringDrawing)
, kategori dekorator yang menambahkan perilaku menggambar ke kelas model ini.sumber
cellForPhotoAtIndexPath
metode sumber data, kemudian memanggil metode pabrik yang sesuai. Yang tentu saja hanya mungkin jika kelas tertentu diprediksi menempati baris tertentu. Sistem Anda pada kategori-model yang menghasilkan tampilan jauh lebih elegan dalam praktiknya, saya pikir, meskipun mungkin itu adalah pendekatan yang tidak lazim untuk MVC! :)Orang-orang cenderung banyak mengemasnya ke dalam UIViewController / UITableViewController.
Delegasi ke kelas lain (bukan view controller) biasanya bekerja dengan baik. Delegasi tidak perlu memerlukan referensi kembali ke pengontrol tampilan, karena semua metode delegasi mendapatkan referensi ke
UITableView
, tetapi mereka akan membutuhkan akses entah bagaimana untuk data yang mereka delegasikan.Beberapa ide untuk reorganisasi untuk mengurangi panjang:
jika Anda membuat sel tampilan tabel dalam kode, pertimbangkan untuk memuatnya dari file nib atau dari storyboard. Storyboard memungkinkan prototipe dan sel tabel statis - periksa fitur-fitur itu jika Anda tidak terbiasa
jika metode delegasi Anda mengandung banyak pernyataan 'jika' (atau beralih pernyataan) itu adalah tanda klasik bahwa Anda dapat melakukan beberapa refactoring
Itu selalu terasa agak lucu bagi saya bahwa
UITableViewDataSource
bertanggung jawab untuk menangani sedikit data yang benar dan mengonfigurasi tampilan untuk menunjukkannya. Satu titik refactoring yang bagus mungkin untuk mengubah AndacellForRowAtIndexPath
untuk menangani data yang perlu ditampilkan dalam sel, kemudian mendelegasikan pembuatan tampilan sel ke delegasi lain (misalnya membuatCellViewDelegate
atau serupa) yang akan diteruskan dalam item data yang sesuai.sumber
Berikut kira-kira apa yang sedang saya lakukan saat menghadapi masalah serupa:
Memindahkan operasi terkait data ke kelas XXXDataSource (yang mewarisi dari BaseDataSource: NSObject). BaseDataSource menyediakan beberapa metode yang mudah digunakan seperti
- (NSUInteger)rowsInSection:(NSUInteger)sectionNum;
, subclass menimpa metode pemuatan data (seperti aplikasi yang biasanya memiliki semacam metode pemuatan cache secara offline seperti- (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;
sehingga kami dapat memperbarui UI dengan data cache yang diterima di LoadProgressBlock sementara kami memperbarui info dari jaringan, dan dalam penyelesaian blok kami menyegarkan UI dengan data baru dan menghapus indikator progess, jika ada). Kelas-kelas itu TIDAK sesuai denganUITableViewDataSource
protokol.Dalam BaseTableViewController (yang sesuai dengan
UITableViewDataSource
danUITableViewDelegate
protokol) saya punya referensi ke BaseDataSource, yang saya buat selama init pengontrol. DiUITableViewDataSource
bagian controller saya cukup mengembalikan nilai dari dataSource (seperti- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }
).Berikut ini adalah cellForRow saya di kelas dasar (tidak perlu diganti dalam subkelas):
configureCell harus diganti oleh subclass dan createCell mengembalikan UITableViewCell, jadi jika Anda ingin sel kustom, timpa juga.
Setelah hal-hal dasar dikonfigurasikan (sebenarnya, dalam proyek pertama yang menggunakan skema seperti itu, setelah itu bagian ini dapat digunakan kembali) yang tersisa untuk
BaseTableViewController
subkelas adalah:Override configureCell (ini biasanya berubah menjadi meminta dataSource untuk objek untuk jalur indeks dan mengumpankannya ke configureWithXXX: metode atau mendapatkan representasi UITableViewCell objek seperti dalam jawaban user4051)
Override didSelectRowAtIndexPath: (jelas)
Tulislah subclass BaseDataSource yang menangani pekerjaan dengan bagian yang diperlukan dari Model (misalkan ada 2 kelas
Account
danLanguage
, jadi subclass akan menjadi AccountDataSource dan LanguageDataSource).Dan itu semua untuk bagian tampilan tabel. Saya dapat memposting beberapa kode ke GitHub jika diperlukan.
Sunting: beberapa rekomendasi dapat ditemukan di http://www.objc.io/issue-1/lighter-view-controllers.html (yang memiliki tautan ke pertanyaan ini) dan artikel pendamping tentang tableviewcontrollers.
sumber
Pandangan saya tentang ini adalah bahwa model perlu memberikan array objek yang disebut ViewModel atau viewData yang dienkapsulasi dalam cellConfigurator. CellConfigurator memegang CellInfo yang diperlukan untuk menghapusnya dan mengkonfigurasi sel. itu memberi sel beberapa data sehingga sel dapat mengkonfigurasi sendiri. ini juga berfungsi dengan bagian jika Anda menambahkan beberapa objek SectionConfigurator yang memegang CellConfigurators. Saya mulai menggunakan ini beberapa waktu lalu pada awalnya hanya memberikan sel viewData dan memiliki kesepakatan ViewController dengan mengeluarkan sel. tapi saya membaca sebuah artikel yang menunjuk ke repo gitHub ini.
https://github.com/fastred/ConfigurableTableViewController
ini dapat mengubah cara Anda mendekati ini.
sumber
Saya baru-baru ini menulis sebuah artikel tentang bagaimana menerapkan delegasi dan sumber data untuk UITableView: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controller/
Gagasan utamanya adalah untuk membagi tanggung jawab menjadi beberapa kelas yang terpisah, seperti pabrik sel, pabrik bagian, dan menyediakan beberapa antarmuka umum untuk model yang akan ditampilkan oleh UITableView. Diagram di bawah ini menjelaskan semuanya:
sumber
Mengikuti prinsip-prinsip SOLID akan menyelesaikan segala jenis masalah seperti ini.
Jika Anda ingin kelas Anda untuk memiliki JUST A TUNGGAL tanggung jawab, Anda harus mendefinisikan terpisah
DataSource
danDelegate
kelas dan hanya menyuntikkan mereka ketableView
pemilik (bisaUITableViewController
atauUIViewController
atau apa pun). Inilah cara Anda mengatasi pemisahan kekhawatiran .Tetapi jika Anda hanya ingin memiliki kode yang bersih dan mudah dibaca dan ingin menyingkirkan file viewController yang besar itu dan Anda menggunakan Swif , Anda dapat menggunakan
extension
s untuk itu. Ekstensi dari kelas tunggal dapat ditulis dalam file yang berbeda dan semuanya memiliki akses satu sama lain. Tapi ini benar-benar memecahkan masalah SoC nit seperti yang saya sebutkan.sumber