objective-c - officiel - freeware qgis



Qual é a melhor maneira de se comunicar entre os controladores de visualização? (3)

Esse tipo de coisa é sempre uma questão de gosto.

Dito isto, eu sempre prefiro fazer minha coordenação (# 2) através de objetos de modelo. O controlador de visualização de nível superior carrega ou cria os modelos necessários e cada controlador de exibição define propriedades em seus controladores filhos para informar com quais objetos de modelo eles precisam trabalhar. A maioria das alterações é comunicada de volta à hierarquia usando o NSNotificationCenter; disparar as notificações geralmente é incorporado ao próprio modelo.

Por exemplo, suponha que eu tenha um aplicativo com contas e transações. Eu também tenho um AccountListController, um AccountController (que exibe um resumo de conta com um botão "show all transactions"), um TransactionListController e um TransactionController. AccountListController carrega uma lista de todas as contas e as exibe. Quando você toca em um item da lista, ele define a propriedade .account de seu AccountController e coloca o AccountController na pilha. Quando você toca no botão "mostrar todas as transações", o AccountController carrega a lista de transações, coloca-a na propriedade .transactions do TransactionListController e envia o TransactionListController para a pilha, e assim por diante.

Se, por exemplo, TransactionController edita a transação, ela faz a alteração em seu objeto de transação e, em seguida, chama seu método 'save'. 'save' envia uma TransactionChangedNotification. Qualquer outro controlador que precise se atualizar quando a transação for alterada observará a notificação e a própria atualização. TransactionListController presumivelmente faria; AccountController e AccountListController podem, dependendo do que eles estavam tentando fazer.

Para o número 1, nos meus primeiros aplicativos eu tinha algum tipo de método displayModel: withNavigationController: no controlador filho que configurava as coisas e colocava o controlador na pilha. Mas à medida que me sinto mais à vontade com o SDK, afastei-me disso e agora geralmente faço com que os pais pressionem o filho.

Para o nº 3, considere este exemplo. Aqui estamos usando dois controladores, AmountEditor e TextEditor, para editar duas propriedades de uma transação. Os editores não devem salvar a transação que está sendo editada, pois o usuário pode decidir abandonar a transação. Então, ao invés disso, ambos pegam seu controlador pai como um delegado e chamam um método dizendo se eles mudaram alguma coisa.

@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

E agora alguns métodos do 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];
}

O que devemos notar é que definimos um protocolo genérico que os Editores podem usar para se comunicar com seu controlador de propriedade. Ao fazer isso, podemos reutilizar os Editores em outra parte do aplicativo. (Talvez as contas também possam ter anotações.) É claro que o protocolo EditorDelegate pode conter mais de um método; Neste caso, é o único necessário.

https://ffff65535.com

Sendo novo no objetivo-c, cacau e iPhone dev em geral, eu tenho um forte desejo de tirar o máximo proveito da linguagem e dos frameworks.

Um dos recursos que estou usando é o da classe CS193P, da Stanford, que eles deixaram na web. Ele inclui notas de aula, tarefas e código de exemplo, e desde que o curso foi dado pela Apple dev, eu definitivamente considero que seja "da boca do cavalo".

Website da turma:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Aula 08 está relacionada a uma atribuição para criar um aplicativo baseado em UINavigationController que tenha vários UIViewControllers colocados na pilha UINavigationController. É assim que o UINavigationController funciona. Isso é lógico. No entanto, existem alguns avisos severos no slide sobre a comunicação entre seus UIViewControllers.

Vou citar este grave dos slides:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Página 16/51:

Como não compartilhar dados

  • Variáveis ​​globais ou singletons
    • Isso inclui seu delegado de aplicativo
  • Dependências diretas tornam seu código menos reutilizável
    • E mais difícil de depurar e testar

Está bem. Eu estou triste com isso. Não lance cegamente todos os seus métodos que serão usados ​​para comunicação entre o viewcontroller e o delegado do seu aplicativo e faça referência às instâncias do viewcontroller nos métodos delegados do aplicativo. Nuff justo.

Um pouco mais adiante, recebemos este slide nos dizendo o que devemos fazer.

Página 18/51:

Melhores práticas para fluxo de dados

  • Descubra exatamente o que precisa ser comunicado
  • Definir parâmetros de entrada para seu controlador de visualização
  • Para comunicar o backup da hierarquia, use o acoplamento solto
    • Definir uma interface genérica para observadores (como delegação)

Este slide é então seguido pelo que parece ser um slide de espaço reservado onde o palestrante aparentemente demonstra as melhores práticas usando um exemplo com o UIImagePickerController. Eu gostaria que os vídeos estivessem disponíveis! :(

Ok, então ... Eu tenho medo que meu objc-fu não seja tão forte. Eu também estou um pouco confuso com a linha final na citação acima. Eu tenho feito o meu quinhão de googling sobre isso e eu encontrei o que parece ser um artigo decente falando sobre os vários métodos de técnicas de observação / notificação:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

O método 5 indica até delegados como um método! Exceto .... objetos só podem definir um delegado de cada vez. Então, quando tenho várias comunicações do viewcontroller, o que devo fazer?

Ok, essa é a gangue criada. Eu sei que posso facilmente fazer meus métodos de comunicação no delegado app por referência a várias instâncias de viewcontroller no meu appdelegate, mas eu quero fazer esse tipo de coisa do jeito certo .

Por favor, ajude-me a "fazer a coisa certa", respondendo às seguintes perguntas:

  1. Quando eu estou tentando empurrar um novo viewcontroller na pilha UINavigationController, quem deve estar fazendo esse push. Qual classe / arquivo no meu código é o lugar correto?
  2. Quando eu quero afetar algum pedaço de dados (valor de um iVar) em um dos meus UIViewControllers quando estou em um UIViewController diferente , qual é a maneira "certa" de fazer isso?
  3. Damos que só podemos ter um delegado definido de cada vez em um objeto, como seria a implementação quando o palestrante dissesse "Definir uma interface genérica para observadores (como delegação)" . Um exemplo de pseudocódigo seria muito útil aqui, se possível.

Estas são boas perguntas, e é ótimo ver que você está fazendo esta pesquisa e parece preocupado em aprender como "fazer certo" em vez de apenas hackeá-las.

Primeiro , concordo com as respostas anteriores, que se concentram na importância de colocar dados em objetos de modelo quando apropriado (de acordo com o padrão de design do MVC). Normalmente, você quer evitar colocar informações de estado dentro de um controlador, a menos que sejam dados estritamente de "apresentação".

Segundo , veja a página 10 da apresentação de Stanford para um exemplo de como empurrar programaticamente um controlador para o controlador de navegação. Para um exemplo de como fazer isso "visualmente" usando o Interface Builder, dê uma olhada neste tutorial .

Terceiro , e talvez mais importante, observe que as "melhores práticas" mencionadas na apresentação de Stanford são muito mais fáceis de entender se você pensar nelas no contexto do padrão de design de "injeção de dependência". Em poucas palavras, isso significa que seu controlador não deve "procurar" os objetos necessários para realizar seu trabalho (por exemplo, referenciar uma variável global). Em vez disso, você deve sempre "injetar" essas dependências no controlador (isto é, passar os objetos necessários por métodos).

Se você seguir o padrão de injeção de dependência, seu controlador será modular e reutilizável. E se você pensar sobre de onde os apresentadores de Stanford estão vindo (ou seja, como os funcionários da Apple seu trabalho é construir classes que possam ser facilmente reutilizadas), reutilização e modularidade são prioridades altas. Todas as práticas recomendadas mencionadas para compartilhar dados fazem parte da injeção de dependência.

Essa é a essência da minha resposta. Vou incluir um exemplo de uso do padrão de injeção de dependência com um controlador abaixo, caso seja útil.

Exemplo de Uso de Injeção de Dependência com um Controlador de Visualização

Digamos que você esteja criando uma tela na qual vários livros estão listados. O usuário pode escolher os livros que deseja comprar e, em seguida, tocar no botão "checkout" para acessar a tela de checkout.

Para construir isso, você pode criar uma classe BookPickerViewController que controle e exiba os objetos GUI / view. Onde obterá todos os dados do livro? Vamos dizer que depende de um objeto BookWarehouse para isso. Portanto, agora seu controlador está basicamente intermediando dados entre um objeto de modelo (BookWarehouse) e os objetos GUI / view. Em outras palavras, BookPickerViewController DEPENDS no objeto BookWarehouse.

Não faça isso:

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

Em vez disso, as dependências devem ser injetadas assim:

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

Quando os caras da Apple estão falando sobre o uso do padrão de delegação para "comunicar de volta a hierarquia", eles ainda estão falando sobre injeção de dependência. Neste exemplo, o que o BookPickerViewController deve fazer depois que o usuário escolher seus livros e estiver pronto para fazer check-out? Bem, isso não é realmente o seu trabalho. Deve DELEGAR esse trabalho para algum outro objeto, o que significa que DEPENDE de outro objeto. Portanto, podemos modificar nosso método init BookPickerViewController da seguinte maneira:

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

O resultado líquido de tudo isso é que você pode me dar sua classe BookPickerViewController (e objetos GUI / view relacionados) e eu posso facilmente usá-la em meu próprio aplicativo, supondo que BookWarehouse e CheckoutController sejam interfaces genéricas (isto é, protocolos) que eu possa implementar :

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

Finalmente, o BookPickerController não é apenas reutilizável, mas também mais fácil de testar.

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

Suponha que haja duas classes A e B.

instância da classe A é

Ainstância;

classe A faz e instância da classe B, como

B Instância;

E na sua lógica da classe B, em algum lugar você é obrigado a se comunicar ou acionar um método da classe A.

1) caminho errado

Você poderia passar o ainstance para instâncias. agora coloque a chamada do método desejado [a method methodname] do local desejado em bInstance.

Isso serviria ao seu propósito, mas enquanto a liberação levaria a uma memória sendo bloqueada e não liberada.

Como?

Quando você passou do aInstance para Instância, aumentamos a retenção de uma Instância em 1. Quando desalocamos a Instância, teremos a memória bloqueada, porque a Instância em si não pode ser trazida para a Contabilidade, porque a própria Instância é um objeto de uma Instância.

Além disso, por causa de uma Instância que está sendo presa, a memória do bInstance também estará presa (vazada). Assim, mesmo depois de desalocar a própria Instância quando a hora chegar mais tarde, sua memória também será bloqueada porque ela não pode ser liberada e a instancia é uma variável de classe de Instância.

2) caminho certo

Ao definir uma Instância como o delegado de uma Instância, não haverá mudança de retenção ou entrelaçamento de memória de uma Instância.

bInstance será capaz de invocar livremente os métodos delegados que se encontram no aInstance. Na desalfandegamento da empresa, todas as variáveis ​​serão criadas por si e serão liberadas pela desalfandegamento da Instituição, uma vez que não há emaranhamento de uma Instância, sendo liberado de forma limpa.





key-value-observing