Görünüm denetleyicileri arasında iletişim kurmanın en iyi yolu nedir?


165

Objektif-c, kakao ve iPhone dev'de genel olarak yeni olduğum için, dilden ve çerçevelerden en iyi şekilde yararlanmak için güçlü bir arzum var.

Kullandığım kaynaklardan biri, Stanford'un web'de bıraktıkları CS193P sınıfı notları. Ders notlarını, ödevleri ve örnek kodu içerir ve ders Apple dev'in verdiği için kesinlikle "atın ağzından" olduğunu düşünüyorum.

Sınıf Web Sitesi:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Ders 08 UINavigationController yığınına itilmiş birden fazla UIViewController içeren bir UINavigationController tabanlı uygulama oluşturmak için bir atama ile ilgilidir. UINavigationController böyle çalışır. Bu mantıklı. Bununla birlikte, slaytta UIViewControllers'ınız arasında iletişim kurma konusunda bazı sert uyarılar vardır.

Bu ciddi slaytlardan alıntı yapacağım:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Sayfa 16/51:

Verileri Paylaşmama

  • Global Değişkenler veya singletonlar
    • Buna uygulama temsilciniz de dahildir
  • Doğrudan bağımlılıklar kodunuzu daha az yeniden kullanılabilir hale getirir
    • Hata ayıklamak ve test etmek daha zor

Tamam. Ben bununla beraberim. Viewcontroller arasında iletişim kurmak için kullanılacak tüm yöntemlerinizi körü körüne atmayın ve uygulama delege yöntemlerindeki viewcontroller örneklerine başvurmayın. Adil 'nuff.

Biraz daha ileride, bize ne yapmamız gerektiğini söyleyen bu slaydı alıyoruz .

Sayfa 18/51:

Veri Akışı için En İyi Uygulamalar

  • Şekil dışarı tam olarak tebliğ gerekenleri
  • Görünüm denetleyiciniz için giriş parametrelerini tanımlayın
  • Hiyerarşiyi yedeklemek için gevşek bağlantı kullanın
    • Gözlemciler için genel bir arabirim tanımlama (temsilci seçme gibi)

Bu slaytı daha sonra, öğretim elemanının UIImagePickerController ile bir örnek kullanarak en iyi uygulamaları gösterdiği yer tutucu slayt olduğu anlaşılır. Keşke videolar mevcut olsaydı! :(

Tamam, korkarım ... objc-fu'm o kadar güçlü değil. Ayrıca yukarıdaki alıntıdaki son satırdan biraz kafam karıştı. Ben bu konuda googling adil payımı yapıyordum ve çeşitli Gözlem / Bildirim teknikleri yöntemleri hakkında konuşan iyi bir makale gibi görünen buldum:
http://cocoawithlove.com/2008/06/five-approaches-to dinleme;-observing.html

5. yöntem bile delegeleri bir yöntem olarak gösterir! Dışında .... nesneler aynı anda yalnızca bir delege ayarlayabilir. Peki, birden çok viewcontroller iletişimim olduğunda ne yapacağım?

Tamam, bu kurulan çete. Ben kolayca benim appdelegate çoklu viewcontroller örnekleri referans ile uygulama delege iletişim yöntemleri yapabilirim biliyorum ama ben bu şekilde bir şey doğru şekilde yapmak istiyorum .

Lütfen aşağıdaki soruları yanıtlayarak "doğru olanı yapmama" yardım edin:

  1. UINavigationController yığını üzerinde yeni bir viewcontroller itmeye çalıştığımda, kim bu push yapıyor olmalı. Kodumdaki hangi sınıf / dosya doğru yer?
  2. Farklı bir UIViewController içinde olduğum zaman benim UIViewControllers birinde bazı veri parçasını (bir iVar değeri) etkilemek istediğinizde, bunu yapmak için "doğru" yolu nedir?
  3. Bir nesnede aynı anda yalnızca bir delege ayarlayabileceğimizi söyleyin, öğretim görevlisi "Gözlemciler için genel bir arabirim tanımlayın (delegasyon gibi)" derken uygulama neye benzeyecektir . Bir sözde kod örneği, mümkünse burada çok yardımcı olacaktır.

Bunlardan bazıları bu makalede Apple'dan ele alınmıştır - developer.apple.com/library/ios/#featuredarticles/…
James Moore

Sadece hızlı sözler: Stanford CS193P sınıf için videoları artık iTunes U. The son (2012-13) yoluyla ulaşılabilir görülebilir olduğunu itunes.apple.com/us/course/coding-together-developing/... ve ben bekliyoruz gelecekteki videolar ve slaytlar cs193p.stanford.edu
Thomas Watson

Yanıtlar:


224

Bunlar iyi sorular ve bu araştırmayı yaptığınızı görmek harika ve sadece birlikte kesmek yerine "doğru yapmayı" öğrenmekle ilgileniyor gibi görünüyor.

İlk olarak , uygun olduğunda model nesnelerine (MVC tasarım modeline göre) veri koymanın önemine odaklanan önceki cevaplara katılıyorum. Genellikle "sunum" verileri olmadıkça, durum bilgilerini bir denetleyicinin içine koymaktan kaçınmak istersiniz.

İkinci olarak , bir kontrolörün navigasyon kontrolörüne programlı olarak nasıl aktarılacağına ilişkin bir örnek için Stanford sunumunun 10. sayfasına bakın. Arayüz Oluşturucu'yu kullanarak bunu "görsel olarak" nasıl yapacağınıza dair bir örnek için bu eğiticiye bir göz atın.

Üçüncüsü ve belki de en önemlisi, Stanford sunumunda bahsedilen "en iyi uygulamaların" "bağımlılık enjeksiyonu" tasarım modeli bağlamında bunları düşünürseniz anlamanın çok daha kolay olduğunu unutmayın. Özetle, bu, denetleyicinizin işini yapmak için ihtiyaç duyduğu nesnelere "bakmaması" gerektiği anlamına gelir (örneğin, global bir değişkene bakın). Bunun yerine, bu bağımlılıkları her zaman denetleyiciye "enjekte etmelisiniz" (yani, ihtiyaç duyduğu nesneleri yöntemlerle geçirin).

Bağımlılık enjeksiyon modelini takip ederseniz, kontrol cihazınız modüler ve tekrar kullanılabilir olacaktır. Ve eğer Stanford sunucularının nereden geldiğini düşünürseniz (yani Apple çalışanları olarak işleri kolayca yeniden kullanılabilecek sınıflar oluşturmaktır), yeniden kullanılabilirlik ve modülerlik yüksek önceliklerdir. Veri paylaşımı için bahsettikleri en iyi uygulamaların tümü bağımlılık enjeksiyonunun bir parçasıdır.

Bu benim cevabımın özü. Yararlı olması durumunda bağımlılık enjeksiyon desenini aşağıdaki bir denetleyiciyle kullanma örneğini ekleyeceğim.

Bir View Controller ile Bağımlılık Enjeksiyonu Kullanma Örneği

Birkaç kitabın listelendiği bir ekran oluşturduğunuzu varsayalım. Kullanıcı satın almak istediği kitapları seçebilir ve ardından ödeme ekranına gitmek için bir "ödeme" düğmesine dokunabilir.

Bunu oluşturmak için, GUI / görünüm nesnelerini denetleyen ve görüntüleyen bir BookPickerViewController sınıfı oluşturabilirsiniz. Tüm kitap verilerini nereden alacak? Bunun için bir BookWarehouse nesnesine bağlı olduğunu varsayalım. Şimdi kontrolörünüz temel olarak bir model nesnesi (BookWarehouse) ve GUI / görünüm nesneleri arasında veri aracılığı yapıyor. Başka bir deyişle, BookPareView nesnesinde BookPickerViewController DEPENDS.

Bunu yapma:

@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];
   ...
}

Bunun yerine, bağımlılıklar şu şekilde enjekte edilmelidir:

@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];
   ...
}

Apple adamları "hiyerarşiyi yedeklemek için" temsilci seçme modelinden bahsederken hala bağımlılık enjeksiyonundan bahsediyorlar. Bu örnekte, kullanıcı kitaplarını seçip kontrol etmeye hazır olduğunda BookPickerViewController ne yapmalıdır? Aslında bu onun işi değil. Bu çalışmayı başka bir nesneye DELEGATE yapmalıdır, yani başka bir nesneye DEPENDS. Bu nedenle BookPickerViewController init yöntemimizi aşağıdaki gibi değiştirebiliriz:

@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];
   ...
}

Tüm bunların net sonucu bana BookPickerViewController sınıfınızı (ve ilgili GUI / görünüm nesnelerini) verebilmeniz ve BookWarehouse ve CheckoutController'ın uygulayabileceğim genel arayüzler (yani protokoller) olduğunu varsayarak kendi uygulamamda kolayca kullanabilmemdir. :

@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];
}

Son olarak, yalnızca BookPickerController yeniden kullanılabilir değil aynı zamanda test edilmesi de kolaydır.

-(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
   ...
}

19
Böyle bir özenle hazırlanmış böyle sorular (ve cevaplar) gördüğümde yardım edemem, gülümsemem. Bizim cesur sorgulayıcı ve size haklı kudos !! Bu arada, ikinci noktanızda bahsettiğiniz kullanışlı invasivecode.com bağlantısı için güncellenmiş bir bağlantı paylaşmak istedim: invasivecode.com/2009/09/… - Analizinizi ve en iyi uygulamalarınızı paylaştığınız, ayrıca örneklerle yedeklediğiniz için tekrar teşekkürler!
Joe D'Andrea

Katılıyorum. Soru iyi şekillenmişti ve cevap sadece harikaydı. Teknik bir cevap vermek yerine, DI kullanarak nasıl / neden uygulandığı ile ilgili bazı psikolojileri de içeriyordu. Teşekkür ederim! +1 yukarı.
Kevin Elliott

BookPickerController'ı bir istek listesi için kitap seçmek için veya birkaç olası kitap oluşturma nedeninden birini kullanmak isterseniz ne olur? Yine de CheckoutController arabirimi yaklaşımını (belki de BookSelectionController gibi bir adla değiştirildi) veya NSNotificationCenter'ı kullanabilir misiniz?
Les

Bu hala sıkı sıkıya bağlı. Merkezi bir yerden olay toplamak ve tüketmek daha gevşek olacaktır.
Neil McGuigan


15

Bu tür şeyler her zaman bir zevk meselesidir.

Bunu söyledikten sonra daima koordinasyonumu (# 2) model nesneler üzerinden yapmayı tercih ederim. Üst düzey görünüm denetleyicisi, ihtiyaç duyduğu modelleri yükler veya oluşturur ve her görünüm denetleyicisi, hangi model nesneleriyle çalışmak zorunda olduklarını söylemek için alt denetleyicilerindeki özellikleri ayarlar. Değişikliklerin çoğu hiyerarşiyi NSNotificationCenter kullanarak yedekler; bildirimleri tetiklemek genellikle modelin kendisinde yerleşiktir.

Örneğin, Hesaplar ve İşlemler içeren bir uygulamam olduğunu varsayalım. Ayrıca bir AccountListController, bir AccountController (bir hesap özeti "tüm işlemleri göster" düğmesi ile görüntüler), bir TransactionListController ve bir TransactionController var. AccountListController tüm hesapların bir listesini yükler ve görüntüler. Bir liste öğesine dokunduğunuzda, AccountController'ın .account özelliğini ayarlar ve AccountController'ı yığının üzerine iter. "Tüm işlemleri göster" düğmesine dokunduğunuzda, AccountController işlem listesini yükler, TransactionListController'ın .transactions özelliğine koyar ve TransactionListController'ı yığına iter vb.

Örneğin, TransactionController işlemi düzenlerse, işlem nesnesinde değişiklik yapar ve sonra 'save' yöntemini çağırır. 'save' bir TransactionChangedNotification gönderir. İşlem değiştiğinde kendini yenilemesi gereken diğer denetleyiciler bildirimi izler ve kendini günceller. TransactionListController muhtemelen; AccountController ve AccountListController ne yapmaya çalıştıklarına bağlı olarak değişebilir.

# 1 için, erken uygulamalarda, alt denetleyicide işleri ayarlayıp denetleyiciyi yığına itecek bir çeşit displayModel: withNavigationController: yöntemi vardı. Ama SDK ile daha rahat olduğum için, bundan uzaklaştım ve şimdi genellikle ebeveyni çocuğu ittiriyorum.

# 3 için bu örneği düşünün. Burada bir Transaction'ın iki özelliğini düzenlemek için AmountEditor ve TextEditor adlı iki denetleyici kullanıyoruz. Editörler, kullanıcı işlemi iptal etmeye karar verebileceğinden, düzenlenen işlemi gerçekten kaydetmemelidir. Bunun yerine, her ikisi de ebeveyn denetleyicilerini temsilci olarak alırlar ve üzerinde herhangi bir şey değiştirip değiştirmediklerini söyleyen bir yöntem çağırırlar.

@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

Ve şimdi TransactionController'dan birkaç yöntem:

- (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];
}

Dikkat edilmesi gereken şey, Editörlerin kendi denetleyicileri ile iletişim kurmak için kullanabileceği genel bir protokol tanımladığımızdır. Böylece, Editörleri uygulamanın başka bir bölümünde tekrar kullanabiliriz. (Belki de Hesaplarda notlar da olabilir.) Elbette, EditorDelegate protokolü birden fazla yöntem içerebilir; bu durumda tek gerekli olan budur.


1
Bunun olduğu gibi çalışması gerekiyor mu? Üye ile sorun yaşıyorum Editor.delegate. Benim içinde viewDidLoadyöntemle, ben alıyorum Property 'delegate' not found.... Baţka birţeyleri mahvettiđimden emin deđilim. Ya da bu kısalık için kısaltılmışsa.
Jeff

Bu artık eski sözleşmelerle eski bir tarzda yazılmış oldukça eski bir kod. Doğrudan projenize kopyalayıp yapıştırmam; Sadece kalıplardan öğrenmeye çalışırdım.
Brent Royal-Gordon

Anladım. Tam olarak bilmek istediğim şey bu. Bazı değişikliklerle çalıştım, ancak kelimesi kelimesine uymadığından biraz endişeliydim.
Jeff

0

Sorununu görüyorum ..

Olan şey, birisinin MVC mimarisi fikrini karıştırmasıdır.

MVC'nin üç bölümü vardır .. modeller, görüşler ve kontrolörler .. Belirtilen sorun, ikisini birleştirmek için iyi bir neden yok gibi görünüyor. görünümler ve kontrolörler ayrı mantık parçalarıdır.

yani ... birden fazla görüntü denetleyicisine sahip olmak istemezsiniz.

birden çok görünüme ve aralarında seçim yapan bir denetleyiciye sahip olmak istiyorsunuz. (birden fazla uygulamanız varsa, birden fazla denetleyiciniz de olabilir)

görüşler karar vermemelidir. Kontrolör (ler) bunu yapmalıdır. Böylece görevlerin ayrılması, mantık ve hayatınızı kolaylaştırma yolları.

Bu yüzden, görünümünüzün bunu yaptığından emin olun, verilerin güzel bir görünümünü ortaya koyar. denetleyicinizin verilerle ne yapacağına ve hangi görünümü kullanacağına karar vermesine izin verin.

(ve veriler hakkında konuştuğumuzda, modelden bahsediyoruz ... depolanmaya, erişilmeye, değiştirilmeye yönelik güzel bir standart yol ... Parsel edip unutabileceğimiz ayrı bir mantık parçası)


0

İki A ve B sınıfı olduğunu varsayalım.

A sınıfı örneği

Durum;

A sınıfı markaları ve B sınıfı örneği,

B bDoğası;

Ve B sınıfı mantığınızda, bir yerlerde A sınıfı bir yöntemi iletmeniz veya tetiklemeniz gerekir.

1) Yanlış yol

Anlayışı bencans'a aktarabilirsiniz. şimdi istenen yöntemin [aInstance methodname] çağrısını bInstance içinde istediğiniz yerden yerleştirin.

Bu sizin amacınıza hizmet ederdi, ancak serbest bırakma bir belleğin kilitlenmesine ve serbest kalmamasına neden olurdu.

Nasıl?

Anlayışı bencansa geçtiğinizde, ansansın alıkonma oranını 1 arttırdık. Bansı yeniden yerleştirirken, anıların bir ajansın nesnesi olması nedeniyle anstance hiçbir zaman 0 alıkoymaya getirilemez.

Ayrıca, Durumun sıkışması nedeniyle, Durumun belleği de sıkışacaktır (sızdırılmış). Bu yüzden, daha sonra zamanı geldiğinde ansans'ın kendisinin yerini değiştirdikten sonra bile, bansı serbest bırakılamadığından ve bacans bir ansans sınıfının değişkeni olduğu için belleği de engellenir.

2) Doğru yol

Anıtsallığı, anonsun temsilcisi olarak tanımlayarak, ansansta alıkonma değişikliği veya hafıza dolaşımı olmayacaktır.

bEnstance, olayda yatan delege yöntemlerini serbestçe çağırabilecektir. Btance of deallocation, tüm değişkenler kendi oluşturulmuş olacak ve ansans'ın deallocation üzerinde serbest bırakılacaktır, çünkü btance ofans intant dolaşma yok, temiz bir şekilde serbest bırakılacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.