需要一些有关 Cocoa MVC/KVO 模式的提示

发布于 2024-10-05 13:27:31 字数 799 浏览 8 评论 0原文

这是一个非常广泛/模糊的问题,但这里是。提前致歉。

我正在构建的应用程序(桌面应用程序)需要不同类型的输入来生成 QR 码(我只是构建它来学习一些 Obj-C/Cocoa)。用户可以在允许输入纯文本(单个文本字段)、VCard/MeCard 数据(多个文本字段)和其他内容的不同视图之间切换。无论输入什么,结果都是二维码。

为了保持事物的可控性,我想使用视图作为视图控制器,这样它们就可以处理自己的输入,并且可以简单地“发送”一个包含所有数据的通用“要编码的数据”对象到中央编码器。即,纯文本视图将使用其文本字段的文本创建数据对象,而 VCard/MeCard 视图将使用其所有字段来创建结构化 VCard/MeCard 数据。

我可以在代码中手动将所有这些东西绑定在一起,但我真的很想了解绑定/KVO 如何帮助我。唉,在阅读了苹果的开发人员文档以及我能找到的更简单的教程/示例之后,我仍然不确定如何将其应用到我的应用程序中。

例如:用户在 VCard 视图中编辑文本字段。 VCard 视图控制器会收到每次更新的通知,并“重新计算”数据对象。然后,中央编码器控制器被通知更新的数据对象,并对数据进行编码。

所有这一切的要点是输入视图可以完全独立地创建,并且可以包含各种输入字段。然后,它们处理自己的输入,并“返回”编码器可以使用的通用数据对象。在内部,视图观察它们的输入来更新数据对象,而在外部,编码器只需要观察数据对象。

问题是我不知道如何让这一切发生并保持分离。输入视图及其字段之间是否应该有一个对象控制器?视图和编码器之间是否应该有另一个?我需要什么,在哪里?如果有人有好教程的链接,请分享。

同样,我可以推出自己的通知系统和粘合代码,但我认为重点是避免这种情况。

This is a very wide-ranging/vague question, but here goes. Apologies in advance.

The app (desktop app) I'm building takes different kinds of input to generate a QR code (I'm just building it to learn some Obj-C/Cocoa). The user can switch between different views that allow input of plain text (single text field), VCard/MeCard data (multiple text fields), and other stuff. No matter the input, the result is a QR code.

To keep things contained, I'd like to use the views as view-controllers, so they handle they're own inputs, and can simply "send" a generic "data to encode" object containing all the data to a central encoder. I.e. the plain text view would make a data object with its textfield's text, while the VCard/MeCard view would use all of its fields to make structured VCard/MeCard data.

I can bind all of this stuff together manually in code, but I'd really like to learn how bindings/KVO could help me out. Alas, after reading Apple's developer docs, and the simpler tutorials/examples I could find, I'm still not sure how to apply it to my app.

For instance: The user edits the textfields in the VCard-view. The VCard view-controller is notified of each update and "recalculates" the data object. The central encoder controller is then notified of the updated data object, and encodes the data.

The point of all this, is that the input views can be created completely independently, and can contain all kinds of input fields. They then handle their own inputs, and "return" a generic data object, which the encoder can use. Internally, the views observe their inputs to update the data object, and externally the encoder needs only observe the data object.

Trouble is I have no idea how to make this all happen and keep it decoupled. Should there be an object controller between the input-view and its fields? Should there be another one between the view and the encoder? What do I need where? If anyone has a link to a good tutorial, please share.

Again, I can roll my own system of notifications and glue code, but I think the point is to avoid that.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

这绝对是一个模糊的问题,但从一个初学者到另一个初学者,我都能感受到你的痛苦:)

我下载并解压了每一个示例,并经常对它们进行 grep。我发现这是帮助我渡过难关的最有价值的事情。我绝对建议不要放弃这些例子。我破解了这个脚本来下载并解压它们。

就良好的 KVO 模式而言,我发现此处描述的技术非常有用。然而,它在 Objective-C 2.0 中不能按原样工作 。他也没有详细说明它的实际使用方式。这是我所做的工作:

KVODispatcher.h 像这样:

#import <Foundation/Foundation.h>

@interface KVODispatcher : NSObject {

    id owner;
}

@property (nonatomic, retain) id owner;

- (id) initWithOwner:(id)owner;

- (void)startObserving:(id)object keyPath:(NSString*)keyPath 
               options:(NSKeyValueObservingOptions)options 
              selector:(SEL)sel;

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context;
@end

KVODispatcher.m 如下:

#import "KVODispatcher.h"
#import <objc/runtime.h>

@implementation KVODispatcher

@synthesize owner;

- (id)initWithOwner:(id)theOwner 
{
    self = [super init];
    if (self != nil) {
        self.owner = theOwner;
    }
    return self;
}

- (void)startObserving:(id)object 
               keyPath:(NSString*)keyPath 
               options:(NSKeyValueObservingOptions)options 
              selector:(SEL)sel
{
    // here is the actual KVO registration
    [object addObserver:self forKeyPath:keyPath options:options context:sel];
}

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context
{
    // The event is delegated back to the owner
    // It is assumed the method identified by the selector takes
    // three parameters 'keyPath:object:change:'
    objc_msgSend(owner, (SEL)context, keyPath, object, change);

    // As noted, a variation of this technique could be 
    // to expand the data passed in to 'initWithOwner' and 
    // have that data passed to the selected method here.
}
@end

然后你可以注册来观察事件,如下

KVODispatcher* dispatcher = [[KVODispatcher alloc] initWithOwner:self];
[dispatcher startObserving:theObject 
                   keyPath:@"thePath" 
                   options:NSKeyValueChangeNewKey 
                   selector:@selector(doSomething:object:change:)];

所示 :对于执行上述操作的同一对象,您可以拥有如下方法:

- (void) doSomething:(NSString *)keyPath 
             object:(id)object 
             change:(NSDictionary *)change {

    // do your thing
}

您可以拥有任意数量的“doSomething”类型方法。只要他们使用相同的参数(keyPath:object:change:),它就会成功。每个对象有一个调度程序,希望接收有关任意数量对象更改的任意数量的通知。

我喜欢它的原因:

  1. 每个类只能有一个 observeValueForKeyPath,但您可能想要观察几件事。自然的下一个想法是“嘿也许我可以传递一个选择器”
  2. 哦,但是不可能通过 performSelector 传递多个参数,除非使用像 NSNotification 这样的包装对象。谁想要清理包装对象。
  3. 当超类也使用 KVO 时,重写observeValueForKeyPath 会使任何通用方法变得困难——您必须知道哪些通知要传递给超类以及要保留哪些通知。
  4. 无论如何,谁想在每个对象中重新实现相同的基于选择器的通用observeValueForKeyPath?最好只做一次并重复使用。

一个不错的变体可能是向 KVODispatcher 添加另一个字段,例如 id extraContext ,并在 objc_msgSend 调用中传递该 extraContext 对象。使用它来存储在观察到的数据发生变化时需要更新的 UI 对象可能很有用。甚至可能是一个 NSArray。

Definitely a vague question, but one beginner to another, I feel your pain :)

I downloaded and unpacked every single example and grep through them frequently. I've found that to be the most valuable thing to get me over the hump. I definitely recommend not giving up on the examples. I hacked up this script to download and unpack them all.

In terms of good KVO patterns, I found the technique described here to be very useful. It doesn't work as-is in Objective-C 2.0 however. Also he doesn't give much detail on how it's actually used. Here's what I've got working:

The KVODispatcher.h like this:

#import <Foundation/Foundation.h>

@interface KVODispatcher : NSObject {

    id owner;
}

@property (nonatomic, retain) id owner;

- (id) initWithOwner:(id)owner;

- (void)startObserving:(id)object keyPath:(NSString*)keyPath 
               options:(NSKeyValueObservingOptions)options 
              selector:(SEL)sel;

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context;
@end

And the KVODispatcher.m is as so:

#import "KVODispatcher.h"
#import <objc/runtime.h>

@implementation KVODispatcher

@synthesize owner;

- (id)initWithOwner:(id)theOwner 
{
    self = [super init];
    if (self != nil) {
        self.owner = theOwner;
    }
    return self;
}

- (void)startObserving:(id)object 
               keyPath:(NSString*)keyPath 
               options:(NSKeyValueObservingOptions)options 
              selector:(SEL)sel
{
    // here is the actual KVO registration
    [object addObserver:self forKeyPath:keyPath options:options context:sel];
}

- (void)observeValueForKeyPath:(NSString *)keyPath 
                      ofObject:(id)object 
                        change:(NSDictionary *)change 
                       context:(void *)context
{
    // The event is delegated back to the owner
    // It is assumed the method identified by the selector takes
    // three parameters 'keyPath:object:change:'
    objc_msgSend(owner, (SEL)context, keyPath, object, change);

    // As noted, a variation of this technique could be 
    // to expand the data passed in to 'initWithOwner' and 
    // have that data passed to the selected method here.
}
@end

Then you can register to observe events like so:

KVODispatcher* dispatcher = [[KVODispatcher alloc] initWithOwner:self];
[dispatcher startObserving:theObject 
                   keyPath:@"thePath" 
                   options:NSKeyValueChangeNewKey 
                   selector:@selector(doSomething:object:change:)];

And in the same object that executed the above, you can have a method like so:

- (void) doSomething:(NSString *)keyPath 
             object:(id)object 
             change:(NSDictionary *)change {

    // do your thing
}

You can have as many of these "doSomething" type methods as you like. Just as long as they use the same parameters (keyPath:object:change:) it will work out. With one dispatcher per object that wishes to receive any number of notifications about changes in any number of objects.

What I like about it:

  1. You can only have one observeValueForKeyPath per class, but you may want to observe several things. Natural next thought is "hey maybe I can pass a selector"
  2. Oh, but it isn't possible to pass multiple arguments via performSelector unless wrapper objects like NSNotification are used. Who wants to clean up wrapper objects.
  3. Overriding observeValueForKeyPath when a superclass also uses KVO makes any generic approaches hard -- you have to know which notifications to pass to the super class and which to keep.
  4. Who wants to re-implement the same generic selector-based observeValueForKeyPath in every object anyway? Better to just do it once and reuse it.

A nice variation might be to add another field like id additionalContext to KVODispatcher and have that additionalContext object passed in the objc_msgSend call. Could be useful to use it to stash a UI object that needs to get updated when the observed data changes. Even perhaps an NSArray.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文