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 告诉您需要重写方法:
在子类中,您必须重写它的两个原始方法:
setObject:forKey:
removeObjectForKey:
您还必须覆盖 NSDictionary 的原始方法
类。
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 协议,但无法使其在密钥归档器的上下文中工作。键控存档器会生成一个空的 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) initWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt;
{
self = [super init];
if (self) {
self.dict = [[NSMutableDictionary alloc] initWithObjects:objects 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)encodeWithCoder:(NSCoder *)aCoder;
{
//[aCoder encodeObject:self.dict];
[aCoder encodeObject:self.dict forKey:@"dict"];
}
*/
/*
- (nullable instancetype)initWithCoder:(NSCoder *)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) objectForKey:(id)aKey;
{
return [self.dict objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator;
{
return [self.dict keyEnumerator];
}
- (void) setObject:(id)anObject forKey:(id<NSCopying>)aKey;
{
[self.dict setObject:anObject forKey:aKey];
if (self.delegate) {
[self.delegate dictionaryWasChanged:self];
}
}
- (void) removeObjectForKey:(id)aKey;
{
[self.dict removeObjectForKey:aKey];
if (self.delegate) {
[self.delegate dictionaryWasChanged:self];
}
}
@end
2015 年 10 月 9 日更新
为了澄清我所说的“突变变化”(回复下面的@quelish)的意思,这里有一个带有 NSMutableDictionary 的 KVO 示例。请注意,它的输出不反映了下面的测试 1。即,KVO 不指示对密钥的更改。本例改编自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)
}
}