Ios 当涉及到子类NSMutableDictionary时,有哪些陷阱和困难?

Ios 当涉及到子类NSMutableDictionary时,有哪些陷阱和困难?,ios,subclass,nsmutabledictionary,Ios,Subclass,Nsmutabledictionary,苹果说 通常不需要将NSMutableDictionary子类化。 如果您确实需要定制行为,通常最好考虑一下。 组合而不是子类化 (见附件) 他们可能会让这一点变得更有力,并说在你自己承担风险的情况下追求这一点 但是,在某些情况下,将NSMutableDictionary子类化可能很重要。在我的例子中,值得注意的是,它确实与我的代码相关。有许多障碍需要克服。这上面还有其他的网页等条目,但我在这段旅程中遇到了一些似乎是新的问题,所以我想写下这篇文章作为我的记忆,并帮助其他人。所以,我会发布我的答案

苹果说

通常不需要将NSMutableDictionary子类化。 如果您确实需要定制行为,通常最好考虑一下。 组合而不是子类化

(见附件)

他们可能会让这一点变得更有力,并说在你自己承担风险的情况下追求这一点


但是,在某些情况下,将NSMutableDictionary子类化可能很重要。在我的例子中,值得注意的是,它确实与我的代码相关。有许多障碍需要克服。这上面还有其他的网页等条目,但我在这段旅程中遇到了一些似乎是新的问题,所以我想写下这篇文章作为我的记忆,并帮助其他人。所以,我会发布我的答案。请随意贡献您自己的其他发现。

1)没有代理对象。一开始,出于某种原因,苹果似乎以一些不同寻常的方式使
NSMutableDictionary
NSMutableSet
有所不同。我对子类化
NSMutableDictionary
的潜在需求实际上源于需要了解对
NSMutableDictionary
实例的变化<例如,code>nsmutableset,使这变得更容易
nsmutableset
允许您访问“代理”对象:
mutableSetValueForKey
。这为您提供了一种了解集合内容何时发生变化的机制。有关详细信息,请参阅。您希望看到的是类似于mutableDictValueForKey的东西,但这似乎不存在

2) 在子类方法中实现initApple告诉您需要覆盖方法:

setObject:forKey:

removeObjectForKey:
在子类中,必须重写其两个基元方法:

setObject:forKey:

removeObjectForKey:
还必须重写NSDictionary的基元方法 班级

NSDictionary
基本方法是:

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

3) 在Swift中执行此操作还不起作用至少在我尝试这个的时候(大约2015年10月8日,Xcode 7),您必须在Objective-C中创建
NSMutableDictionary
子类,而不是Swift。看

4) NSCoding不适用于NSMutableDictionary子类在我的
NSMutableDictionary
子类中,我尝试实现NSCoding协议,但在键控归档程序的上下文中无法实现。键控的archiver将生成一个空的NSMutableDictionary(解码时),而不是我自己的子类,我不知道为什么。一些特殊的
NSMutableDictionary
magic

5) Swift中的下标可能无法解决此问题。我只尝试为Swift实现下标方法(请参见),但值得注意的是,这留下了许多需要改进的地方。我真的想要一个与NSDictionary/NSMutableDictionary完全互操作的类型,它似乎需要一个子类

6) 不要只实施这些方法;你需要你自己的数据如果您只是尝试覆盖上述方法,并调用“super”,那么您的代码将无法工作。您需要使用“组合”在内部实现NSMutableDictionary属性。或者任何其他实现字典的机制。再一次,一些类集群魔术正在进行。请参阅下面.m文件中的my
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
//
//SMMutableDictionary.h
//字典
//
//克里斯托弗·普林斯于2015年6月10日创作。
//版权所有©2015 Spastic Muffin,LLC。保留所有权利。
//
/*我将NSMutableDictionary子类化,因为:
1) 因为我需要一种方法来知道何时设置或移除了一把钥匙。对于其他可变对象,您可以使用代理对象(例如,请参见https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/),但苹果似乎没有为NSMutableDictionary提供代理对象。
2) 为了便于在我正在编写的其他代码中使用符号。
*/
//问题:我可以设置一个观察者来检测字典中键的值的任何变化吗?如果对象被移除,我们必须移除这个KVO观察器。据推测,使用此接口,删除对象的方式将是(a)设置为nil,以及(b)取消分配此SMMutableDictionary本身。
#进口
@类SMMutableDictionary;
@协议SMmutableDictionaryLegate
@必需的
//报告对此字典的键控值的赋值以及键的删除:setObject:forKey:和removeObjectForKey:
-(void)dictionaryWasChanged:(SMMutableDictionary*_Nonnull)dict;
@结束
@接口SMMutableDictionary:NSMutableDictionary
//出于某种原因(更多与NSMutableDictionary子类相关的丑陋之处),当您取消SMMutableDictionary的键控存档时,它不会返回SMMutableDictionary,而是返回NSMutableDictionary。所以,这种方法是为了您的方便。而且,几乎更好的是,当您使用键控归档程序进行归档时,它使用了我们的编码器方法,但实际上并不生成包含字典的归档!!因此,不要直接使用键控archiver方法,请使用以下两种方法:
-(NSData*_可为空)存档;
+(instancetype _Nullable)unarchiveFromData:(NSData*_Nonnull)键控ArchiverData;
//可选代表
@属性(非原子、弱、可空)id委托;
@结束
以下是.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
//
//SMMutableDictionary.m
//字典
//
//克里斯托弗·普林斯于2015年6月10日创作。
//版权所有©2015 Spastic Muffin,LLC。保留所有权利。
//
//我想让它成为一个Swift NSMutableDictionary子类,但遇到了一些问题。。。
//看https://stackoverflow.com/questions/28636598/cannot-override-initializer-of-nsdictionary-in-swift
// http://www.cocoawithlove.com/2008/12/ordereddictionary-subclassing-cocoa.html
//另见https://stackoverflow.com/questions/10799444/nsdictionary-
//
//  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)
    }
}