观察 UITableViewController 的编辑属性

发布于 2024-09-25 04:11:34 字数 1420 浏览 8 评论 0原文

为什么我无法观察 UITableViewController 实例的 editing 属性?

我正在使用以下代码:

[self addObserver:self 
       forKeyPath:@"editing" 
          options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
          context:NULL];

并已实现该方法:

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

... 但是当该值更改时,永远不会调用 observeValueForKeyPath 方法。


根据 Apple 的确保 KVC 合规性部分:

对于属性或一对一关系的属性,这要求您的类:

  • 实现名为 --is 的方法,或具有实例变量 _
  • 如果属性是可变的,那么它还应该实现 -set:
  • 您的 -set: 方法的实现不应执行验证。
  • 如果验证适合该密钥,您的类应该实现 -validate:error:

editing 属性的文档指出,它的定义如下:

@property(nonatomic, getter=isEditing) BOOL editing

由于该属性不可变,因此它必须符合的唯一要点是第一个(即有一个 -例如,定义了方法)。通过查看属性的声明,您可以看到它确实符合这一点,并注意到定义了一个 isEditing 方法。因此,它应该符合键值观察。怎么不起作用呢?

Why can't I observe the editing property of an instance of UITableViewController?

I'm using the following code:

[self addObserver:self 
       forKeyPath:@"editing" 
          options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
          context:NULL];

And have implemented the method:

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

... but the observeValueForKeyPath method is never called when this value changes.


According to Apple's Ensuring KVC Compliance section:

For properties that are an attribute or a to-one relationship, this requires that your class:

  • Implement a method named -<key>, -is<Key>, or have an instance variable <key> or _<key>.
  • If the property is mutable, then it should also implement -set<Key>:.
  • Your implementation of the -set<Key>: method should not perform validation.
  • Your class should implement -validate<Key>:error: if validation is appropriate for the key.

The documentation for the editing property, states that it is defined as:

@property(nonatomic, getter=isEditing) BOOL editing

Since this property is not mutable, the only bullet point it must conform to is the first one (i.e. that there is an -is<Key> method defined, for example). You can see that it does conform to this by looking at the declaration of the property, and noticing that there is an isEditing method defined. Thus, it should be Key Value Observing compliant. How come it isn't working?

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

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

发布评论

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

评论(2

花心好男孩 2024-10-02 04:11:34

您混淆了键值编码合规性和键值观察合规性。该属性符合 KVC 标准,这意味着您可以使用 [myViewController valueForKey:@"editing"] 来访问它(如果您喜欢打字),但这并不意味着它符合 KVO 标准。

KVO-合规性是通过以下方式实现的 对象要么实现 KVC 兼容的 setter(要点 2 和 3),KVO 将自动包装该 setter,要么通过发送自身来手动发布 KVO 通知 will/didChangeValueForKey: 消息。

UIViewController 和 UITableViewController 没有公开实现 setEditing:;如果他们根本不实现它,那么 KVO 自动包装它就已经过时了。这就留下了手动通知。如果您没有收到该属性的任何 KVO 通知(并且您实际上点击了 addObserver:forKeyPath:options:context: 消息),则表明这些类都没有私下实现 setEditing: 也不手动发布 KVO 通知。

因此,该属性是不可观察的。

如果设置 editing 属性的唯一方法是向控制器发送 setEditing:animated: 消息,那么您可以覆盖 setEditing:animated: > 并从您的实现中自行发送 KVO 通知,然后该属性将是可观察的。

You're confusing Key-Value Coding compliance with Key-Value Observing compliance. The property is KVC-compliant, which means you can use [myViewController valueForKey:@"editing"] to access it (if you like typing), but this does not mean it is KVO-compliant.

KVO-compliance is achieved by the object either implementing a KVC-compliant setter (bullet points 2 and 3), which KVO will wrap automatically, or manually posting KVO notifications by sending itself will/didChangeValueForKey: messages.

UIViewController and UITableViewController do not publicly implement setEditing:; if they don't implement it at all, then KVO wrapping it automatically is out. That leaves manual notifications. If you're not getting any KVO notifications for the property (and you are actually hitting that addObserver:forKeyPath:options:context: message), that suggests that those classes neither privately implement setEditing: nor manually post KVO notifications.

Therefore, the property is not observable.

If the only way anything ever sets the editing property is by sending the controller a setEditing:animated: message, then you can override setEditing:animated: and send the KVO notifications yourself from your implementation, and then the property will be observable.

少钕鈤記 2024-10-02 04:11:34

这有点卡顿,但您可以通过观察 editButtonItemtitle 来解决这个问题。

[self.viewControllerToObserve addObserver:self forKeyPath:@"editButtonItem.title" options:0 context:kMyViewControllerKVOContext];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == kMyViewControllerKVOContext) {
        // editing changed
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

仅供参考,这是我声明上下文的方式(在 @implementation 上方) :

static void * const kMyViewControllerKVOContext = (void *)&kMyViewControllerKVOContext;

使用 Hopper ,我们可以看到在 UIViewControllersetEditing 期间,它创建了一个新的 editButtonItem,其标题取决于要更改的编辑内容。

/* @class UIViewController */
-(void)setEditing:(bool)arg2 animated:(bool)arg3 {
    rdx = arg2;
    rdi = self;
    rax = *ivar_offset(_viewControllerFlags);
    rcx = *(rdi + rax);
    if (((rcx & 0x4) >> 0x2 ^ rdx) == 0x1) {
            stack[-8] = rbp;
            stack[-16] = r15;
            stack[-24] = r14;
            stack[-32] = r13;
            stack[-40] = r12;
            stack[-48] = rbx;
            rsp = rsp - 0x38;
            r12 = rdi;
            *(rdi + rax) = (rcx & 0xfffffffffffffffb) + (rdx & 0xff) * 0x4;
            r15 = [UIBarButtonItem alloc];
            r14 = [__UIKitBundle() retain];
            if ((rdx & 0xff) != 0x0) {
                    rax = [r14 localizedStringForKey:@"Done" value:rcx table:@"Localizable"];
                    rax = [rax retain];
                    r13 = rax;
                    rcx = 0x2;
                    rdi = r15;
                    rdx = rax;
            }
            else {
                    rax = [r14 localizedStringForKey:@"Edit" value:rcx table:@"Localizable"];
                    rax = [rax retain];
                    r13 = rax;
                    rdi = r15;
                    rdx = rax;
                    rcx = 0x0;
            }
            rbx = [rdi initWithTitle:rdx style:rcx target:0x0 action:0x0];
            [r13 release];
            [r14 release];
            [r12->_editButtonItem _setItemVariation:rbx];
            [rbx release];
    }
    return;
}

对于那些有兴趣的人来说,还有更多:

/* @class UIBarButtonItem */
-(void)_setItemVariation:(void *)arg2 {
    rdx = arg2;
    rdi = self;
    rax = *ivar_offset(_barButtonItemFlags);
    if ((*(int8_t *)(rdi + rax) & 0x10) == 0x0) {
            rax = [rdx retain];
            r15 = rax;
            rax = [rax title];
            rax = [rax retain];
            [rdi setTitle:rax];
            [rax release];
            rbx = [r15 style];
            [r15 release];
            [rdi setStyle:rbx];
    }
    return;
}

It's a bit janky but you can work around this by observing the editButtonItem's title.

[self.viewControllerToObserve addObserver:self forKeyPath:@"editButtonItem.title" options:0 context:kMyViewControllerKVOContext];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == kMyViewControllerKVOContext) {
        // editing changed
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

FYI this is how I declare my context (above @implementation):

static void * const kMyViewControllerKVOContext = (void *)&kMyViewControllerKVOContext;

Using Hopper, we can see during UIViewController's setEditing it creates a new editButtonItem with a title dependent on what editing is being changed to.

/* @class UIViewController */
-(void)setEditing:(bool)arg2 animated:(bool)arg3 {
    rdx = arg2;
    rdi = self;
    rax = *ivar_offset(_viewControllerFlags);
    rcx = *(rdi + rax);
    if (((rcx & 0x4) >> 0x2 ^ rdx) == 0x1) {
            stack[-8] = rbp;
            stack[-16] = r15;
            stack[-24] = r14;
            stack[-32] = r13;
            stack[-40] = r12;
            stack[-48] = rbx;
            rsp = rsp - 0x38;
            r12 = rdi;
            *(rdi + rax) = (rcx & 0xfffffffffffffffb) + (rdx & 0xff) * 0x4;
            r15 = [UIBarButtonItem alloc];
            r14 = [__UIKitBundle() retain];
            if ((rdx & 0xff) != 0x0) {
                    rax = [r14 localizedStringForKey:@"Done" value:rcx table:@"Localizable"];
                    rax = [rax retain];
                    r13 = rax;
                    rcx = 0x2;
                    rdi = r15;
                    rdx = rax;
            }
            else {
                    rax = [r14 localizedStringForKey:@"Edit" value:rcx table:@"Localizable"];
                    rax = [rax retain];
                    r13 = rax;
                    rdi = r15;
                    rdx = rax;
                    rcx = 0x0;
            }
            rbx = [rdi initWithTitle:rdx style:rcx target:0x0 action:0x0];
            [r13 release];
            [r14 release];
            [r12->_editButtonItem _setItemVariation:rbx];
            [rbx release];
    }
    return;
}

Little more for those interested:

/* @class UIBarButtonItem */
-(void)_setItemVariation:(void *)arg2 {
    rdx = arg2;
    rdi = self;
    rax = *ivar_offset(_barButtonItemFlags);
    if ((*(int8_t *)(rdi + rax) & 0x10) == 0x0) {
            rax = [rdx retain];
            r15 = rax;
            rax = [rax title];
            rax = [rax retain];
            [rdi setTitle:rax];
            [rax release];
            rbx = [r15 style];
            [r15 release];
            [rdi setStyle:rbx];
    }
    return;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文