Cách tốt nhất để giao tiếp giữa các bộ điều khiển xem là gì?


165

Là người mới đối với nhà phát triển khách quan, ca cao và iPhone nói chung, tôi có một mong muốn mạnh mẽ để tận dụng tối đa ngôn ngữ và khuôn khổ.

Một trong những tài nguyên tôi đang sử dụng là ghi chú lớp CS193P của Stanford mà họ đã để lại trên web. Nó bao gồm các ghi chú bài giảng, bài tập và mã mẫu, và vì khóa học được đưa ra bởi nhà phát triển của Apple, tôi chắc chắn coi đó là "từ miệng ngựa".

Trang web của lớp: http://www.stanford.edu/ class / cs193p / cgi-bin /
index.php

Bài giảng 08 có liên quan đến một nhiệm vụ để xây dựng một ứng dụng dựa trên UINavestionControll có nhiều UIViewControllers được đẩy lên ngăn xếp UINavestionControll. Đó là cách hoạt động của UINavestionControll. Điều đó hợp lý. Tuy nhiên, có một số cảnh báo nghiêm khắc trong slide về giao tiếp giữa các UIViewControllers của bạn.

Tôi sẽ trích dẫn từ trang trình bày nghiêm túc này:
http://cs193p.stanford.edu/doads/08-NavlationTabBarControllers.pdf

Trang 16/51:

Làm thế nào để không chia sẻ dữ liệu

  • Biến toàn cầu hoặc singletons
    • Điều này bao gồm đại biểu ứng dụng của bạn
  • Phụ thuộc trực tiếp làm cho mã của bạn ít sử dụng lại
    • Và khó khăn hơn để gỡ lỗi và kiểm tra

Đồng ý. Tôi thất vọng với điều đó. Đừng mù quáng ném tất cả các phương thức của bạn sẽ được sử dụng để liên lạc giữa trình điều khiển khung nhìn vào đại biểu ứng dụng của bạn và tham chiếu các phiên bản của trình điều khiển khung nhìn trong các phương thức ủy nhiệm ứng dụng. Hội chợ 'nuff.

Xa hơn một chút, chúng tôi nhận được slide này cho chúng tôi biết những gì chúng ta nên làm.

Trang 18/51:

Thực tiễn tốt nhất cho luồng dữ liệu

  • Chỉ ra chính xác những gì cần được truyền đạt
  • Xác định tham số đầu vào cho bộ điều khiển xem của bạn
  • Để liên lạc sao lưu hệ thống phân cấp, hãy sử dụng khớp nối lỏng lẻo
    • Xác định giao diện chung cho người quan sát (như ủy quyền)

Slide này sau đó được theo sau bởi những gì dường như là một slide giữ chỗ, trong đó giảng viên sau đó rõ ràng thể hiện các thực tiễn tốt nhất bằng cách sử dụng một ví dụ với UIImagePickerControll. Tôi muốn các video có sẵn! :

Ok, vậy ... tôi sợ objc-fu của tôi không mạnh lắm. Tôi cũng có một chút bối rối bởi dòng cuối cùng trong đoạn trích dẫn trên. Tôi đã chia sẻ công bằng về việc này và tôi thấy những gì dường như là một bài viết hay nói về các phương pháp khác nhau của các kỹ thuật Quan sát / Thông báo:
http://cocoawithlove.com/2008/06/five-approaches-to -lắng nghe-quan sát.html

Phương pháp số 5 thậm chí chỉ ra các đại biểu là một phương thức! Ngoại trừ .... các đối tượng chỉ có thể đặt một đại biểu tại một thời điểm. Vậy khi tôi có nhiều giao tiếp điều khiển khung nhìn, tôi phải làm gì?

Ok, đó là băng nhóm thành lập. Tôi biết tôi có thể dễ dàng thực hiện các phương thức giao tiếp của mình trong đại biểu ứng dụng bằng cách tham chiếu nhiều trường hợp trình điều khiển chế độ xem trong ứng dụng của tôi nhưng tôi muốn thực hiện đúng cách này.

Hãy giúp tôi "làm điều đúng đắn" bằng cách trả lời các câu hỏi sau:

  1. Khi tôi đang cố gắng đẩy một trình điều khiển khung nhìn mới trên ngăn xếp UINavestionControll, ai sẽ thực hiện thao tác đẩy này. lớp / tập tin trong mã của tôi là nơi có đúng không?
  2. Khi tôi muốn ảnh hưởng đến một số phần dữ liệu (giá trị của một iVar) trong một trong những UIViewControllers của tôi khi tôi ở một UIViewControll khác , cách "đúng" để làm điều này là gì?
  3. Cho rằng chúng ta chỉ có thể có một đại biểu được đặt tại một thời điểm trong một đối tượng, việc triển khai sẽ như thế nào khi giảng viên nói "Xác định giao diện chung cho người quan sát (như ủy nhiệm)" . Một ví dụ mã giả sẽ rất hữu ích ở đây nếu có thể.

Một số điều này được đề cập trong bài viết này từ Apple - developer.apple.com/l
James Moore

Chỉ là một nhận xét nhanh: Các video cho lớp Stanford CS193P hiện có sẵn thông qua iTunes U. Bản mới nhất (2012-13) có thể được xem tại itunes.apple.com/us/cference/coding-together-developing/ , và tôi mong đợi rằng các video và slide trong tương lai sẽ được công bố tại cs193p.stanford.edu
Thomas Watson

Câu trả lời:


224

Đây là những câu hỏi hay, và thật tuyệt khi thấy bạn đang thực hiện nghiên cứu này và có vẻ quan tâm đến việc học cách "làm đúng" thay vì chỉ hack nó cùng nhau.

Đầu tiên , tôi đồng ý với các câu trả lời trước tập trung vào tầm quan trọng của việc đưa dữ liệu vào các đối tượng mô hình khi thích hợp (theo mẫu thiết kế MVC). Thông thường bạn muốn tránh đưa thông tin trạng thái vào trong bộ điều khiển, trừ khi đó là dữ liệu "trình bày" nghiêm ngặt.

Thứ hai , xem trang 10 của bài thuyết trình Stanford để biết ví dụ về cách lập trình đẩy bộ điều khiển lên bộ điều khiển điều hướng. Để biết ví dụ về cách thực hiện "trực quan" này bằng Trình tạo giao diện, hãy xem hướng dẫn này .

Ngày thứ ba , và có lẽ là quan trọng nhất, lưu ý rằng "các thực tiễn tốt nhất" được đề cập trong bài thuyết trình của Stanford sẽ dễ hiểu hơn nhiều nếu bạn nghĩ về chúng trong bối cảnh của mẫu thiết kế "tiêm phụ thuộc". Tóm lại, điều này có nghĩa là bộ điều khiển của bạn không nên "tra cứu" các đối tượng cần thiết để thực hiện công việc của mình (ví dụ: tham chiếu một biến toàn cục). Thay vào đó, bạn nên luôn luôn "tiêm" các phụ thuộc đó vào bộ điều khiển (nghĩa là truyền vào các đối tượng cần thiết thông qua các phương thức).

Nếu bạn theo mô hình tiêm phụ thuộc, bộ điều khiển của bạn sẽ được mô-đun và có thể tái sử dụng. Và nếu bạn nghĩ về việc những người thuyết trình Stanford đến từ đâu (nghĩa là nhân viên của Apple, công việc của họ là xây dựng các lớp có thể dễ dàng sử dụng lại), khả năng sử dụng lại và mô đun hóa là những ưu tiên cao. Tất cả các thực tiễn tốt nhất mà họ đề cập để chia sẻ dữ liệu là một phần của tiêm phụ thuộc.

Đó là ý chính trong phản ứng của tôi. Tôi sẽ bao gồm một ví dụ về việc sử dụng mẫu tiêm phụ thuộc với bộ điều khiển bên dưới trong trường hợp hữu ích.

Ví dụ về Sử dụng Tiêm phụ thuộc với Trình điều khiển xem

Giả sử bạn đang xây dựng một màn hình trong đó một số sách được liệt kê. Người dùng có thể chọn những cuốn sách mình muốn mua và sau đó nhấn nút "thanh toán" để đến màn hình thanh toán.

Để xây dựng điều này, bạn có thể tạo một lớp BookPickerViewCont kiểm soát và hiển thị các đối tượng GUI / view. Nó sẽ lấy tất cả dữ liệu sách ở đâu? Giả sử nó phụ thuộc vào đối tượng BookWarehouse cho điều đó. Vì vậy, bây giờ bộ điều khiển của bạn về cơ bản là môi giới dữ liệu giữa một đối tượng mô hình (BookWarehouse) và các đối tượng GUI / view. Nói cách khác, BookPickerViewControll DEPENDS trên đối tượng BookWarehouse.

Đừng làm điều này:

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

Thay vào đó, các phụ thuộc nên được tiêm như thế này:

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

Khi những người của Apple đang nói về việc sử dụng mô hình ủy quyền để "truyền đạt lại hệ thống phân cấp", họ vẫn đang nói về việc tiêm phụ thuộc. Trong ví dụ này, BookPickerViewControll nên làm gì khi người dùng đã chọn sách của mình và sẵn sàng kiểm tra? Vâng, đó không thực sự là công việc của nó. Nó nên XÓA nó hoạt động với một số đối tượng khác, điều đó có nghĩa là nó PHỤ THUỘC trên một đối tượng khác. Vì vậy, chúng tôi có thể sửa đổi phương thức init BookPickerViewControll của mình như sau:

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

Kết quả cuối cùng của tất cả những điều này là bạn có thể đưa cho tôi lớp BookPickerViewControll (và các đối tượng GUI / view có liên quan) và tôi có thể dễ dàng sử dụng nó trong ứng dụng của riêng mình, giả sử BookWarehouse và CheckoutContoder là các giao diện chung (nghĩa là các giao thức) mà tôi có thể thực hiện :

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

Cuối cùng, không chỉ BookPickerControll của bạn có thể tái sử dụng mà còn dễ kiểm tra hơn.

-(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
Khi tôi thấy những câu hỏi (và câu trả lời) như thế này, được tạo ra với sự quan tâm như vậy, tôi không thể không mỉm cười. Danh tiếng xứng đáng cho người hỏi thông minh của chúng tôi và cho bạn !! Trong khi đó, tôi muốn chia sẻ một liên kết được cập nhật cho liên kết invasivecode.com tiện dụng mà bạn đã tham chiếu ở điểm thứ hai: invasivecode.com/2009/09/ Thẻ - Cảm ơn một lần nữa vì đã chia sẻ cái nhìn sâu sắc và thực tiễn tốt nhất của bạn, cộng với sao lưu nó bằng các ví dụ!
Joe D'Andrea

Tôi đồng ý. Câu hỏi đã được hình thành tốt, và câu trả lời chỉ đơn giản là tuyệt vời. Thay vì chỉ có một câu trả lời kỹ thuật, nó cũng bao gồm một số tâm lý đằng sau cách thức / lý do tại sao nó được thực hiện bằng DI. Cảm ơn bạn! +1 lên.
Kevin Elliott

Điều gì sẽ xảy ra nếu bạn cũng muốn sử dụng BookPickerControll để chọn một cuốn sách cho danh sách mong muốn hoặc một trong một số lý do có thể đặt cược. Bạn vẫn sẽ sử dụng cách tiếp cận giao diện CheckoutContoder (có thể được đổi tên thành một cái gì đó như BookSelectionControll) hoặc có thể sử dụng NSNotificationCenter?
Les

Điều này vẫn còn khá chặt chẽ kết hợp. Tăng và tiêu thụ các sự kiện từ một nơi tập trung sẽ lỏng lẻo hơn.
Neil McGuigan

1
Liên kết được tham chiếu ở điểm 2 dường như đã thay đổi một lần nữa - đây là liên kết hoạt động invasivecode.com/blog/archives/322
vikmalhotra

15

Loại điều này luôn luôn là một vấn đề của hương vị.

Phải nói rằng, tôi luôn thích phối hợp (# 2) thông qua các đối tượng mô hình. Bộ điều khiển khung nhìn mức cao nhất tải hoặc tạo các mô hình mà nó cần và mỗi bộ điều khiển khung nhìn đặt các thuộc tính trong các bộ điều khiển con của nó để cho chúng biết các đối tượng mô hình nào chúng cần làm việc với. Hầu hết các thay đổi được truyền đạt sao lưu phân cấp bằng cách sử dụng NSNotificationCenter; bắn các thông báo thường được tích hợp vào chính mô hình.

Ví dụ: giả sử tôi có một ứng dụng có Tài khoản và Giao dịch. Tôi cũng có một AccountListContoder, AccountContoder (hiển thị tóm tắt tài khoản với nút "hiển thị tất cả các giao dịch"), TransactionListContaptor và TransactionContoder. AccountListControll tải một danh sách tất cả các tài khoản và hiển thị chúng. Khi bạn nhấn vào một mục danh sách, nó sẽ đặt thuộc tính .account của AccountContoder của nó và đẩy AccountContoder lên ngăn xếp. Khi bạn chạm vào nút "hiển thị tất cả các giao dịch", AccountContoder sẽ tải danh sách giao dịch, đặt nó vào thuộc tính .transilities của TransactionListContoder và đẩy Trình điều khiển TransactionListControll lên ngăn xếp, v.v.

Nếu, giả sử, TransactionContoder chỉnh sửa giao dịch, nó thực hiện thay đổi trong đối tượng giao dịch và sau đó gọi phương thức 'lưu' của nó. 'lưu' sẽ gửi một Giao dịch thay đổi thông báo. Bất kỳ bộ điều khiển nào khác cần tự làm mới khi thay đổi giao dịch sẽ quan sát thông báo và tự cập nhật. Giao dịch liệt kê có lẽ sẽ; AccountControll và AccountListControll có thể, tùy thuộc vào những gì họ đang cố gắng làm.

Đối với # 1, trong các ứng dụng đầu tiên của tôi, tôi đã có một số loại displayModel: withNavlationControll: phương thức trong bộ điều khiển con sẽ thiết lập mọi thứ và đẩy bộ điều khiển lên ngăn xếp. Nhưng khi tôi trở nên thoải mái hơn với SDK, tôi đã tránh xa điều đó và bây giờ tôi thường có cha mẹ đẩy đứa trẻ.

Đối với # 3, hãy xem xét ví dụ này. Ở đây chúng tôi đang sử dụng hai bộ điều khiển, MoneyEditor và TextEditor, để chỉnh sửa hai thuộc tính của Giao dịch. Các biên tập viên thực sự không nên lưu giao dịch đang được chỉnh sửa, vì người dùng có thể quyết định từ bỏ giao dịch. Vì vậy, thay vào đó, cả hai đều lấy bộ điều khiển chính của mình làm đại biểu và gọi một phương thức trên đó để nói nếu họ đã thay đổi bất cứ điều gì.

@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

Và bây giờ là một vài phương thức từ TransactionContoder:

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

Điều cần chú ý là chúng tôi đã xác định một giao thức chung mà các Biên tập viên có thể sử dụng để giao tiếp với bộ điều khiển sở hữu của họ. Bằng cách đó, chúng ta có thể sử dụng lại các Biên tập viên trong một phần khác của ứng dụng. (Có lẽ Tài khoản cũng có thể có ghi chú.) Tất nhiên, giao thức EditorDelegate có thể chứa nhiều hơn một phương thức; trong trường hợp này đó là điều duy nhất cần thiết


1
Đây có phải là để làm việc như là? Tôi đang gặp rắc rối với Editor.delegatethành viên. Trong viewDidLoadphương pháp của tôi , tôi đang nhận được Property 'delegate' not found.... Tôi chỉ không chắc là tôi có làm hỏng chuyện gì khác không. Hoặc nếu điều này được rút ngắn cho ngắn gọn.
Jeff

Đây là mã khá cũ, được viết theo kiểu cũ hơn với các quy ước cũ hơn. Tôi sẽ không sao chép và dán nó trực tiếp vào dự án của bạn; Tôi chỉ cố gắng học hỏi từ các mẫu.
Brent Royal-Gordon

Gotcha. Đó chính xác là những gì tôi muốn biết. Tôi đã làm cho nó hoạt động với một số sửa đổi, nhưng tôi hơi lo ngại rằng nó không phù hợp với nguyên văn.
Jeff

0

Tôi thấy vấn đề của bạn ..

Điều gì đã xảy ra là ai đó đã nhầm lẫn ý tưởng về kiến ​​trúc MVC.

MVC có ba phần .. mô hình, khung nhìn và bộ điều khiển .. Vấn đề đã nêu dường như đã kết hợp hai trong số chúng không có lý do chính đáng. khung nhìn và bộ điều khiển là những mảnh logic riêng biệt.

vì vậy ... bạn không muốn có nhiều bộ điều khiển xem ..

bạn muốn có nhiều chế độ xem và bộ điều khiển chọn giữa chúng. (bạn cũng có thể có nhiều bộ điều khiển, nếu bạn có nhiều ứng dụng)

quan điểm KHÔNG nên đưa ra quyết định. Các bộ điều khiển nên làm điều đó. Do đó sự phân chia các nhiệm vụ, logic và cách làm cho cuộc sống của bạn dễ dàng hơn.

Vì vậy, hãy chắc chắn rằng quan điểm của bạn chỉ thực hiện điều đó, đưa ra một veiw tốt đẹp của dữ liệu. hãy để bộ điều khiển của bạn quyết định phải làm gì với dữ liệu và chế độ xem nào sẽ sử dụng.

(và khi chúng ta nói về dữ liệu, chúng ta đang nói về mô hình ... một cách tiêu chuẩn tốt đẹp để được lo lắng, truy cập, sửa đổi .. một đoạn logic riêng biệt khác mà chúng ta có thể bỏ qua và quên đi)


0

Giả sử có hai lớp A và B.

ví dụ của lớp A là

Một vấn đề;

lớp A tạo và thể hiện của lớp B, như

B bststance;

Và theo logic của bạn về lớp B, ở đâu đó bạn bắt buộc phải giao tiếp hoặc kích hoạt một phương thức của lớp A.

1) sai cách

Bạn có thể chuyển aInstance sang bInstance. bây giờ thực hiện cuộc gọi của phương thức mong muốn [aInstance methodname] từ vị trí mong muốn trong bInstance.

Điều này sẽ phục vụ mục đích của bạn, nhưng trong khi phát hành sẽ dẫn đến một bộ nhớ bị khóa và không được giải phóng.

Làm sao?

Khi bạn chuyển aInstance cho bInstance, chúng tôi đã tăng tỷ lệ giữ lại của aInstance lên 1. Khi giải quyết bInstance, chúng tôi sẽ bị chặn bộ nhớ vì aInstance không bao giờ có thể được đưa về 0 vì lý do bInstance là chính đối tượng đó.

Hơn nữa, vì aInstance bị kẹt, bộ nhớ của bInstance cũng sẽ bị kẹt (bị rò rỉ). Vì vậy, ngay cả sau khi tự giải quyết aInstance khi thời gian muộn hơn, bộ nhớ của nó cũng sẽ bị chặn vì bInstance không thể được giải phóng và bInstance là một biến lớp của aInstance.

2) Đúng cách

Bằng cách xác định aInstance là đại biểu của bInstance, sẽ không có thay đổi về số lượng hoặc vướng mắc bộ nhớ của aInstance.

bInstance sẽ có thể tự do gọi các phương thức đại biểu nằm trong aInstance. Trên giao dịch của bInstance, tất cả các biến sẽ do chính nó tạo ra và sẽ được phát hành Trên giao dịch của aInstance, vì không có sự vướng víu của aInstance trong bInstance, nó sẽ được phát hành sạch sẽ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.