Apa cara terbaik untuk berkomunikasi antara pengontrol tampilan?

165

Menjadi orang baru di bidang obyektif-c, kakao, dan iPhone secara umum, saya memiliki keinginan kuat untuk mendapatkan yang terbaik dari bahasa dan kerangka kerja.

Salah satu sumber yang saya gunakan adalah catatan kelas CS193P milik Stanford yang mereka tinggalkan di web. Ini termasuk catatan kuliah, tugas dan kode sampel, dan karena kursus diberikan oleh Apple dev, saya pasti menganggapnya sebagai "dari mulut kuda".

Situs Web Kelas:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Kuliah 08 terkait dengan tugas untuk membangun aplikasi berbasis UINavigationController yang memiliki beberapa UIViewControllers yang didorong ke tumpukan UINavigationController. Begitulah cara kerja UINavigationController. Itu logis. Namun, ada beberapa peringatan keras di slide tentang komunikasi antara UIViewControllers Anda.

Saya akan mengutip dari slide yang serius ini:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Halaman 16/51:

Bagaimana Tidak Berbagi Data

  • Variabel Global atau lajang
    • Ini termasuk delegasi aplikasi Anda
  • Ketergantungan langsung membuat kode Anda kurang dapat digunakan kembali
    • Dan lebih sulit untuk di-debug & diuji

Baik. Saya kecewa dengan itu. Jangan membabi buta melemparkan semua metode Anda yang akan digunakan untuk berkomunikasi antara viewcontroller ke dalam delegasi aplikasi Anda dan merujuk contoh instance viewcontroller dalam metode delegasi aplikasi. Adil.

Sedikit lebih jauh, kita dapat slide ini memberi tahu kita apa yang harus kita lakukan.

Halaman 18/51:

Praktik Terbaik untuk Aliran Data

  • Cari tahu persis apa yang perlu dikomunikasikan
  • Tetapkan parameter input untuk pengontrol tampilan Anda
  • Untuk berkomunikasi cadangan hierarki, gunakan kopling longgar
    • Tetapkan antarmuka umum untuk pengamat (seperti delegasi)

Slide ini kemudian diikuti oleh apa yang tampak sebagai slide tempat penampung di mana dosen kemudian menunjukkan praktik terbaik menggunakan contoh dengan UIImagePickerController. Saya berharap videonya tersedia! :(

Ok, jadi ... Saya khawatir objc-fu saya tidak begitu kuat. Saya juga agak bingung dengan baris terakhir dalam kutipan di atas. Saya telah melakukan bagian yang adil dari googling tentang hal ini dan saya menemukan apa yang tampaknya menjadi artikel yang layak berbicara tentang berbagai metode teknik Pengamatan / Pemberitahuan:
http://cocoawithlove.com/2008/06/five-approaches-to -listening-observing.html

Metode # 5 bahkan menunjukkan delegasi sebagai metode! Kecuali .... objek hanya dapat menetapkan satu delegasi pada satu waktu. Jadi ketika saya memiliki beberapa komunikasi viewcontroller, apa yang harus saya lakukan?

Ok, itu yang mengatur geng. Saya tahu saya dapat dengan mudah melakukan metode komunikasi dalam delegasi aplikasi dengan merujuk beberapa instance viewcontroller di delegasi app saya, tetapi saya ingin melakukan hal semacam ini dengan cara yang benar .

Tolong bantu saya "melakukan hal yang benar" dengan menjawab pertanyaan-pertanyaan berikut:

  1. Ketika saya mencoba untuk mendorong viewcontroller baru di tumpukan UINavigationController, siapa yang harus melakukan push ini. Yang kelas / file dalam kode saya adalah tempat yang benar?
  2. Ketika saya ingin memengaruhi sebagian data (nilai iVar) di salah satu UIViewControllers saya ketika saya berada di UIViewController yang berbeda , apa cara "benar" untuk melakukan ini?
  3. Memberikan bahwa kita hanya dapat memiliki satu delegasi ditetapkan pada suatu waktu dalam suatu objek, seperti apa implementasi untuk ketika dosen mengatakan "Tentukan antarmuka umum untuk pengamat (seperti delegasi)" . Contoh pseudocode akan sangat membantu di sini jika memungkinkan.
Quinn Taylor
sumber
Beberapa dari ini dibahas dalam artikel ini dari Apple - developer.apple.com/library/ios/#featuredarticles/…
James Moore
Hanya komentar singkat: Video untuk kelas Stanford CS193P sekarang tersedia melalui iTunes U. Yang terbaru (2012-13) dapat dilihat di itunes.apple.com/us/course/coding-together-developing/… dan saya berharap bahwa video dan slide di masa depan akan diumumkan di cs193p.stanford.edu
Thomas Watson

Jawaban:

224

Ini adalah pertanyaan yang bagus, dan bagus untuk melihat bahwa Anda melakukan penelitian ini dan tampaknya peduli dengan belajar bagaimana "melakukannya dengan benar" daripada hanya meretasnya bersama.

Pertama , saya setuju dengan jawaban sebelumnya yang fokus pada pentingnya memasukkan data ke objek model bila perlu (sesuai pola desain MVC). Biasanya Anda ingin menghindari memasukkan informasi status ke dalam pengontrol, kecuali jika itu hanya "presentasi" data.

Kedua , lihat halaman 10 presentasi Stanford untuk contoh tentang cara mendorong pengontrol secara terprogram ke pengontrol navigasi. Untuk contoh cara melakukan ini "secara visual" menggunakan Interface Builder, lihat tutorial ini .

Ketiga , dan mungkin yang paling penting, perhatikan bahwa "praktik terbaik" yang disebutkan dalam presentasi Stanford jauh lebih mudah dipahami jika Anda memikirkannya dalam konteks pola desain "ketergantungan injeksi". Singkatnya, ini berarti bahwa pengontrol Anda tidak harus "melihat" objek yang diperlukan untuk melakukan tugasnya (misalnya, referensi variabel global). Sebagai gantinya, Anda harus selalu "menyuntikkan" dependensi tersebut ke controller (mis., Meneruskan objek yang dibutuhkan melalui metode).

Jika Anda mengikuti pola injeksi ketergantungan, pengontrol Anda akan modular dan dapat digunakan kembali. Dan jika Anda memikirkan dari mana datangnya presenter Stanford (yaitu, sebagai karyawan Apple, pekerjaan mereka adalah membangun kelas yang dapat dengan mudah digunakan kembali), usabilitas dan modularitas adalah prioritas tinggi. Semua praktik terbaik yang mereka sebutkan untuk berbagi data adalah bagian dari injeksi ketergantungan.

Itulah inti dari tanggapan saya. Saya akan menyertakan contoh menggunakan pola injeksi ketergantungan dengan pengontrol di bawah jika itu membantu.

Contoh Menggunakan Injeksi Ketergantungan dengan View Controller

Katakanlah Anda sedang membangun layar tempat beberapa buku terdaftar. Pengguna dapat memilih buku yang ingin dibelinya, lalu ketuk tombol "checkout" untuk menuju ke layar checkout.

Untuk membangun ini, Anda bisa membuat kelas BookPickerViewController yang mengontrol dan menampilkan objek GUI / view. Di mana ia akan mendapatkan semua data buku? Katakanlah itu tergantung pada objek BookWarehouse untuk itu. Jadi sekarang pengontrol Anda pada dasarnya memperantarai data antara objek model (BookWarehouse) dan objek GUI / view. Dengan kata lain, BookPickerViewController TERGANTUNG pada objek BookWarehouse.

Jangan lakukan ini:

@implementation BookPickerViewController

-(void) doSomething {
   // I need to do something with the BookWarehouse so I'm going to look it up
   // using the BookWarehouse class method (comparable to a global variable)
   BookWarehouse *warehouse = [BookWarehouse getSingleton];
   ...
}

Alih-alih, dependensi harus disuntikkan seperti ini:

@implementation BookPickerViewController

-(void) initWithWarehouse: (BookWarehouse*)warehouse {
   // myBookWarehouse is an instance variable
   myBookWarehouse = warehouse;
   [myBookWarehouse retain];
}

-(void) doSomething {
   // I need to do something with the BookWarehouse object which was 
   // injected for me
   [myBookWarehouse listBooks];
   ...
}

Ketika orang-orang Apple berbicara tentang menggunakan pola delegasi untuk "mengkomunikasikan kembali hierarki," mereka masih berbicara tentang injeksi ketergantungan. Dalam contoh ini, apa yang harus dilakukan BookPickerViewController setelah pengguna memilih bukunya dan siap untuk memeriksa? Ya, itu bukan tugasnya. Harus HAPUS yang bekerja untuk beberapa objek lain, yang berarti bahwa ia TERGANTUNG pada objek lain. Jadi kita dapat memodifikasi metode init BookPickerViewController kami sebagai berikut:

@implementation BookPickerViewController

-(void) initWithWarehouse:    (BookWarehouse*)warehouse 
        andCheckoutController:(CheckoutController*)checkoutController 
{
   myBookWarehouse = warehouse;
   myCheckoutController = checkoutController;
}

-(void) handleCheckout {
   // We've collected the user's book picks in a "bookPicks" variable
   [myCheckoutController handleCheckout: bookPicks];
   ...
}

Hasil bersih dari semua ini adalah Anda dapat memberi saya kelas BookPickerViewController Anda (dan GUI / objek tampilan terkait) dan saya dapat dengan mudah menggunakannya di aplikasi saya sendiri, dengan asumsi BookWarehouse dan CheckoutController adalah antarmuka umum (mis. Protokol) yang dapat saya implementasikan :

@interface MyBookWarehouse : NSObject <BookWarehouse> { ... } @end
@implementation MyBookWarehouse { ... } @end

@interface MyCheckoutController : NSObject <CheckoutController> { ... } @end
@implementation MyCheckoutController { ... } @end

...

-(void) applicationDidFinishLoading {
   MyBookWarehouse *myWarehouse = [[MyBookWarehouse alloc]init];
   MyCheckoutController *myCheckout = [[MyCheckoutController alloc]init];

   BookPickerViewController *bookPicker = [[BookPickerViewController alloc] 
                                         initWithWarehouse:myWarehouse 
                                         andCheckoutController:myCheckout];
   ...
   [window addSubview:[bookPicker view]];
   [window makeKeyAndVisible];
}

Akhirnya, BookPickerController Anda tidak hanya dapat digunakan kembali tetapi juga lebih mudah untuk diuji.

-(void) testBookPickerController {
   MockBookWarehouse *myWarehouse = [[MockBookWarehouse alloc]init];
   MockCheckoutController *myCheckout = [[MockCheckoutController alloc]init];

   BookPickerViewController *bookPicker = [[BookPickerViewController alloc] initWithWarehouse:myWarehouse andCheckoutController:myCheckout];
   ...
   [bookPicker handleCheckout];

   // Do stuff to verify that BookPickerViewController correctly called
   // MockCheckoutController's handleCheckout: method and passed it a valid
   // list of books
   ...
}
Clint Harris
sumber
19
Ketika saya melihat pertanyaan (dan jawaban) seperti ini, dibuat dengan sangat hati-hati, saya tidak bisa menahan senyum. Pujian yang pantas diterima untuk penanya pemberani kami dan untuk Anda !! Sementara itu, saya ingin membagikan tautan yang diperbarui untuk tautan invasivecode.com yang berguna yang Anda rujuk di poin kedua Anda: invasivecode.com/2009/09/... - Terima kasih lagi karena telah berbagi wawasan dan praktik terbaik Anda, serta mendukungnya dengan contoh!
Joe D'Andrea
Saya setuju. Pertanyaan itu terbentuk dengan baik, dan jawabannya sangat fantastis. Alih-alih hanya memiliki jawaban teknis, itu juga mencakup beberapa psikologi di balik bagaimana / mengapa itu diterapkan menggunakan DI. Terima kasih! +1 naik.
Kevin Elliott
Bagaimana jika Anda juga ingin menggunakan BookPickerController untuk memilih buku untuk daftar keinginan, atau salah satu dari beberapa kemungkinan alasan bookpicking. Apakah Anda Masih menggunakan pendekatan antarmuka CheckoutController (mungkin berganti nama menjadi sesuatu seperti BookSelectionController) atau mungkin menggunakan NSNotificationCenter?
Les
Ini masih sangat erat. Mengangkat dan mengkonsumsi acara dari tempat yang terpusat akan lebih longgar.
Neil McGuigan
1
Tautan yang dirujuk dalam poin 2 tampaknya telah berubah lagi - inilah tautan yang berfungsi invasivecode.com/blog/archives/322
vikmalhotra
15

Hal semacam ini selalu merupakan masalah selera.

Karena itu, saya selalu lebih suka melakukan koordinasi saya (# 2) melalui objek model. Pengontrol tampilan tingkat atas memuat atau membuat model yang diperlukan, dan masing-masing pengontrol tampilan menetapkan properti di pengontrol anak untuk memberi tahu mereka model objek mana yang harus mereka kerjakan. Sebagian besar perubahan dikomunikasikan cadangan hierarki dengan menggunakan NSNotificationCenter; memecat notifikasi biasanya dibangun untuk model itu sendiri.

Misalnya, saya memiliki aplikasi dengan Akun dan Transaksi. Saya juga memiliki AccountListController, AccountController (yang menampilkan ringkasan akun dengan tombol "tampilkan semua transaksi"), TransactionListController, dan TransactionController. AccountListController memuat daftar semua akun dan menampilkannya. Ketika Anda mengetuk item daftar, itu mengatur properti .account dari AccountController-nya dan mendorong AccountController ke tumpukan. Saat Anda mengetuk tombol "tampilkan semua transaksi", AccountController memuat daftar transaksi, memasukkannya ke properti .transaksi TransactionListController-nya, dan mendorong TransactionListController ke tumpukan, dan seterusnya.

Jika, katakanlah, TransactionController mengedit transaksi, itu membuat perubahan pada objek transaksinya dan kemudian memanggil metode 'save'. 'save' mengirimkan TransactionChangedNotification. Pengontrol lain yang perlu menyegarkan dirinya sendiri ketika transaksi berubah akan mengamati pemberitahuan dan memperbarui itu sendiri. TransactionListController mungkin akan; AccountController dan AccountListController mungkin, tergantung pada apa yang mereka coba lakukan.

Untuk # 1, di aplikasi awal saya, saya punya semacam displayModel: withNavigationController: metode di pengontrol anak yang akan mengatur segalanya dan mendorong controller ke tumpukan. Tetapi karena saya menjadi lebih nyaman dengan SDK, saya sudah menjauh dari itu, dan sekarang saya biasanya meminta orang tua mendorong anak itu.

Untuk # 3, pertimbangkan contoh ini. Di sini kita menggunakan dua pengontrol, AmountEditor dan TextEditor, untuk mengedit dua properti Transaksi. Editor seharusnya tidak benar-benar menyimpan transaksi yang sedang diedit, karena pengguna dapat memutuskan untuk meninggalkan transaksi. Jadi alih-alih mereka berdua mengambil kontroler orangtua mereka sebagai delegasi dan memanggil metode di atasnya mengatakan jika mereka telah mengubah apa pun.

@class Editor;
@protocol EditorDelegate
// called when you're finished.  updated = YES for 'save' button, NO for 'cancel'
- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated;  
@end

// this is an abstract class
@interface Editor : UIViewController {
    id model;
    id <EditorDelegate> delegate;
}
@property (retain) Model * model;
@property (assign) id <EditorDelegate> delegate;

...define methods here...
@end

@interface AmountEditor : Editor
...define interface here...
@end

@interface TextEditor : Editor
...define interface here...
@end

// TransactionController shows the transaction's details in a table view
@interface TransactionController : UITableViewController <EditorDelegate> {
    AmountEditor * amountEditor;
    TextEditor * textEditor;
    Transaction * transaction;
}
...properties and methods here...
@end

Dan sekarang beberapa metode dari TransactionController:

- (void)viewDidLoad {
    amountEditor.delegate = self;
    textEditor.delegate = self;
}

- (void)editAmount {
    amountEditor.model = self.transaction;
    [self.navigationController pushViewController:amountEditor animated:YES];
}

- (void)editNote {
    textEditor.model = self.transaction;
    [self.navigationController pushViewController:textEditor animated:YES];
}

- (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated {
    if(updated) {
        [self.tableView reloadData];
    }

    [self.navigationController popViewControllerAnimated:YES];
}

Yang perlu diperhatikan adalah bahwa kita telah menetapkan protokol umum yang dapat digunakan Editor untuk berkomunikasi dengan pengontrol yang dimilikinya. Dengan melakukannya, kita dapat menggunakan kembali Editor di bagian lain aplikasi. (Mungkin Akun dapat memiliki catatan juga.) Tentu saja, protokol EditorDelegate dapat berisi lebih dari satu metode; dalam hal ini hanya itu yang diperlukan.

Brent Royal-Gordon
sumber
1
Apakah ini seharusnya berfungsi apa adanya? Saya mengalami masalah dengan Editor.delegateanggota. Dalam viewDidLoadmetode saya , saya mengerti Property 'delegate' not found.... Saya hanya tidak yakin apakah saya mengacaukan sesuatu yang lain. Atau jika ini diringkas untuk singkatnya.
Jeff
Ini sekarang kode yang cukup lama, ditulis dalam gaya yang lebih tua dengan konvensi yang lebih tua. Saya tidak akan menyalin dan menempelkannya langsung ke proyek Anda; Saya hanya mencoba belajar dari polanya.
Brent Royal-Gordon
Kena kau. Itulah yang ingin saya ketahui. Saya membuatnya bekerja dengan beberapa modifikasi, tetapi saya sedikit khawatir bahwa itu tidak cocok dengan kata demi kata.
Jeff
0

Saya melihat masalah Anda ..

Apa yang terjadi adalah seseorang memiliki ide yang membingungkan tentang arsitektur MVC.

MVC memiliki tiga bagian .. model, pandangan, dan pengontrol .. Masalah yang dinyatakan tampaknya telah menggabungkan dua dari mereka tanpa alasan yang baik. pandangan dan pengontrol adalah bagian logika yang terpisah.

jadi ... Anda tidak ingin memiliki banyak pengontrol tampilan ..

Anda ingin memiliki banyak tampilan, dan pengontrol yang memilih di antara mereka. (Anda juga bisa memiliki banyak pengontrol, jika Anda memiliki beberapa aplikasi)

pandangan TIDAK boleh membuat keputusan. Pengontrol harus melakukan itu. Karenanya pemisahan tugas, dan logika, dan cara membuat hidup Anda lebih mudah.

Jadi .. pastikan pandangan Anda hanya melakukan itu, mengeluarkan veiw data yang bagus. biarkan pengontrol Anda memutuskan apa yang harus dilakukan dengan data, dan tampilan mana yang digunakan.

(dan ketika kita berbicara tentang data, kita berbicara tentang model ... cara standar yang bagus untuk disimpan, diakses, dimodifikasi .. sepotong logika terpisah yang dapat kita pisahkan dan lupakan)

Bingy
sumber
0

Misalkan ada dua kelas A dan B.

turunan dari kelas A adalah

Instansi;

kelas A membuat dan instance dari kelas B, sebagai

BInstance;

Dan dalam logika kelas B Anda, di suatu tempat Anda diminta untuk berkomunikasi atau memicu metode kelas A.

1) Cara yang salah

Anda bisa meneruskan Instansi ke Instansi. sekarang letakkan panggilan metode yang diinginkan [nama metode aInstance] dari lokasi yang diinginkan di bInstance.

Ini akan melayani tujuan Anda, tetapi sementara rilis akan menyebabkan memori dikunci dan tidak dibebaskan.

Bagaimana?

Ketika Anda meneruskan Instance ke bInstance, kami meningkatkan retaincount ofInstance sebesar 1. Ketika membatalkan alokasi bInstance, kami akan memiliki memori yang diblokir karena Instance tidak pernah dapat dibawa ke 0 retaincount dengan alasan bInstance adalah bahwa bInstance itu sendiri adalah objek dari Instance.

Lebih lanjut, karena Instansi sedang macet, memori bInstance juga akan macet (bocor). Jadi, bahkan setelah deallocating suatu Instance sendiri ketika waktunya tiba nanti, memorinya juga akan diblokir karena bInstance tidak dapat dibebaskan dan bInstance adalah variabel kelas dari Instance.

2) Jalan yang benar

Dengan mendefinisikan suatu Instance sebagai delegasi dari Instance, tidak akan ada perubahan jumlah penyimpanan atau keterikatan memori dari Instance.

bInstance akan dapat dengan bebas memanggil metode delegasi yang ada di dalam Instance. Pada deallokasi bInstance, semua variabel akan dibuat sendiri dan akan dirilis pada alokasi aInstance, karena tidak ada keterikatan suatu Instance dalam bInstance, itu akan dirilis dengan bersih.

rd_
sumber