NSDictionary und redundante Werte - wie kann ich die "faule" Speicherung umgehen?

  • NSDictionary und redundante Werte - wie kann ich die "faule" Speicherung umgehen?

    Hi!

    In einem größeren Projekt stolpere ich gerade über eine seltsame Sache: Wenn in einem Dictionary für mehrere Keys identische Werte gespeichert werden sollen, dann wird nur ein Objekt angelegt und die Redundanzen verweisen darauf, statt eigene Objekte darzustellen. Im Sinne von Effizienz vermutlich. Kleines Beispiel dazu:

    Quellcode

    1. NSDictionary *myDict = @{@"key1": @"obj1", @"key2": @"obj1", @"key3" : @"x"};
    2. NSLog(@"%p",[myDict objectForKey:@"key1"]);
    3. NSLog(@"%p",[myDict objectForKey:@"key2"]);


    Die Ausgabe dazu:

    Quellcode

    1. 2013-03-06 01:31:45.567 DictTestRedundanz[99430:303] 0x1000028f8
    2. 2013-03-06 01:31:45.568 DictTestRedundanz[99430:303] 0x1000028f8


    Wie kann ich erwirken, dass tatsächlich jeder Wert ein eigenes Objekt darstellt?
  • Ich weiß nicht ob das möglich ist (wird wohl mit dem Hashwert der Strings festgestellt), aber wieso sollte man das überhaupt wollen? Ist doch gut so.
    “I want to see an elephant hunt down a man for the sole purpose of collecting his teeth, while a chorus of typewriters sings songs that praises the bananas for their wisdom, leadership, and their high levels of potassium.” ― Jarod Kintz, I Want
  • Das hat weniger mit dem Dictionary zu tun als mit den String-Literalen. Literale (und andere häufig gebrauchte Objekte) werden oft nicht mehrfach erzeugt, sondern verweisen alle auf dieselbe Instanz. Die Objekte sind immutable und überleben den gesamten Programmablauf, deshalb macht das keinen Unterschied.

    Du kannst verschiedene Instanzen über Umwege über die entsprechenden Konstruktoren erzeugen. Die sind allerdings manchmal recht clever: Sie vermeiden es, unnötig Speicher zu verbraten und geben lieber eine Referenz auf das bereits existierende Objekt zurück (das sich, wie gesagt, exakt identisch verhält). Deshalb sind ggf. Umwege, in diesem Fall z.B. über einen NSMutableString, notwendig, um sie auszutricksen.

    Die Frage ist eher (wie Daniel2 schon sagte), warum du unbedingt verschiedene Instanzen haben willst. In allen regulären Fällen sollte man keinen Unterschied bemerken. Mit anderen Worten: Wenn du da unterschiedliche Instanzen brauchst, ist höchstwahrscheinlich etwas anderes in deinem Design im Argen. Warum sollen die denn unbedingt unterschiedlich sein?
    Multigrad - 360°-Produktfotografie für den Mac
  • mattik schrieb:

    Das hat weniger mit dem Dictionary zu tun als mit den String-Literalen. Literale (und andere häufig gebrauchte Objekte) werden oft nicht mehrfach erzeugt, sondern verweisen alle auf dieselbe Instanz. Die Objekte sind immutable und überleben den gesamten Programmablauf, deshalb macht das keinen Unterschied.

    Du kannst verschiedene Instanzen über Umwege über die entsprechenden Konstruktoren erzeugen. Die sind allerdings manchmal recht clever: Sie vermeiden es, unnötig Speicher zu verbraten und geben lieber eine Referenz auf das bereits existierende Objekt zurück (das sich, wie gesagt, exakt identisch verhält). Deshalb sind ggf. Umwege, in diesem Fall z.B. über einen NSMutableString, notwendig, um sie auszutricksen.

    Die Frage ist eher (wie Daniel2 schon sagte), warum du unbedingt verschiedene Instanzen haben willst. In allen regulären Fällen sollte man keinen Unterschied bemerken. Mit anderen Worten: Wenn du da unterschiedliche Instanzen brauchst, ist höchstwahrscheinlich etwas anderes in deinem Design im Argen. Warum sollen die denn unbedingt unterschiedlich sein?


    aber solange sie mittels isEqul identisch sind ist es ein und der selbe key für das dictionary.

    das NSDictionary ist vergleichbar mit einer stl-map, ein stl-multimap gibt es in obj-c nicht, braucht man sowas, kann man aber ein NSMutableArray/Set für den key speichern und dort rein dann die objekte.
  • awado schrieb:

    Wie kann ich erwirken, dass tatsächlich jeder Wert ein eigenes Objekt darstellt?

    Du speicherst keine Objekte, du speicherst Zeiger. 5x dasselbe Objekt ist auch 5x derselbe Zeiger.
    Sonst wäre es ja nicht dasselbe, sondern das gleiche Objekt.
    Umgehen kannst du das beispielsweise via [@"obj1" copy]; sofern dein Objekt NSCopying unterstützt.

    Doch auch von mir die Frage: wozu?
    Wenn identische Werte in deinem Dictionary landen können, dann ist es doch sinnvoll, wenn sie auch identisch sind.
    Wenn sie das nicht können sollen, dann speichere halt keine identischen Werte in dein Dictionary.

    gritsch schrieb:

    aber solange sie mittels isEqul identisch sind ist es ein und der selbe key für das dictionary.

    Ich verstehe die Sache so, dass nicht derselbe Key das Problem ist, sondern der Fall, dass @"obj1" bei jeder Verwendung auf die Speicheradresse zeigt.
    Hätte ich zunächst auch nicht verstanden, da das ja ein absolut logisches und bekanntes Verhalten ist.
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Lucas de Vil schrieb:

    awado schrieb:

    Wie kann ich erwirken, dass tatsächlich jeder Wert ein eigenes Objekt darstellt?

    Du speicherst keine Objekte, du speicherst Zeiger. 5x dasselbe Objekt ist auch 5x derselbe Zeiger.
    Sonst wäre es ja nicht dasselbe, sondern das gleiche Objekt.
    Umgehen kannst du das beispielsweise via [@"obj1" copy]; sofern dein Objekt NSCopying unterstützt.

    Doch auch von mir die Frage: wozu?
    Wenn identische Werte in deinem Dictionary landen können, dann ist es doch sinnvoll, wenn sie auch identisch sind.
    Wenn sie das nicht können sollen, dann speichere halt keine identischen Werte in dein Dictionary.

    gritsch schrieb:

    aber solange sie mittels isEqul identisch sind ist es ein und der selbe key für das dictionary.

    Ich verstehe die Sache so, dass nicht derselbe Key das Problem ist, sondern der Fall, dass @"obj1" bei jeder Verwendung auf die Speicheradresse zeigt.
    Hätte ich zunächst auch nicht verstanden, da das ja ein absolut logisches und bekanntes Verhalten ist.


    nein, copy bringt bei immutablen klassen auch nichts weil dort ein copy einfach nur ein (retained) self zurück gibt
  • Danke für Eure Antworten. Der Grund, warum ich das benötige, ist recht einfach: Zum Auswerten von PLists. Diese lassen sich ja recht praktisch in einem Dictionary aufnehmen. Nun sind die bei mir aber sehr groß und unterschiedlich. Somit weiss ich nicht immer, welche Keys darin vorhanden sind und muss diese auslesen. Das geht so lange mit "allKeys" gut, bis verschiedene Keys gleiche Values haben. So entsteht das, was ich in meinem Beispiel dargestellt habe.
  • awado schrieb:

    Danke für Eure Antworten. Der Grund, warum ich das benötige, ist recht einfach: Zum Auswerten von PLists. Diese lassen sich ja recht praktisch in einem Dictionary aufnehmen. Nun sind die bei mir aber sehr groß und unterschiedlich. Somit weiss ich nicht immer, welche Keys darin vorhanden sind und muss diese auslesen. Das geht so lange mit "allKeys" gut, bis verschiedene Keys gleiche Values haben. So entsteht das, was ich in meinem Beispiel dargestellt habe.


    achso, es geht dir also doch um die objekte/values/payload und nicht um die keys.

    und wo liegt da das problem? es sind ja die gleichen werte. egal mit wie vielen verschiedenen keys du darauf zeigst. das ist doch spitze da es automatisch optimiert wird!
  • Jaja, an sich ne Klasse Sache. Aber in manchen Fällen eben hinderlich. Anderes Beispiel: Ich habe ein Dictionary mit folgenden Einträgen:

    Name -> admin
    Kurzname -> admin
    Passwort -> 123

    Nun liefert mir allKEys:

    Name
    Kurzname
    Passwort

    Möchte ich wissen, unter welchem Key das zweite "admin" abgelegt ist, werde ich immer bei "Name" landen, wenn ich nach der ID gehe. Niemals bei "Kurzname"!

    Ich habe inzwischen herausgefunden, dass es tatsächlich an den String Literals liegt. Nicht am NSDictionary. Wenn ich also ein Dictionary vorbelegen möchte, muss ich tricksen:

    Quellcode

    1. NSMutableDictionary *myDict = @{@"key1": [NSString stringWithFormat:@"%@",@"obj1"], @"key2": [NSString stringWithFormat:@"%@",@"obj1"]};


    Nun stellt sich also eher die Frage, ob es da einen eleganteren Weg gibt. UND: ob bei programmatisch erzeugten Dictionaries solche Redundanzen nicht ebenfalls optimiert werden. Ich bekomme die PLists nämlich per NSTask von einem Server.
  • warum willst du von einem wert rückschlüsse auf den key ziehen? das macht ja sektenst keinen sinn! Und falls du das doch willst, gibt es ja allKeysForObject:

    NEIN, bloß nicht so trixen wie du geschrieben hast, oder steht in der doku dass garantiert ist dass dies nicht unter die optimierung fällt. es ist eher fraglich warum das NICHT unter die optimierung fällt.

    wenn es wirklich sein muss, dann verwende NSMutableStrings!
  • Also, hier die ausführliche Zusammenfassung meines Vorhabens:

    Ich kommuniziere mit einem Server per NSTask. Das heisst, er bekommt seine Befehle als Plist und er liefert mir Antworten auch als Plist. Diese werden in NSDictionaries aufgenommen. Nun will ich beides - Antwort und Eingabe - per NSOutlineView hierarchisch darstellen. (Aufklappbar etc.) In den Methoden von NSOutlineView wird aber ein "item" als Objekt durchgereicht, von dem ich zur Laufzeit nicht weiss, welches Parent-Objekt es besitzt. Bei diesem "item" handelt es sich natürlich um die Values und ich möchte entsprechend daneben auch die Keys darstellen, sonst sieht man ja in der NSOLView nicht, welche Bedeutung sie haben. Also bin ich gezwungen, vorher das Dictionary zu analysieren und mir die Keys der Values zu merken. Dazu brauche ich aber stets eine 1-zu-1 Beziehung.

    Das Resultat ist momentan eben, dass in der NSOLView z.B. 5x "Name = ''" vorkommt, weil bei 5 Einträgen noch kein Value gesetzt ist.
  • awado schrieb:

    Perfekt! Danke! Mit dem "stringWithFormat" kann ich leben, weil die Redundanzen bei den Query-Plists beherrschbar sind. Mit dieser NSPropertyListMutabilityOption sind nun auch die Replies beherrschbar. You made my day!


    NEIN, bitte nicht verwenden. vielleicht erzeugt das stringWithFormat eben auch nicht ein neues objekt (oder steht in der doku dass das NIE so sein wird?).
  • awado schrieb:

    Doch, tut es. Zumindest in meinen Tests, wenn ich "[NSMutableString stringWithFormat..." benutze. Mein NSOLView stimmt nun.


    ist mir schon klar dass es durchaus möglich ist dass das MOMENTAN bzw auf deinem TEST-OS so ist, aber es ist nicht garantiert (und erst recht nicht wahrscheinlich) dass das morgen (nächstes systemupdate) noch so sein wird.

    man soll keine SEITENEFFEKTE ausnutzen oder steht in der doku irgendwo dass man die methode zu diesem zweck verwenden soll?