뷰 컨트롤러간에 통신하는 가장 좋은 방법은 무엇입니까?


165

objective-c, cocoa 및 iPhone dev에 익숙하지 않기 때문에 언어와 프레임 워크를 최대한 활용하고자합니다.

내가 사용하는 리소스 중 하나는 웹에 남겨둔 Stanford의 CS193P 클래스 노트입니다. 강의 노트, 과제 및 샘플 코드가 포함되어 있으며 Apple 개발자가 강의를 수강 한 이후로 "말의 입에서 나온 것"이라고 생각합니다.

수업 웹 사이트 :
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

강의 08은 여러 UIViewController를 UINavigationController 스택에 푸시 한 UINavigationController 기반 앱을 빌드하기위한 과제와 관련이 있습니다. 이것이 UINavigationController의 작동 방식입니다. 논리적입니다. 그러나 슬라이드에서 UIViewController 간의 통신에 대한 엄격한 경고가 있습니다.

나는이 심각한 슬라이드에서 인용 할 것입니다 :
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

16/51 페이지 :

데이터를 공유하지 않는 방법

  • 전역 변수 또는 싱글 톤
    • 여기에는 응용 프로그램 대리인 이 포함됩니다
  • 직접적인 의존성은 코드 재사용을 덜 만든다
    • 그리고 디버깅 및 테스트가 더 어렵습니다.

확인. 나는 그것으로 아래로입니다. 뷰 컨트롤러와 앱 대리자로 통신하는 데 사용될 모든 메서드를 맹목적으로 던지지 말고 앱 대리자 메서드에서 뷰 컨트롤러 인스턴스를 참조하십시오. 페어 넛

조금 더 나아가서, 우리가 해야 할 일을 알려주는이 슬라이드를 얻습니다 .

페이지 18/51 :

데이터 흐름을위한 모범 사례

  • 그림 밖으로 정확히 전달 될 필요가 무엇
  • 뷰 컨트롤러의 입력 매개 변수 정의
  • 계층 구조를 다시 통신 하려면 느슨한 결합을 사용하십시오.
    • 관찰자에 대한 일반 인터페이스 정의 (위임과 같은)

이 슬라이드 다음에는 강사가 UIImagePickerController와 함께 예제를 사용하여 모범 사례를 보여주는 자리 표시 자 슬라이드 인 것처럼 보입니다. 비디오를 사용할 수 있기를 바랍니다! :(

좋아, 그래서 ... 내 objc-fu가 그렇게 강하지 않다는 것을 두려워한다. 또한 위 인용문의 마지막 줄에 약간 혼란 스럽습니다. 나는 이것에 대해 인터넷 검색을 공정하게 수행했으며 다양한 관찰 / 알림 기술 방법에 대해 이야기하는 적절한 기사 인 것으로 나타났습니다 :
http://cocoawithlove.com/2008/06/five-approaches-to -listening-observing.html

방법 # 5는 델리게이트를 방법으로 나타냅니다! 제외 .... 객체는 한 번에 하나의 델리게이트 만 설정할 수 있습니다. 그래서 여러 개의 viewcontroller 통신이있을 때 어떻게해야합니까?

좋아, 그게 설정 조직이야. appdelegate의 여러 viewcontroller 인스턴스를 참조하여 앱 대리자에서 통신 방법을 쉽게 수행 할 수 있다는 것을 알고 있지만 올바른 방법 으로이 작업을 수행하고 싶습니다 .

다음 질문에 답하여 "옳은 일을하도록"도와주세요.

  1. UINavigationController 스택에서 새 viewcontroller를 푸시하려고 할 때 누가이 푸시를 수행해야합니까. 내 코드에서 어떤 클래스 / 파일이 올바른 장소입니까?
  2. 다른 UIViewController 에있을 때 UIViewController 중 하나의 일부 데이터 (iVar 값)에 영향을 주려면 "올바른"방법은 무엇입니까?
  3. 한 개체에 한 번에 하나의 델리게이트 세트 만 가질 수 있으며 강사가 "관찰자 (예 : 위임)에 대한 일반 인터페이스 정의" 라고 말하면 구현 방식은 어떻습니까 ? 의사 코드 예제는 가능하다면 여기에 대단히 도움이 될 것입니다.

-이 중 일부는 애플이 문서에서 해결 developer.apple.com/library/ios/#featuredarticles/...
제임스 무어

그냥 빨리 말 : 스탠포드 CS193P 클래스의 비디오는 현재 아이튠즈 U. 최근 (2012-13)를 통해 사용할 수에서 볼 수있다 itunes.apple.com/us/course/coding-together-developing/... 내가 기대 미래의 비디오와 슬라이드는 cs193p.stanford.edu
Thomas Watson

답변:


224

이것들은 좋은 질문이며, 당신이이 연구를하고 있다는 것을 알기에 훌륭합니다. 단지 그것을 해킹하는 대신 "올바른 방법"을 배우는 것에 관심이있는 것 같습니다.

먼저 , 적절한 경우 (MVC 디자인 패턴에 따라) 모델 객체에 데이터를 넣는 것의 중요성에 중점을 둔 이전 답변에 동의합니다. 일반적으로 컨트롤러는 상태 정보를 엄격히 "표시"하지 않는 한 상태 정보를 컨트롤러에 넣지 않도록합니다.

둘째 , 컨트롤러를 내비게이션 컨트롤러에 프로그래밍 방식으로 푸시하는 방법에 대한 예제는 Stanford 프레젠테이션 10 페이지를 참조하십시오. Interface Builder를 사용하여 "시각적으로"이 작업을 수행하는 방법에 대한 예제는 이 학습서를보십시오 .

제삼 , 그리고 아마도 가장 중요한 것은 스탠포드 프레젠테이션에서 언급 된 "모범 사례"는 "종속성 주입"디자인 패턴의 맥락에서 생각하면 이해하기 훨씬 쉽다는 것입니다. 간단히 말해서, 이것은 컨트롤러가 작업을 수행하는 데 필요한 객체를 "찾아서는"안된다는 것을 의미합니다 (예 : 전역 변수 참조). 대신, 항상 이러한 의존성을 컨트롤러에 "주입"해야합니다 (즉, 메소드를 통해 필요한 객체를 전달).

의존성 주입 패턴을 따르는 경우 컨트롤러는 모듈 식이며 재사용 가능합니다. 그리고 스탠포드 발표자가 어디에서 왔는지 생각한다면 (즉, Apple 직원으로서 쉽게 재사용 할 수있는 클래스를 만드는 것이 중요합니다) 재사용 성과 모듈성이 우선 순위가 높습니다. 데이터 공유에 대해 언급 한 모든 모범 사례는 종속성 주입의 일부입니다.

그것은 나의 반응의 요지입니다. 도움이 될 수 있도록 아래 컨트롤러와 함께 의존성 주입 패턴을 사용하는 예를 포함하겠습니다.

View Controller에서 종속성 주입을 사용하는 예

여러 권의 책이 나열된 화면을 작성한다고 가정 해 보겠습니다. 사용자는 구매하려는 책을 선택한 다음 "체크 아웃"버튼을 눌러 체크 아웃 화면으로 이동할 수 있습니다.

이를 구축하기 위해 GUI / view 객체를 제어하고 표시하는 BookPickerViewController 클래스를 만들 수 있습니다. 모든 도서 데이터를 어디서 얻을 수 있습니까? 그것을 위해 BookWarehouse 객체에 의존한다고 가정 해 봅시다. 이제 컨트롤러는 기본적으로 모델 객체 (BookWarehouse)와 GUI / view 객체간에 데이터를 중개합니다. 다시 말해 BookWarehouse 개체에 대한 BookPickerViewController DEPENDS입니다.

이 작업을 수행하지 마십시오 :

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

대신, 종속성은 다음과 같이 주입되어야합니다.

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

애플 팀은 위임 패턴을 사용하여 "계층 구조를 다시 통신"하는 것에 대해 여전히 의존성 주입에 대해 이야기하고 있습니다. 이 예제에서 BookPickerViewController는 사용자가 책을 고르고 체크 아웃 할 준비가되면 어떻게해야합니까? 글쎄, 그것은 실제로 그 일이 아닙니다. 다른 객체에 작동하는 것을 삭제해야합니다. 즉, 다른 객체에 의존합니다. 따라서 BookPickerViewController init 메소드를 다음과 같이 수정할 수 있습니다.

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

이 모든 것의 최종 결과는 BookPickerViewController 클래스 (및 관련 GUI /보기 객체)를 제공 할 수 있으며 BookWarehouse 및 CheckoutController가 구현할 수있는 일반적인 인터페이스 (예 : 프로토콜)라고 가정하면 내 응용 프로그램에서 쉽게 사용할 수 있다는 것입니다 :

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

마지막으로, BookPickerController를 재사용 할 수있을뿐만 아니라 테스트하기도 쉽습니다.

-(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
이러한주의를 기울여 만들어진 이와 같은 질문과 답변을 보면 웃을 수밖에 없습니다. 우리의 대담한 질문자와 당신에게 잘 받아 들일만한 명성! 한편, 두 번째 요점에서 참조한 편리한 invasivecode.com 링크에 대한 업데이트 된 링크를 공유하고 싶었습니다. invasivecode.com/2009/09/… — 통찰력과 모범 사례를 공유하고 예제와 함께 백업 해 주셔서 다시 한 번 감사드립니다!
Joe D' Andrea

나는 동의한다. 그 질문은 잘 정립되었으며 그 답은 환상적이었습니다. 기술적 인 답변이 아니라 DI를 사용하여 구현 된 방법 / 이유에 대한 심리학도 포함되었습니다. 감사합니다! +1 올립니다.
케빈 엘리엇

희망 목록으로 책을 고르기 위해 BookPickerController를 사용하려는 경우 또는 몇 가지 가능한 북 픽킹 이유 중 하나입니다. CheckoutController 인터페이스 접근 방식 (아마도 BookSelectionController와 같은 이름으로 변경)을 사용하거나 NSNotificationCenter를 사용 하시겠습니까?

이것은 여전히 ​​꽤 밀접하게 결합되어 있습니다. 중앙 장소에서 이벤트를 발생시키고 소비하는 것이 더 느슨합니다.
Neil McGuigan 2012 년

1
포인트 2에서 참조 된 링크가 다시 변경된 것 같습니다. 여기에 작업 링크가 있습니다. invasivecode.com/blog/archives/322
vikmalhotra

15

이런 종류의 것은 항상 맛의 문제입니다.

나는 항상 모델 객체를 통해 조정 (# 2)을 선호합니다. 최상위 뷰 컨트롤러는 필요한 모델을로드하거나 생성하며, 각 뷰 컨트롤러는 하위 컨트롤러에 속성을 설정하여 작업해야하는 모델 객체를 알려줍니다. NSNotificationCenter를 사용하여 대부분의 변경 내용이 계층 구조로 백업됩니다. 알림 실행은 일반적으로 모델 자체에 내장되어 있습니다.

예를 들어, Accounts and Transactions가있는 앱이 있다고 가정합니다. 또한 AccountListController, AccountController ( "모든 트랜잭션 표시"버튼으로 계정 요약을 표시 함), TransactionListController 및 TransactionController가 있습니다. AccountListController는 모든 계정 목록을로드하고 표시합니다. 목록 항목을 누르면 AccountController의 .account 속성이 설정되고 AccountController가 스택으로 푸시됩니다. "모든 트랜잭션 표시"버튼을 누르면 AccountController는 트랜잭션 목록을로드하고 TransactionListController의 .transactions 특성에 넣고 TransactionListController를 스택에 넣습니다.

예를 들어 TransactionController가 트랜잭션을 편집하면 트랜잭션 오브젝트를 변경 한 다음 'save'메소드를 호출합니다. 'save'는 TransactionChangedNotification을 보냅니다. 트랜잭션 변경시 알림을 확인하고 업데이트 할 때 자체 새로 고쳐야하는 다른 컨트롤러 TransactionListController는 아마도; 수행하려는 작업에 따라 AccountController 및 AccountListController가있을 수 있습니다.

# 1의 초기 앱에는 하위 컨트롤러에 일종의 displayModel : withNavigationController : 메서드가있어서 컨트롤러를 스택에 밀어 넣습니다. 그러나 SDK에 익숙해지면서 나는 그로부터 멀어졌으며 이제는 일반적으로 부모가 아이를 강요하도록합니다.

# 3의 경우이 예를 고려하십시오. 여기서는 AmountEditor와 TextEditor의 두 컨트롤러를 사용하여 Transaction의 두 속성을 편집합니다. 사용자는 트랜잭션을 포기하기로 결정할 수 있으므로 편집자는 편집중인 트랜잭션을 실제로 저장하지 않아야합니다. 대신 부모 컨트롤러를 대리자로 가져 와서 변경 사항이 있는지 알려주는 메소드를 호출합니다.

@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

이제 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];
}

주목해야 할 것은 편집자가 소유 컨트롤러와 통신하는 데 사용할 수있는 일반 프로토콜을 정의했다는 것입니다. 이렇게하면 응용 프로그램의 다른 부분에서 편집기를 재사용 할 수 있습니다. 물론 계정에도 메모가있을 수 있습니다. 물론 EditorDelegate 프로토콜에는 둘 이상의 방법이 포함될 수 있습니다. 이 경우에 필요한 유일한 것입니다.


1
이것은 그대로 작동합니까? Editor.delegate회원 과 문제가 있습니다. 내 viewDidLoad방법으로, 나는 얻는다 Property 'delegate' not found.... 다른 것을 망쳐 놓았는지 확실하지 않습니다. 또는 이것이 간결성을 위해 요약 된 경우.
Jeff

이것은 이전 규칙으로 오래된 스타일로 작성된 꽤 오래된 코드입니다. 나는 그것을 복사하여 프로젝트에 직접 붙여 넣지 않을 것입니다. 나는 단지 패턴에서 배우려고합니다.
브렌트 로얄-고든

알았어 바로 내가 알고 싶었던 것입니다. 나는 약간의 수정 작업을했지만, 그것이 일치하지 않는 것이 약간 걱정되었습니다.
Jeff

0

나는 당신의 문제를 참조하십시오 ..

일어난 일은 누군가가 MVC 아키텍처에 대한 생각을 혼동했다는 것입니다.

MVC에는 모델, 뷰 및 컨트롤러의 세 부분이 있습니다. 언급 된 문제는 아무런 이유없이 두 부분을 결합한 것으로 보입니다. 뷰와 컨트롤러는 별도의 로직입니다.

그래서 ... 여러 개의 뷰 컨트롤러를 원하지 않습니다 ..

여러 개의 뷰와 그 중에서 선택하는 컨트롤러를 원합니다. (응용 프로그램이 여러 개인 경우 여러 컨트롤러를 가질 수도 있음)

보기는 결정을 내려서는 안됩니다. 컨트롤러가 그렇게해야합니다. 따라서 작업 분리, 논리 및 삶을 편하게 만드는 방법.

그래서 .. 당신의 견해가 그렇게하는지 확인하고, 데이터에 대한 훌륭한 정보를 제시하십시오. 컨트롤러가 데이터와 관련된 작업과 사용할 뷰를 결정하도록합니다.

(그리고 데이터에 대해 이야기 할 때 모델에 대해 이야기하고 있습니다. 저장, 액세스, 수정되는 훌륭한 표준 방법입니다.


0

두 개의 클래스 A와 B가 있다고 가정하십시오.

클래스 A의 인스턴스는

인스턴스;

클래스 A는 클래스 B의 인스턴스와 인스턴스를

B 인스턴스;

그리고 클래스 B의 논리에서 어딘가에서 클래스 A의 메소드를 통신하거나 트리거해야합니다.

1) 잘못된 길

aInstance를 bInstance로 전달할 수 있습니다. 이제 bInstance의 원하는 위치에서 원하는 메소드 [aInstance methodname]을 호출합니다.

이것은 당신의 목적에 도움이되었지만 릴리스는 메모리가 잠기고 해제되지 않게 만들었습니다.

어떻게?

aInstance를 bInstance로 전달하면 aInstance의 보유 수가 1 씩 증가했습니다. bInstance를 할당 해제 할 때 bInstance 자체가 aInstance의 객체이기 때문에 bInstance 이유로 aInstance를 0으로 보유 할 수 없으므로 메모리가 차단됩니다.

또한 aInstance가 고착되어 있기 때문에 bInstance의 메모리도 고착됩니다 (누설). 따라서 나중에 인스턴스가 할당 될 때 aInstance 자체를 할당 해제 한 후에도 bInstance를 해제 할 수없고 bInstance가 aInstance의 클래스 변수이므로 메모리도 차단됩니다.

2) 올바른 방법

aInstance를 bInstance의 위임으로 정의하면 aInstance의 보유 횟수 변경 또는 메모리 얽힘이 없습니다.

bInstance는 aInstance에있는 델리게이트 메서드를 자유롭게 호출 할 수 있습니다. bInstance의 할당 해제시 모든 변수가 자체적으로 생성되어 해제됩니다. aInstance의 할당 해제시 bInstance에 aInstance가 얽혀 있지 않으므로 완전히 해제됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.