I'm trying to figure out how to create an NSMutableDictionary that retains instead of copies its keys. I have implemented -(NSUInteger)hash and -(id)isEqual: for my desired keys, I am just having trouble figuring out which options to specify in the callbacks.
CFDictionaryKeyCallBacks keyCallbacks = { 0, NULL, NULL, CFCopyDescription, CFEqual, NULL };
self.commonParents = (NSMutableDictionary*)CFBridgingRelease(CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks));
The above code works correctly in ARC for using weak references to keys, but what if I want strong references? What should the key callbacks look like?
tl;dr:
Create a CFDictionaryRef with the provided default callback functions. It'll do what you want. Just don't call it an NSDictionary.
Yes, you can create a CFDictionaryRef that retains its keys and does not copy them. This is, in fact, the default behavior of a CFDictionaryRef.
The documentation for CFDictionaryCreateMutable() says:
If the dictionary will contain only CFType objects, then pass a pointer to kCFTypeDictionaryKeyCallBacks as this parameter to use the default callback functions.
(So if you're only going to be putting normal Objective-C objects into the array and not random things like void * pointers or whatever, this is what you want)
And the documentation for kCFTypeDictionaryKeyCallBacks says:
Predefined CFDictionaryKeyCallBacks structure containing a set of callbacks appropriate for use when the keys of a CFDictionary are all CFType-derived objects.
The retain callback is CFRetain, the release callback is CFRelease, the copy callback is CFCopyDescription, the equal callback is CFEqual. Therefore, if you use a pointer to this constant when creating the dictionary, keys are automatically retained when added to the collection, and released when removed from the collection.
Note that the retain callback is CFRetain() and not something like CFCopyObject (which doesn't actually exist).
In fact, Core Foundation doesn't have a canonical way to "copy any object", which is why functions like CFStringCreateCopy, CFArrayCreateCopy, CGPathCreateCopy, etc exist.
So, what you can do is create your dictionary like this:
CFDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
And you now have a dictionary that retains its keys and does not copy them.
I'm going to put the following bit in big letters so that you grok what I'm about to say:
This dictionary you've created is not an NSDictionary.
Yes, NSDictionary and CFDictionaryRef are toll-free bridged. But casting this CFDictionaryRef to an NSDictionary would be an abuse of that bridging, because of this line in the NSDictionary documentation:
...a key can be any object (provided that it conforms to the NSCopying protocol—see below)
Similarly, the documentation for -[NSMutableDictionary setObject:forKey:] explicitly says:
The key is copied (using copyWithZone:; keys must conform to the NSCopying protocol).
The keys in your dictionary don't have to conform to <NSCopying> and are not copied using -copyWithZone:, which means your dictionary is NOT an NSDictionary (or NSMutableDictionary). Any time you see NSDictionary used in code, you should be providing a key-value map where the keys are copied (not retained). That is the API contract. To do anything else could result in undefined behavior.
(The fact that some objects override -copy to return [self retain] is an implementation detail and is not relevant to this discussion on "what is an NSDictionary".)
I think the best answer is buried in comment, so I'll highlight it here: The simplest approach is to use a +[NSMapTable strongToStrongObjectsMapTable] (or maybe one of the variants with weak references).
My suggest is that instead of doing this you subclass NSString or whatever class you're using as key, and override the copy method in a way that it returns the string retained, instead of a copied string.
I think there is 2 possibles solutions that could be achieved using plain old NSMutableDictionary. They are not as elegant as NSMapTable would be.
You state that each of your Key have a uniqueID, so I assume that this Value won't change over time.
Option 1 :
Use the uniqueID of your actual key to be the key of an NSMutableDictionary that would store NSArray of #[key, value] so the whole structure look like this
#{ key1.uniqueID : #[key1, value1], key2.uniqueID : #[key2 : value2] }
Option 2 :
Make a subclass of NSObject that is a wrapper around option 1. Or any variation on option 1.
Those are only valid if uniqueID never change
Related
In converting old projects from Objective-C to Swift, I've mostly been able to use Dictionary in place of NSMutableDictionary. But in some cases, it's a hassle or uses a lot of extra memory to have the Dictionaries copying by value.
I thought I could simply change some Dictionary collections to NSMutableDictionary to make them objects that copy by value, but I don't see a way to specify the key and value types. This works in Objective-C:
NSMutableDictionary<NSString*, NSString*> *dict = [NSMutableDictionary dictionary];
But this gives an error "Cannot specialize non-generic type 'NSMutableDictionary'" in Swift:
let dict: NSMutableDictionary<String, String> = [:]
Is there a way to specify the types so I don't have to be constantly casting the values I get out of the dictionary?
Alternatively, is there another kind of collection object that supports key and value types like Dictionary but copies by reference like NSMutableDictionary?
UPDATE
I tried using NSMapTable as suggested in the comment below. That's missing some features of NSDictionary (e.g., it doesn't conform to IteratorProtocol), so I made a subclass to try making a drop-in replacement for Dictionary. I then ran into problems making my subclass generic since Swift and Objective-C have different support for that.
Since that would either require a lot of casting (or making a different subclass for each type of data I wanted to store in the dictionary), I then tried just using NSMutableDictionary and casting all the values when I read them. Unfortunately, after all that work, I couldn't see any difference in memory usage compared to using Dictionary.
So I guess having collections that copy by value isn't really my problem. It shouldn't be since I'm never retaining anything for very long, but I didn't see these memory problems until I migrated from Objective-C. I'll have to do more testing and explore other solutions.
The Objective-C specification:
NSMutableDictionary<NSString*, NSString*>
Is not a true implementation of Generics. It simply gives hints to the compiler that the dictionary will contain strings as the keys and values and the compiler can tell you, at compile time, if you make a simple mistake.
At the end of the day, there is nothing at runtime to enforce those type specifications. An NSDictionary (mutable or not) will have id for the keys, and id for the values and the runtime format will not change. That's why you can get away with using [NSMutableDictionary dictionary] to initialize all NSDictionaries... the type spec only has meaning at compile time.
In contrast when you use a identical syntax in Swift, say Dictionary<String, Int>, you are using true generics. The runtime representation of the dictionary may change depending on what key and value types you use.
In other words, in spite of similarities in their in Syntax, the <type, type> construct in Objective-C and in Swift mean VERY different things.
In Swift's Eyes, an NSDictionary (mutable or not) is simply a NSObject, just like every other NSObject so NSDictionary<NSString, NSString> is a nonsensical use of the generic type specification syntax - you're saying you want to use generics with a type that is not a generic type (hence the error).
There is no Swift syntax (that I'm aware of) that lets you specify the type you'd like to stand in for NSObject in things like NSDictionaries and NSArrays. You're going to have to use casting.
Even in Objective-C the type specs mean nothing and it's possible to squeeze something in there that doesn't belong. Consider:
NSDictionary<NSString *, NSString *> *myDictionary = [NSMutableDictionary dictionary];
((NSMutableDictionary *)myDictionary)[[NSNumber numberWithInt: 3]] = [NSURL URLWithString: #"http://www.apple.com"];
Here I declare the dict to use Strings, then shove in a number and a URL. The only way to guard against this would be to check the types, that is to do typecasting (or at least type-checking), for each key and value. Most folks code doesn't do that because it would be a pain, but the only way to get true safety.
Swift, in contrast, focus on the safety right up front. It's one of the defining differences between Swift an Objective-C. So you have to go through the pain if you insist on using "unsafe" Objective-C types.
I want to create a mutable dictionary which I can pass it to another controller so that both the dictionaries in different controllers points to the same memory location. If I change the value at another controller, it is also reflected in the previous controller.
This trick used to work fine with NSMutableDictionary without using any delegates.
My dictionary is of type: [String:AnyObject]
Swift collections are value types, not reference types and although you can pass value types by reference, that lasts only for the lifetime of the call.
What you're doing would be considered bad design — objects are sovereign, with well-defined interfaces, and encapsulated state; they do not informally pool state.
What you probably need to do is take your shared state, formalise an interface to it, and move it to your model. Each controller can separately talk to your model.
Swift's dictionary types are value types whereas your old NSMutableDictionary instances are reference types.
There is nothing that says you HAVE to use Swift's value types in the place of your old dictionary. If you have a good reason for using reference semantics with the dictionary, go ahead and leave it as an NSMutableDictionary and use the methods of that class to manipulate it. Just note in your code that you are using NSMutableDictionary explicitly because you want the reference semantics.
Is there a way how to create conditional NSDictionary? For example, lets assume there's a custom class with 3 properties:
class UserInfoObject
firstName
lastName
address
What I need is to create NSDictionary for non-nil properties out of userInfoObject. It straightforward when I know what properties are non-nil in advance, so I could use this shorthand syntax (or classic one):
NSDictionary *userInfoDic = #{#"firstName": userInfoObject.firstName, #"lastName":userInfoObject.lastName, #"address":userInfoObject.address}
However in my case, I need to create mutable dictionary and then perform manual check/add for each of the property. Is there another shorter way for doing below?
// Create mutable thing
NSMutableDictionary *userInfoDic = [NSMutableDictionary new];
// Check and add first name
if (userInfoDic.firstName) {
userInfoDic[#"firstName"] = userInfoDic.firstName;
}
// Check and add last name
if (userInfoDic.lastName) {
userInfoDic[#"lastName"] = userInfoDic.lastName;
}
// Check and add address
if (userInfoDic.address) {
userInfoDic[#"address"] = userInfoDic.address;
}
I had pretty much classes and properties, so shorthand (if exists) could facilitate the process :)
NSMutableDictionary already does this.
setValue:forKey: does this for a mutable dictionary:
This method adds value and key to the dictionary using setObject:forKey:, unless value is nil in which case the method instead attempts to remove key using removeObjectForKey:.
So, use setValue:forKey: instead of setObject:forKey:. There is no shorthand literal for this method. You could write your own NSMutableDictionary subclass and override setObject:forKeyedSubscript: to safely ignore nil objects, but it seems like too much work. As the documents say:
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.
Take this to mean that you should, rather than subclass, create your own wrapper object that has an NSMutableDictionary as a backing store, and implements all the required methods to access the inner dictionary, overriding setObject:forKeyedSubscript: to work with nil. But I wouldn't recommend it just to avoid a few lines of code elsewhere.
I have dictionary in which i m storing
1)keys = which is string attribute of an "SimpleObject" with 'assign' property
--2) value = "SimpleObject"
NSMutableDictionary retains the object so im releasing the object. the key is present in the same object. The key is string.
Now do i need to explicitly retain the string key before add it to dict ?
No, you do not need to explicitly retain the NSString, because your object already does that. Since the dictionary retains the object, the NSString is safe from being released prematurely.
Moreover, NSDictionary makes a copy of your string key, as a precaution against using a mutable object as a dictionary key (which is very bad). That's why you are free to change that key inside the object as you please. Of course that would not change the placement of the object inside the dictionary.
The answer is no. Whether or not you are using ARC. No.
No, you do not need to retain the keys (or the values) of an NSDictionary. This is because NSDictionary copies the key. When you retrieve an objects with objectForKey: isEqual: is used to determine which key refers to the object you passed in.
The basic rule in manual memory management in Cocoa is -- worry about what you're doing in that object or method; don't worry about what any other object is doing.
All you're doing is passing the key to a method of the dictionary. You are not storing it around anywhere. Whatever the dictionary does with it, it is responsible for the proper memory management. What it does is none of your business.
(There is a slight exception with blocks, in that you sometimes must copy them before passing to a type-agnostic method. But let's not worry about this for now.)
What is the point of the NSUserDefaults methods such as -setFloat:forKey: and -floatForKey: when -registerDefaults: accepts only a NSDictionary which can't hold any primitive data types - only objects.
Instead it seems I have to use -setObject:forKey and -objectForKey: and store only NSNumber objects if I want to be able to give my floats any actual default values.
What am I missing here?
setFloat: is just a convenience method that creates an NSNumber and then passes that to setObject:. floatForKey: does the reverse.
NSDictionary can only hold object types, so you need to wrap primitives in the appropriate objects. So yes, you do need to do what you are doing to set up the default defaults.
It would be nice if you could use those methods directly on an NSDictionary, that would be a pretty trivial category to write.