My app uses a Dictionary
protected _categoryToValueDict:Dictionary = new Dictionary();
to map something to something else.
Now, at a certain point in the application, I need to remove a certain key from the Dictionary
.
I implemented this simple method:
public function setCategoryNoValue( cat:TAModelCategory ):void {
// delete( _categoryToValueDict[ cat ] );
var old:Dictionary = _categoryToValueDict;
_categoryToValueDict = new Dictionary();
for ( var key:* in old ) {
if ( key != cat ) {
_categoryToValueDict[ key ] = old[ key ];
}
}
}
If I only use [description of the delete operator]
delete( _categoryToValueDict[ cat ] );
the app itself doesn't throw errors in normal mode. But as soon as I serialize its external data structure to an external source [currently SharedObject], the app isn't able to de-serialize it later on.
If I use the above coded manual iterative removal operation, the de-serialize operation works as expected and the model appears in the app.
The alternative should be identical. Shouldn't they?
Thus, my question: What's the difference between the two alternatives?
PS: This question might be related to my previous one.
UPDATE-1
Adobe explains on this page:
To make the object referenced by myObject eligible for garbage collection, you must remove all references to it. In this case, you must change the value of myObject and delete the myObject key from myMap, as shown in the following code:
myObject = null;
delete myMap[myObject];
Is suppose this to be a typo. Shouldn't it read like this:
delete myMap[myObject];
myObject = null;
Why pass a null-pointer to myMap as key?
Okay, I just spent a good two hours or so looking into this, which is way more than I planning on spending looking at this. But I was intrigued.
I think you may have uncovered a legitimate bug in ActionScript's AMF encoding (or in how the Dictionary
class gets seralized via AMF). The bug effects anything that uses AMF, so the exact same bug is reproduceable with a ByteArray
, so I'm going to use that for demonstration purposes.
Consider the following code:
var d:Dictionary = new Dictionary(false);
d["goodbye"] = "world";
d["hello"] = "world";
delete d["hello"]
var ba:ByteArray = new ByteArray();
ba.writeObject(d);
var len:uint = ba.position;
ba.position = 0;
for(var i:uint=0;i<len;i++) {
trace(ba.readUnsignedByte().toString(16));
}
The output will be:
11 05 00 06 0f 67 6f 6f 64 62 79 65 06 0b 77 6f 72 6c 64
Now what if we don't ever put the "hello"
in as a key:
var d:Dictionary = new Dictionary(false);
d["goodbye"] = "world";
var ba:ByteArray = new ByteArray();
ba.writeObject(d);
var len:uint = ba.position;
ba.position = 0;
for(var i:uint=0;i<len;i++) {
trace(ba.readUnsignedByte().toString(16));
}
The output then is:
11 03 00 06 0f 67 6f 6f 64 62 79 65 06 0b 77 6f 72 6c 64
Notice that the length is exactly the same, however they differ in the second byte.
Now lets look at the serialization for if I don't delete "hello"
:
11 05 01 06 0b 68 65 6c 6c 6f 06 0b 77 6f 72 6c 64 06 0f 67 6f 6f 64 62 79 65 06 02
Notice that 05
in the second byte is the same as when we deleted it. I think this is specifying the number of items in the Dictionary. I say "I think" because I dug through the documentation on AMF0/3 for quite a while trying to figure out exactly whats going on here, because it doesn't seem like this should be the serialization for a Dictionary, but its fairly consistent, but I don't get it.
So I think that's why you are hitting an exception (specifically the "End of file" error), because its still thinks there should be another item in the dictionary that it should be de-serializing.
Your alternate method works because you are constructing a new Dictionary and populating it... Its "internal counter" is only ever increasing, so it works like a charm.
Another thing to note, that if you set d["Hello"] = undefined
, it does not throw an exception, but the item does not get removed from the dictionary. The key gets serialized with a value of undefined
in the AMF stream. So the resulting byte-stream is longer than if it was never there.
Using an Object
doesn't seem to exhibit this same behavior. Not only doesn't not produce an error, the generated bytecode is more in line with the AMF0/3 documentation I could find from Adobe. And the resulting "key" is literally dropped from the serialization, like it was in fact never there. So I'm not sure what special case they are using for Dictionary
(apparently the undocumented AMF3 datatype 0x11
), but it does not play right with deleting items out of it.
It seems like a legit bug to me.
edit
So I dug around a bit more and found other people talking about AMF serilization of a Dictionary
.
0x11 : Dictionary Data Type
0x05 : Bit code: XXXX XXXY
: If y == 0 then X is a reference to a previously encoded object in the stream
: If y == 1 then X is the number of key/val pairs in the dictionary.
So if this case 5&1 == 1
and 5>>1 == 2
, so it's expecting two key/val pairs in the "bad" serialized version.
Correct syntax for the delete operator is like this:
delete _categoryToValueDict[ cat ];
Although using parenthesis seems to compile fine, it's not the correct way.