OGeek|极客世界-中国程序员成长平台

标题: ios - 子类化 NSMutableDictionary 有哪些陷阱和困难? [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-13 11:13
标题: ios - 子类化 NSMutableDictionary 有哪些陷阱和困难?

苹果说

There should typically be little need to subclass NSMutableDictionary. If you do need to customize behavior, it is often better to consider composition rather than subclassing.

(见 https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSMutableDictionary_Class/)

他们可能应该让这件事变得更强大,并说后果自负

但是,在某些情况下,子类化 NSMutableDictionary 可能很重要。就我而言,从符号上讲,它确实与我的代码相关。有很多障碍需要克服。关于这个还有其他的 web 和 SO 条目,但是我在旅途中遇到了一些看似新的问题,所以想写下来以备不时之需并帮助他人。所以,我会发布我的答案。随意贡献您自己的其他发现。



Best Answer-推荐答案


1) 没有代理对象。一开始,出于某种原因,Apple 似乎使 NSMutableDictionary 在一些不寻常的方面不同于 NSMutableSet。我对 NSMutableDictionary 子类化的潜在需求实际上源于需要了解对 NSMutableDictionary 实例的突变更改。例如,NSMutableSets 让这变得更容易一些。 NSMutableSets 让您可以访问“代理”对象:mutableSetValueForKey。这为您提供了一种了解集合内容何时发生变化的机制。见 https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/了解一些细节。您希望看到的是 mutableDictValueForKey 之类的东西,但它似乎不存在。

2) 在您的子类方法中实现 init! Apple 告诉您需要重写方法:

In a subclass, you must override both of its primitive methods:

setObject:forKey:

removeObjectForKey:

You must also override the primitive methods of the NSDictionary class.

NSDictionary 原始方法是:

initWithObjects:forKeys:count:

@property count

objectForKey:

keyEnumerator:

但是,您还必须重写 init 方法!

3) 在 Swift 中执行此操作还行不通! 至少在我尝试此操作的日期(大约 10/8/15 和 Xcode 7)之前,您必须让您的NSMutableDictionary 是 Objective-C 中的子类,而不是 Swift。见 Cannot override initializer of NSDictionary in Swift

4) NSCoding 不适用于 NSMutableDictionary 子类! 在我的 NSMutableDictionary 子类中,我尝试实现 NSCoding 协议(protocol),但无法使其在上下文中工作 key 归档器。键控存档器会生成一个空的 NSMutableDictionary(解码时),而不是我自己的子类,我不知道为什么。一些特殊的 NSMutableDictionary 魔法?

5) 在 Swift 中的下标可能不会删除它。 我尝试只为 Swift 实现下标方法(参见 https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html),但从符号上看,这还有很多不足之处。我真的想要一个与 NSDictionary/NSMutableDictionary 完全互操作的类型,这似乎需要一个子类。

6) 不要只实现方法;您需要自己的数据!如果您只是尝试覆盖上述方法并调用“super”,您的代码将无法正常工作。您需要使用“组合”在内部实现 NSMutableDictionary 属性。或者您想要实现字典的任何其他机制。再一次,一些类集群魔法正在发生。在下面的 .m 文件中查看我的 dict 属性。

以下是我迄今为止的 Objective-C 代码:

//
//  SMMutableDictionary.h
//  Dictionary
//
//  Created by Christopher Prince on 10/6/15.
//  Copyright © 2015 Spastic Muffin, LLC. All rights reserved.
//

/* I subclassed NSMutableDictionary because:
    1) because I needed a way to know when a key was set or removed. With other mutable objects you can use proxy objects (e.g., see https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/), but a proxy object doesn't seem to be provided by Apple for NSMutableDictionary's.
    2) for notational convenience in some other code that I was writing.
*/

// QUESTION: Can I set up an observer to detect any changes to the value of the key's within the dictionary? We'd have to remove this KVO observer if the object was removed. Presumably, with this interface, the way that the object would be removed would be (a) setting with nil, and (b) deallocation of this SMMutableDictionary itself.

#import <Foundation/Foundation.h>

@class SMMutableDictionary;

@protocol SMMutableDictionaryDelegate <NSObject>

@required

// Reports on the assignment to a keyed value for this dictionary and the removal of a key: setObject:forKey: and removeObjectForKey:
- (void) dictionaryWasChanged: (SMMutableDictionary * _Nonnull) dict;

@end

@interface SMMutableDictionary : NSMutableDictionary

// For some reason (more of the ugliness associated with having an NSMutableDictionary subclass), when you unarchive a keyed archive of an SMMutableDictionary, it doesn't give you back the SMMutableDictionary, it gives you an NSMutableDictionary. So, this method is for your convenience. AND, almost even better, when you use a keyed archiver to archive, it uses our encoder method, but doesn't actually generate an archive containing our dictionary!! SO, don't use keyed archiver methods directly, use the following two methods:
- (NSData * _Nullable) archive;
+ (instancetype _Nullable) unarchiveFromData: (NSData * _Nonnull) keyedArchiverData;

// Optional delegate
@property (nonatomic, weak, nullable) id<SMMutableDictionaryDelegate> delegate;

@end

这是 .m 文件:

//
//  SMMutableDictionary.m
//  Dictionary
//
//  Created by Christopher Prince on 10/6/15.
//  Copyright © 2015 Spastic Muffin, LLC. All rights reserved.
//

// I wanted to make this a Swift NSMutableDictionary subclass, but run into issues...
// See https://stackoverflow.com/questions/28636598/cannot-override-initializer-of-nsdictionary-in-swift
// http://www.cocoawithlove.com/2008/12/ordereddictionary-subclassing-cocoa.html
// See also https://stackoverflow.com/questions/10799444/nsdictionary-method-only-defined-for-abstract-class-my-app-crashed
// I tried only implementing the subscript method for Swift (see https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html), but notationally this left much to be desired. I really wanted a type that was fully interoperable with NSDictionary/NSMutableDictionary, which seems to require a subclass.

// See also http://www.smackie.org/notes/2007/07/11/subclassing-nsmutabledictionary/

#import "SMMutableDictionary.h"

@interface SMMutableDictionary()
@property (nonatomic, strong) NSMutableDictionary *dict;
@end

// See this for methods you have to implement to subclass: https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Classes/NSMutableDictionary_Class/index.html
// HOWEVER, while they didn't say you have to subclass the init method, it did't work for me without doing that. i.e., I needed to have [1] below.

@implementation SMMutableDictionary

- (instancetype) initWithObjectsconst id  _Nonnull __unsafe_unretained *)objects forKeysconst id<NSCopying>  _Nonnull __unsafe_unretained *)keys countNSUInteger)cnt;
{
    self = [super init];
    if (self) {
        self.dict = [[NSMutableDictionary alloc] initWithObjectsbjects forKeys:keys count:cnt];
    }
    return self;
}

// [1].
- (instancetype) init;
{
    self = [super init];
    if (self) {
        self.dict = [NSMutableDictionary new];
    }
    return self;
}

// Both of these are useless. See the keyed archiver/unarchiver methods on the .h interface.
/*
- (void)encodeWithCoderNSCoder *)aCoder;
{
    //[aCoder encodeObject:self.dict];
    [aCoder encodeObject:self.dict forKey"dict"];
}
 */

/*
- (nullable instancetype)initWithCoderNSCoder *)aDecoder;
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        //self.dict = [aDecoder decodeObject];
        self.dict = [aDecoder decodeObjectForKey"dict"];
    }
    return self;
}
*/

- (NSData * _Nullable) archive;
{
    return [NSKeyedArchiver archivedDataWithRootObject:self.dict];
}

+ (instancetype _Nullable) unarchiveFromData: (NSData * _Nonnull) keyedArchiverData;
{
    NSMutableDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:keyedArchiverData];
    if (nil == dict) return nil;

    return [[SMMutableDictionary alloc] initWithDictionary:dict];
}

- (NSUInteger) count;
{
    return self.dict.count;
}

- (id) objectForKeyid)aKey;
{
    return [self.dict objectForKey:aKey];
}

- (NSEnumerator *)keyEnumerator;
{
    return [self.dict keyEnumerator];
}

- (void) setObjectid)anObject forKeyid<NSCopying>)aKey;
{
    [self.dict setObject:anObject forKey:aKey];
    if (self.delegate) {
        [self.delegate dictionaryWasChanged:self];
    }
}

- (void) removeObjectForKeyid)aKey;
{
    [self.dict removeObjectForKey:aKey];
    if (self.delegate) {
        [self.delegate dictionaryWasChanged:self];
    }
}

@end

2015 年 10 月 9 日更新

为了澄清我所说的“突变更改”(回复下面的@quelish)的意思,这里有一个带有 NSMutableDictionary 的 KVO 示例。请注意,它的输出反射(reflect)了下面的测试 1。即,KVO 不指示对 key 的更改。这个例子改编自https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-XID_5

如果您确实知道字典的所有键,则可以使用 KVO。见 Observing NSMutableDictionary changes

//
//  ViewController.swift
//  Dictionary2
//
//  Created by Christopher Prince on 10/9/15.
//  Copyright © 2015 Spastic Muffin, LLC. All rights reserved.
//

import UIKit

private var myContext = 0

class ViewController: UIViewController {
    var obj = MyObserver()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        print("Test 1")
        obj.objectToObserve.myDict["key1"] = "value1"

        print("Test 2")
        obj.objectToObserve.myDict = NSMutableDictionary()
    }
}

class MyObjectToObserve: NSObject {
    dynamic var myDict = NSMutableDictionary()
    override var description : String {
        return "\(myDict)"
    }
}

class MyObserver: NSObject {
    var objectToObserve = MyObjectToObserve()

    override init() {
        super.init()
        objectToObserve.addObserver(self, forKeyPath: "myDict", options: NSKeyValueObservingOptions(rawValue: 0), context: &myContext)
    }

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if context == &myContext {
            //let newValue = change?[NSKeyValueChangeNewKey]
            print("change: \(change)")
            print("object: \(object)")
        } else {
            super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
        }
    }

    deinit {
        objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
    }
}

关于ios - 子类化 NSMutableDictionary 有哪些陷阱和困难?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33028236/






欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://sqlite.in/) Powered by Discuz! X3.4