Core Data, Drag&Drop und Reference Counting

  • Core Data, Drag&Drop und Reference Counting

    Hi,

    ich habe ein kleines Verständnisproblem. Ich habe ein Core-Data-Projekt, non-DBA, in dem ich eigene Subklassen von NSManagedObject verwende. Diese sind hierarchisch: Schuljahr -> Klasse -> Fach. Wenn ich nun ein Fach per Drag and Drop verschiebe und mir den Reference Count vorher und nachher ansehe, dann erhöht sich dieser immer während des Drag and Drop. Ich habe beim Verschieben eine Methode, die per KVC die entsprechende (Schul-) Klasse in der Relationship ändert, so dass doch eigentlich der Reference Count gleich hoch bleiben müsste (zumindest wenn ich wieder auf die selbe Klasse verschiebe), oder?! Wenn es so ist, hätte ich vermutlich irgendwo ein Release vergessen, oder?

    Ich bin für jede Hilfe dankbar. Wenn es benötigt wird um mir zu helfen, kann ich ggf. natürlich Code und/oder das Model posten..

    Vielen Dank schon mal.
  • Also, die Delete Rule steht auf Nullify, aber Cascade habe ich auch schon probiert (bitte nicht erschlagen, wenn das auch nicht stimmt.. ;) ).

    Ich benutze zum Ändern wie gesagt eine eigene Methode, da es vorher mit [subject setValue: newClass forKey: @"memberOfClass"] (es ist eine 1:n-Relationship; geht das dann überhaupt oder muss ich den Wert trotzdem mit einem Mutable Set, mutableSetValueforKey: und add- bzw. removeObject ändern?) auch nicht geklappt hat. Die Accessoren für die Eigenschaft "memberOfClass" (übrigens als NSSet*) habe ich als @property (retain) und dann mit @dynamic erzeugt. Leider bin ich gerade nicht zuhause. Code würde ich später mal posten, wenn Ihr nicht evtl. doch noch die rettende Idee habt..

    Vielen Dank schon mal.
  • 1. Speicherverwaltung hat nichts mit der Delete-Rule zu tun. Die Delete-Rule bestimmt lediglich, was bei Löschung (nicht: Entwerfnung aus dem Speicher) eeiner Instanz mit den verwiesenen Instanzen geschehen soll, namentlich, ob die auch gelöscht (nicht: aus dem Speicher entfernt) werden sollen.

    2. Mutmaßlich machst du beim D&D etwas falsch. Wie legst du denn herein und wie beendest du den D&D-Zyklus.
    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"?
  • Eigentlich müssten beim Erstellen Deiner MO-Subclass die entsprechenden KVC-kompatiblen Setaccessoren hinzugefügt worden sein. Ansonsten die entspr. relationship im Xcode CD-Modeller auswählen und im Menü Design->Data Model->"Copy Obj-C 2.0 Method Declarations to Clipboard" auswählen. Dann im Header Deiner MO-Subklasse einfügen. Über diese Accessoren kannst Du dann Dein Modell aktualisieren. Oder Du machst es über mutableSetValueforKey:

    Gruß, Markus
  • So, ich habe nun mal die Accessoren generieren lassen und entsprechend die beiden Dateien erstellt. Leider funktioniert es immer noch nicht so recht.. Scheinbar tut das setPrimitiveValueForKey: nix. Dies ist zwar hier die Beziehung zwischen Schuljahr und Klasse, aber wenn ich das bei dem einen hin bekomme, dann kriege ich das andere auch hin.. Wenn ich nun eine Klasse zu einem Schuljahr hinzufügen will, rufe ich im AppDelegate in einer Methode [aSchoolyear addEntityChildrenObject: aClass] auf. Wenn ich mir nun per NSLog aClass vorher und nachher ansehe, dann steht nach dem Aufruf korrekterweise "aSchoolyear" in der entsprechenden Relationship. Nur wenn ich mir "aSchoolyear" ansehe, dann ist die Relationship auch nachher NULL. Wie kann das sein? Da müsste doch dann entsprechend "aClass" stehen. Im OutlineView wird logischerweise die Klasse auch nicht angezeigt. Ich habe mal den Code gepostet. Irgendwas mache ich (offensichtlich) falsch.. Ich bitte um Nachsicht-bin noch ein ganz kleiner Programmierer.. :D

    Deklaration:


    ...

    Quellcode

    1. @property (nonatomic, retain) NSNumber * firstYear;
    2. @property (nonatomic, retain) NSNumber * secondYear;
    3. @property (nonatomic, retain) NSString * name;
    4. @property (nonatomic, retain) NSSet* entityChildren;
    5. + (ALSchoolyear*) schoolyearWithFirstYear: (NSNumber*) aFirstYear andSecondYear: (NSNumber*) aSecondYear;
    6. - (id) initWithFirstYear: (NSNumber*) aFirstYear andSecondYear: (NSNumber*) aSecondYear;
    7. @end
    8. interface ALSchoolyear (CoreDataGeneratedAccessors)
    9. - (void)addEntityChildrenObject:(ALClass *)value;
    10. - (void)removeEntityChildrenObject:(ALClass *)value;
    11. @end
    Alles anzeigen



    Implementierung:


    Quellcode

    1. @implementation ALSchoolyear
    2. @dynamic firstYear, secondYear, name, entityChildren;
    3. + (ALSchoolyear*) schoolyearWithFirstYear: (NSNumber*) aFirstYear andSecondYear: (NSNumber*) aSecondYear {
    4. return [[[self alloc] initWithFirstYear: aFirstYear andSecondYear: aSecondYear] autorelease];
    5. }
    6. - (id) initWithFirstYear: (NSNumber*) aFirstYear andSecondYear: (NSNumber*) aSecondYear {
    7. NSManagedObjectContext* context;
    8. id appDelegate = [NSApp delegate];
    9. context = [appDelegate managedObjectContext];
    10. NSEntityDescription* entity = [NSEntityDescription entityForName: @"Schoolyear" inManagedObjectContext: context];
    11. if (self = [super initWithEntity: entity insertIntoManagedObjectContext: context] ) {
    12. firstYear = aFirstYear;
    13. secondYear = aSecondYear;
    14. NSString* tempName = [NSString stringWithFormat: @"%@/%@", aFirstYear, aSecondYear];
    15. name = tempName;
    16. }
    17. return self;
    18. }
    19. @end
    20. @interface ALSchoolyear (CoreDataGeneratedPrimitiveAccessors)
    21. - (NSNumber *)primitiveFirstYear;
    22. - (void)setPrimitiveFirstYear:(NSNumber *)value;
    23. - (NSNumber *)primitiveSecondYear;
    24. - (void)setPrimitiveSecondYear:(NSNumber *)value;
    25. - (NSString *)primitiveName;
    26. - (void)setPrimitiveName:(NSString *)value;
    27. - (NSMutableSet*)primitiveEntityChildren;
    28. - (void)setPrimitiveEntityChildren:(NSMutableSet*)value;
    29. @end
    30. @implementation ALSchoolyear (CoreDataGeneratedAccessors)
    31. - (void)addEntityChildrenObject:(ALClass *)value
    32. {
    33. NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    34. [self willChangeValueForKey:@"entityChildren" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    35. [[self primitiveEntityChildren] addObject:value];
    36. [self didChangeValueForKey:@"entityChildren" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
    37. [changedObjects release];
    38. }
    39. - (void)removeEntityChildrenObject:(ALClass *)value
    40. {
    41. NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
    42. [self willChangeValueForKey:@"entityChildren" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    43. [[self primitiveEntityChildren] removeObject:value];
    44. [self didChangeValueForKey:@"entityChildren" withSetMutation:NSKeyValueMinusSetMutation usingObjects:changedObjects];
    45. [changedObjects release];
    46. }
    Alles anzeigen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von alexlaske ()

  • So, jetzt habe ich den Code oben in die Tags gesetzt und muss zugeben, dass man jetzt alles viel besser erkennt.. :D :D

    Ich habe mir mittlerweile mal in der Methode addEntityChildrenObject: per Log [self primitiveValueForKey] ausgeben lassen. Sowohl vor als auch nach der Addition von "value" ist dies (NULL). Vorher ist klar, weil wirklich noch kein Objekt enthalten ist. Aber danach müsste ja die Instanz von ALClass da drin sein, oder? Muss primitiveValueForKey: noch implementiert werden, vielleicht?

    Ich hoffe, jemand von Euch hat ne zündende Idee..

    Und sorry nochmal für den schlampigen Beitrag vorher.
  • Du kannst auch die Implementation deiner Set-Accessoren weglassen, das macht CD automatisch. Damit schließt Du wirklich alle Fehlerquellen aus. Du brauchst diese nur, wenn Du in der Implementation eigenen Code ausführen möchtest, was hier nicht der Fall zu sein scheint.

    Deine init-Methoden würde ich weglassen, denn das ist nicht der normale Pfad um MOs zu erzeugen. Initialisierungen machst Du in awakeFromInsert.
    Ausserdem greifst Du dort auf das App-Delegate zu und verstösst gegen das MVC-Pattern (Appdelegate ist Controllerschicht und Du referenzierst diese aus der Modellschicht).
  • Ok, vielen Dank. Habe jetzt schon mal wieder umgeschrieben und es so ähnlich gemacht wie Du es mir vorgeschlagen hast. Prinzipiell verstehe ich auch, was Du bzgl. AwakeFromInsert sagst. Nur wie bekomme ich meine Werte dahin? AwakeFromInsert wird ja nur einmal aufgerufen und ich kann der Funktion ja keine Werte übergeben..

    Ist es denn richtig, dass ich dann einfach die Instanz per [[[ALSchoolyear alloc] init] autorelease] erstelle?

  • Ist es denn richtig, dass ich dann einfach die Instanz per [[[ALSchoolyear alloc] init] autorelease] erstelle

    NEIN!

    Zitat Apple Doku:

    "In a typical Cocoa class, you usually override the designated initializer (often the init method). In a subclass of NSManagedObject, there are three different ways you can customize initialization —by overriding initWithEntity:insertIntoManagedObjectContext:, awakeFromInsert, or awakeFromFetch. You should not override init. You are discouraged from overriding initWithEntity:insertIntoManagedObjectContext: as state changes made in this method may not be properly integrated with undo and redo. The two other methods, awakeFromInsert and awakeFromFetch, allow you to differentiate between two different situations:"

    developer.apple.com/mac/librar…ef/doc/uid/TP40003397-SW2

    Für Deine Initialisierungen erzeuge Deine MO am besten in der Controllerschicht (oder der nächsthöheren Modellebene):

    Quellcode

    1. // im Controller
    2. - (void)addNewSchoolyear
    3. {
    4. NSManagedObjectContext *moc = ...;
    5. NSManagedObject *newSchoolYear = [NSEntityDescription insertNewObjectForEntityForName:@"ALSchoolYear" inManagedObjectContext:moc];
    6. // newSchoolYear jetzt über die accessoren initialisieren
    7. }

    All das findest Du auch im Core Data Programming Guide
  • Hmm, das Problem ist nur, dass die Klasse ja eben nicht NSManagedObject ist, sondern es sich um Entitäten "Schoolyear" handelt, die die Klasse ALSchoolyear (als Unterklasse von NSManagedObject) haben. Also muss ich doch irgendwie eine Instanz der Klasse ALSchoolyear erzeugen. Dies habe ich bisher wie in meinem Code oben gemacht und es hat ja im Prinzip auch geklappt bis auf die Geschichte mit dem Reference Count, wenn ich Drag and Drop Move mache bzw. dass die Relationships nicht stimmen. Ich werde mal versuchen die Accessoren anzupassen und den Rest so zu lassen oder so optimieren, wie Markus mir das vorgeschlagen hat.

    Mal schauen, was passiert..

    Und natürlich werde ich mir den Core Data Programming Guide nochmal zu Gemüte führen.. ;)
  • Nachtrag:

    Quellcode

    1. - (void)addNewSchoolyear
    2. {
    3. NSManagedObjectContext *moc = ...;
    4. ALSchoolYear *newSchoolYear = [NSEntityDescription insertNewObjectForEntityForName:@"ALSchoolYear" inManagedObjectContext:moc];
    5. // newSchoolYear jetzt über die accessoren initialisieren
    6. }


    insertNewObjectForEntityForName gibt Dir die Klasse zurück, die Du im Modellingeditor angegeben hast (also Deine MO-Subklasse ALSchoolYear).
    Und bitte IMMER über die Accessoren auf Deine Properties zugreifen.

    Gruß, Markus
  • Jo, vielen Dank. Scheint so zu funktionieren, ABER: ich habe entsprechend meine anderen MO-Subklassen angepasst und da ist eine weitere Frage aufgetreten. Habe nun noch Folgendes:

    Quellcode

    1. Entity: Class (Klasse ist ALClass)
    2. entityChildren -> inverse Relationship mit u.g. memberOfClass
    3. Entity: Subject (Klasse ist ALSubject)
    4. memberOfClass -> inverse Relationship zu o.g. entityChildren


    Nach dem Erstellen einer Instanz von ALClass erstelle ich ein Subject und füge es per [newClass addEntityChildrenObject: newSubject] ein. Wenn ich mir danach newClass und newSubject per Log ansehe, dann stimmt newClass super und so, wie es sein soll (bei entityChildren steht die gleiche Speicheradresse, die newSubject hat). Bei newSubject steht allerdings: memberOfClass = nil. Da sollte doch dann entsprechend die Relationship"gefüllt" worden sein, oder?

    Ich habe die Relationship folgendermaßen im Interface stehen:

    Quellcode

    1. ALClass* memberOfClass;
    2. ...
    3. @property (nonatomic, retain) ALCLass* memberOfClass;


    Dann in der Implementierung nur per @dynamic Accessoren erzeugen lassen. Da konnte ich doch eigentlich nicht viel falsch machen?!? Ich werd noch blöde.. ;)
  • Hi Amin,

    die inverse Beziehung ist eingestellt. Was meinst Du mit "ständig eigene Accessoren anmeldest"?

    Das setEntityChildrenObject wurde mir vom Modeller "erstellt", als ich Copy Obj. Declarations to Clipboard angewählt habe. Und steht es nicht auch in Deinem Buch, dass man bei To-Many-Relationships solche Accessoren erstellen sollte? Oder habe ich da was falsch verstanden? Wie auch immer. Ich habe es nun mal andersrum versucht und das Subject statt so:

    [newClass addEntityChildrenObject: newSubject];

    so hinzugefügt:

    [newSubject setMemberOfClass: newClass].

    Funktioniert. Ist zwar einerseits schön, andererseits verstehe ich es trotzdem nicht so ganz bzw. stehe ich ja wieder vor dem gleichen Problem, wenn ich mal To-Many-Relationships in beide Richtungen habe..
  • alexlaske schrieb:

    Hi Amin,

    die inverse Beziehung ist eingestellt. Was meinst Du mit "ständig eigene Accessoren anmeldest"?

    Das setEntityChildrenObject wurde mir vom Modeller "erstellt", als ich Copy Obj. Declarations to Clipboard angewählt habe. Und steht es nicht auch in Deinem Buch, dass man bei To-Many-Relationships solche Accessoren erstellen sollte? Oder habe ich da was falsch verstanden? Wie auch immer. Ich habe es nun mal andersrum versucht und das Subject statt so:

    [newClass addEntityChildrenObject: newSubject];

    so hinzugefügt:

    [newSubject setMemberOfClass: newClass].

    Funktioniert. Ist zwar einerseits schön, andererseits verstehe ich es trotzdem nicht so ganz bzw. stehe ich ja wieder vor dem gleichen Problem, wenn ich mal To-Many-Relationships in beide Richtungen habe..


    Dort wo du das hernimmst, geht es aber nicht um Accessoren für CD. Zeig mal deinen Code.
    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"?