Grand Central Dispatch und Memory Management

  • Grand Central Dispatch und Memory Management

    Hallo,

    ich habe mal eine Frage zu Grand Central Dispatch. Angenommen ich habe eine dispatch queue:

    Quellcode

    1. dataWorkerQueue = dispatch_queue_create("dataWorkerQueue", NULL);

    Welche die Zugriffe (aus unterschiedlichen Threads) eines NSMutableDictionary (dataDictionary) synchronisiert:

    Quellcode

    1. - (void) setData: (Data*) newData {
    2. dispatch_sync(dataWorkerQueue , ^{
    3. [self.dataDictionarysetObject:newData forKey:[NSNumber numberWithInt:newData.dataID]];
    4. });
    5. }


    Nun benötige ich im ViewController ein Array mit allen Objekten des NSMutableDictionarys. Dafür habe ich die Methode data erstellt:

    Quellcode

    1. - (NSArray*) data {
    2. __block NSArray *result = nil;
    3. dispatch_sync(dataWorkerQueue , ^{
    4. result = [self.dataDictionary allValues];
    5. });
    6. return result;
    7. }


    Ist die Annahme richtig, dass result eine (flache) Kopie ist, welche autoreleased wird? Somit also das dataDictionary problemlos (per Background Thread) geändert werden kann?

    Stefan
  • stefan! schrieb:

    Ist die Annahme richtig, dass result eine (flache) Kopie ist, welche autoreleased wird?

    Flache Kopien und Autorelease haben nichts miteinander zu tun. Nach den Speicherverwaltungsregeln (Name der Methode allValues) brauchst Du Dich jedoch nicht um die Freigabe kümmern.

    stefan! schrieb:

    Somit also das dataDictionary problemlos (per Background Thread) geändert werden kann?

    Auch das hat weder mit flachen Kopien noch mit Autorelease zu tun.

    BTW: Die Operationen laufen zwar in einer eigenen Queue, vermutlich hast Du jedoch keine Nebenläufigkeit. Mit dem Konstrukt dispatch_sync kannst Du auch keine Synchronisationsprobleme vermeiden. Das heißt nur, dass der Block synchron zum aktuellen Thread ausgeführt wird.
    „Meine Komplikation hatte eine Komplikation.“
  • Hallo macmoonshine,

    macmoonshine schrieb:

    BTW: Die Operationen laufen zwar in einer eigenen Queue, vermutlich hast Du jedoch keine Nebenläufigkeit. Mit dem Konstrukt dispatch_sync kannst Du auch keine Synchronisationsprobleme vermeiden. Das heißt nur, dass der Block synchron zum aktuellen Thread ausgeführt wird.

    Was meinst du mit "vermutlich hast du jedoch keine Nebenläufigkeit" ? Ich hatte angenommen mit dispatch_sync kann ich @synchronized Aufrufe ersetzen, wie hier beschrieben: fieryrobot.com/blog/2010/09/01…g-grand-central-dispatch/

    D.h. wenn 2 Threads parallel setData aufrufen könnte es knallen, da NSMutableDictionary nicht thread-safe ist?
  • Wenn es eine sequentielle Dispatch-Queue ist und Du alle Operationen auf den Dictionary nur so durchführst, sollte das klappen.

    stefan! schrieb:

    D.h. wenn 2 Threads parallel setData aufrufen könnte es knallen, da NSMutableDictionary nicht thread-safe ist?

    Problematisch ist in der Regel paralleles Setzen eines Wertes und eine Iteration. Das gilt auch, wenn Du über allValues oder sogar im selben Thread (also nicht nebenläufig) iteratierst.
    „Meine Komplikation hatte eine Komplikation.“
  • Bist du sicher das allValues keine Kopie zurückgibt?

    Quellcode

    1. NSLog(@"address 1 %p", [self.dataDictionary allValues]);
    2. NSLog(@"address 2 %p", [self.dataDictionary allValues]);

    Quellcode

    1. 2013-07-29 16:57:28.946 mobile[18236:5f03] address 1 0x1f06d0a0
    2. 2013-07-29 16:57:28.947 mobile[18236:5f03] address 2 0x1dddd880

    und

    Quellcode

    1. __block NSArray *result = nil;
    2. dispatch_sync(dataWorkerQueue, ^{
    3. result = [self.dataDictionary allValues]
    4. NSLog(@"print: %@", result);
    5. >> print: data1
    6. >> print: data2
    7. self.dataDictionary removeObjectForKey:@1]; //remove data1
    8. NSLog(@"print after remove: %@", result);
    9. >> print after remove: data1
    10. >> print after remove: data2
    11. });
    Alles anzeigen


    Beim 2. NSLog hätte data1 fehlen müssen !?
  • macmoonshine schrieb:

    Das zweite Beispiel verstehe ich nicht so ganz.

    Die Idee war, wenn es sich bei allValues um eine Kopie handelt und ich mir die Referenz merke (result), ist nach dem Entfernen eines Eintrags aus original NSDictionary der entfernte Eintrag in der Kopie (result) noch vorhanden. Falls es keine Kopie sein sollte, ist der Eintrag auch in der Referenz (result) nicht mehr vorhanden. Die >> sollen die Ausgabe des NSLog darstellen.
  • stefan! schrieb:

    Die Idee war, wenn es sich bei allValues um eine Kopie handelt und ich mir die Referenz merke (result), ist nach dem Entfernen eines Eintrags aus original NSDictionary der entfernte Eintrag in der Kopie (result) noch vorhanden.

    Das der Eintrag in results noch vorhanden ist liegt nicht daran, dass der Eintrag kopiert wurde. Es liegt daran, dass ein NSArray für seine Objekte auch die Eigentümerschaft übernimmt (retain).

    stefan! schrieb:

    Falls es keine Kopie sein sollte, ist der Eintrag auch in der Referenz (result) nicht mehr vorhanden. Die >> sollen die Ausgabe des NSLog darstellen.

    Wenn Du ein Objekt aus dem Dictionary entfernst, welches Du Dir vorher mit allObjects in ein NSArray gepackt hast, wird lediglich der retainCount um eins erniedrigt. Deine NSLog Ausgabe gibt ja nur die Adresse des NSArrays aus. Schau Dir mal die Ausgaben von diesem Codeschnipsel an. Dann sollte es klarer werden:

    Quellcode

    1. NSDictionary *dict = @{@1: @"Nummer 1", @2: @"Nummer 2"};
    2. NSArray *values = [dict allValues];
    3. NSLog(@"Adressen der Value-Objekte im Dictionary:");
    4. NSLog(@"%p", [dict objectForKey:@1]);
    5. NSLog(@"%p", [dict objectForKey:@2]);
    6. NSLog(@"Adressen der Value-Objekte im Array:");
    7. NSLog(@"%p", [values objectAtIndex:0]);
    8. NSLog(@"%p", [values objectAtIndex:1]);

    Michael
  • Michael schrieb:

    Das der Eintrag in results noch vorhanden ist liegt nicht daran, dass der Eintrag kopiert wurde. Es liegt daran, dass ein NSArray für seine Objekte auch die Eigentümerschaft übernimmt (retain).
    Stimmt, guter Hinweis. Daran habe ich garnicht gedacht.

    Michael schrieb:

    Wenn du ein Objekt aus dem Dictionary entfernst, welches Du Dir vorher mit allObjects in ein NSArray gepackt hast, wird lediglich der retainCount um eins erniedrigt. Deine NSLog Ausgabe gibt ja nur die Adresse des NSArrays aus. Schau Dir mal die Ausgaben von diesem Codeschnipsel an. Dann sollte es klarer werden:

    Quellcode

    1. NSDictionary *dict = @{@1: @"Nummer 1", @2: @"Nummer 2"};
    2. NSArray *values = [dict allValues];
    3. NSLog(@"Adressen der Value-Objekte im Dictionary:");
    4. NSLog(@"%p", [dict objectForKey:@1]);
    5. NSLog(@"%p", [dict objectForKey:@2]);
    6. NSLog(@"Adressen der Value-Objekte im Array:");
    7. NSLog(@"%p", [values objectAtIndex:0]);
    8. NSLog(@"%p", [values objectAtIndex:1]);

    Michael

    Ergebnis:

    Quellcode

    1. 2013-07-29 20:19:37.539 copy[10827:c07] Adressen der Value-Objekte im Dictionary:
    2. 2013-07-29 20:19:37.540 copy[10827:c07] 0x46b8
    3. 2013-07-29 20:19:37.541 copy[10827:c07] 0x46c8
    4. 2013-07-29 20:19:37.542 copy[10827:c07] Adressen der Value-Objekte im Array:
    5. 2013-07-29 20:19:37.542 copy[10827:c07] 0x46b8
    6. 2013-07-29 20:19:37.542 copy[10827:c07] 0x46c8

    Dieses Ergebnis hätte ich auch erwartet, da es ja nur eine "flache Kopie" gewesen wäre. Somit also die Objekte selbst nicht kopiert werden. Gibt es denn keine Möglichkeit zu testen ob es sich um eine (flache) Kopie handelt?
  • stefan! schrieb:

    Dieses Ergebnis hätte ich auch erwartet, da es ja nur eine "flache Kopie" gewesen wäre. Somit also die Objekte selbst nicht kopiert werden. Gibt es denn keine Möglichkeit zu testen ob es sich um eine (flache) Kopie handelt?

    Warum kümmerst Du Dich um solche Implementierungsdetails von NSDictionary? Warum verwendest Du nicht

    Quellcode

    1. [[self.dataDictionary allValues] copy]
    ?
    Damit hast Du eine flache Kopie des Arrays, die auch gegen Änderungen des Dictionarys immun ist.
    „Meine Komplikation hatte eine Komplikation.“
  • macmoonshine schrieb:

    Warum kümmerst Du Dich um solche Implementierungsdetails von NSDictionary? Warum verwendest Du nicht

    Quellcode

    1. [[self.dataDictionary allValues] copy]
    ?
    Damit hast Du eine flache Kopie des Arrays, die auch gegen Änderungen des Dictionarys immun ist.
    Du hast ja recht. Ich wollte halt die (tiefergreifenden) Details verstehen um so ggf. unnötige Kopien zu vermeiden (für den Fall es wäre schon eine Kopie müsste ich es nicht erneut kopieren).
  • kleinweby schrieb:

    Das copy ist nicht notwendig.

    Methodensignatur:

    - (NSArray *)allValues;

    NSArray -> ist immutable, daraus folgt das sich das zurückgegebene Objekt nie ändert. Das verspricht dir das System.


    leider kann man sich bei apple da nicht drauf verlassen (und wohl bei den meisten devs auch nicht).
    da wird gerne mal das mutable-objekt zurückgeliefert das intern zur datenhaltung verwendet wird und siche eben ändert.
    ist sehr ärgerlich wenn man in sowas reintappt ;(
  • Ist dir das bei einem Apple Framework passiert? (Beispiel würde mich interessieren) Wenn man sich auf sowas nicht verlassen kann, kann man sich auch nicht darauf verlassen das copy einem dann was brauchbares liefert.

    Übrigens sagt die Doku auch noch: "A new array containing the dictionary’s values, or an empty array if the dictionary has no entries."
  • kleinweby schrieb:

    Ist dir das bei einem Apple Framework passiert? (Beispiel würde mich interessieren) Wenn man sich auf sowas nicht verlassen kann, kann man sich auch nicht darauf verlassen das copy einem dann was brauchbares liefert.

    Übrigens sagt die Doku auch noch: "A new array containing the dictionary’s values, or an empty array if the dictionary has no entries."


    doch natürlich hilft dann ein copy (mit autorelease bei MRC) denn das copy liefert in der NSArray implementation (jaja, ist nicht wirklich ein NSArray) self zurück und in der NSMutableArray implementation eben eine kopie von sich selbst.

    hier ein beispiel:

    MKMapView.h

    Quellcode

    1. @property (nonatomic, readonly) NSArray *annotations;


    also schreibt man folgenden code:

    Quellcode

    1. NSArray *annotations = [theMap annotations];
    2. for(PEAnnotation *nAnnotation in annotations)
    3. {
    4. [theMap removeAnnotation:nAnnotation];
    5. }


    das ganze crasht unter 4.0 und 4.1 da apple einem bei annotations direkt das interne mutablearray zurückgibt aus welchem dann mittels removeAnnotation die elemente gelöscht werden.
    erst in 4.2 bekommt man eine kopie des arrays zurück.
  • macmoonshine schrieb:

    stefan! schrieb:

    Du hast ja recht. Ich wollte halt die (tiefergreifenden) Details verstehen um so ggf. unnötige Kopien zu vermeiden (für den Fall es wäre schon eine Kopie müsste ich es nicht erneut kopieren).

    Die Methode copy kopiert nur, wenn es notwendig ist, und das ist hier anscheinend der Fall.

    ach, diese Diskussion hatte ich auch mal bei einem Vortrag. Nein, darauf kann man sich nicht verlassen, weil NSMutableArray eine Subklasse von NSArray ist und Subklassen stets auch auf ihre Basisklassen typisiert sind. Dummerweise gilt dann bei Apples Cocoa-Implementierung Liskow nicht mehr. Du wirst in meinen Büchern dazu auch eine, äh, Hinweis finden.
    Es hat noch nie etwas gefunzt. To tear down the Wall would be a Werror!
    25.06.2016: [Swift] gehört zu meinen *Favorite Tags* auf SO. In welcher Bedeutung von "favorite"?