2015-10-09 1 views
2

Apple, говоритКаковы ловушки и невзгоды, когда дело доходит до подкласса NSMutableDictionary?

Там, как правило, должно быть мало нужно подкласс NSMutableDictionary. Если вам нужно настроить поведение, часто лучше рассмотреть композицию , а не подклассифицировать.

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

Они, вероятно, следует сделать это немного сильнее и сказать продолжать это на свой страх и риск.

Однако существуют ситуации, когда это может быть важно для подкласса NSMutableDictionary. В моем случае, нотационно, это действительно имело отношение к моему коду. Существует немало препятствий для преодоления. На этом есть другие веб-страницы и SO-записи, но я столкнулся с некоторыми, казалось бы, новыми проблемами в моих путешествиях по этому пути, поэтому хотел записать это для своей памяти и помочь другим. Итак, я отправлю свой ответ на этот вопрос. Не стесняйтесь вносить свои собственные дополнительные результаты.

+1

На самом деле это не вопрос. – quellish

+0

Хотя это здорово и разрешено, это действительно сообщение в блоге. Но ответ, похоже, больше зависит от того, как я правильно делаю эту работу: подписи под NSCoding и Swift. Дальше это может все сломаться на Swift в обновлении, Swift все еще меняется. – zaph

ответ

1

1) Прокси-объекты отсутствуют. С самого начала, по некоторым причинам, Apple, похоже, сделала NSMutableDictionary различными необычными способами, чем NSMutableSet. Моя основная потребность в подклассе NSMutableDictionary действительно связана с необходимостью знать о изменениях мутаций в экземпляре NSMutableDictionary. NSMutableSets, например, сделать это немного проще. NSMutableSets дает вам доступ к объекту «прокси»: mutableSetValueForKey. Это дает вам механизм знать, когда мутирует содержимое набора. См. https://www.objc.io/issues/7-foundation/key-value-coding-and-observing/ для некоторых деталей. То, что вы ожидали увидеть, было бы похоже на mutableDictValueForKey, но этого, похоже, не существует.

2) Внесите инициализацию в свои подклассы! Apple, говорит вам, что вам нужно переопределить методы:

В подклассе, вы должны переопределить оба примитивных методов:

setObject:forKey: 

removeObjectForKey: 

Вы также должны переопределить примитивные методы класса NSDictionary .

NSDictionary и примитивные методы:

initWithObjects:forKeys:count: 

@property count 

objectForKey: 

keyEnumerator: 

НО, вы должны переопределить инициализации метод!

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 (см. https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html), но в нотации это оставляло желать лучшего. Мне действительно нужен тип, полностью совместимый с NSDictionary/NSMutableDictionary, который, как представляется, требует подкласса.

6) Не просто применять методы; вам нужны ваши собственные данные! Если вы просто попытаетесь переопределить методы, как указано выше, и вызывать «супер», ваш код не будет работать. Вам нужно использовать «состав» для внутреннего выполнения свойства NSMutableDictionary. Или любой другой механизм, который вы хотите для реализации своего словаря. Опять же, некоторая кластерная кластерная игра продолжается. См. Мое свойство dict в .m файле ниже.

Вот что у меня есть на сегодняшний день с точки зрения моего кода 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 

Обновление на 10/9/15

Чтобы уточнить, что я имел в виду под «изменениями мутаций» (отвечая на @quelish ниже), вот пример KVO с NSMutableDictionary. Обратите внимание, что вывод этого значения не отражает тест 1 ниже. I.e, изменение ключа не указывается 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) 
    } 
} 
+1

«проистекает из необходимости знать о изменениях мутаций». Используйте наблюдение за ключевыми значениями. – quellish

+0

KVO хорош для того, чтобы сообщить вам, что значение свойства изменилось, но менее полезно сообщить вам, что измененное содержимое коллекции изменилось. Это то, что я имел в виду под «изменениями мутаций». Я добавлю демонстрацию ниже моего ответа выше. –

+0

Не совсем. Коллекции отправляют уведомления о значении ключа, когда их содержимое изменяется. Это справедливо для упорядоченных, неупорядоченных и наборов значений ключей. – quellish