在子类化NSMutableDictionary时有什么陷阱和困难?

时间:2015-10-09 00:52:05

标签: ios subclass nsmutabledictionary

Apple说

  

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

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

他们可能应该让这个更强一点,并说追究这个风险,风险自负

但是,在某些情况下,继承NSMutableDictionary非常重要。就我而言,在符号上,它确实与我的代码相关。要克服相当多的障碍。关于此,还有其他网络和SO条目,但我在旅途中遇到了一些看似新的问题,所以想写下来为我的记忆和帮助其他人。所以,我会发布我的答案。随意提供自己的额外发现。

1 个答案:

答案 0 :(得分:1)

1)没有代理对象。一开始,由于某种原因,Apple似乎在某些不寻常的方式上使NSMutableDictionaryNSMutableSet不同。我对子类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),您必须执行此操作在Objective-C中创建NSMutableDictionary子类,而不是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"你的代码不起作用。你需要使用" composition"在内部实现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日更新

澄清我的意思&#34;突变变化&#34; (回复下面的@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)
    }
}