2010-06-04 6 views
65

Ich muss eine benutzerdefinierte Setter-Methode für ein Feld schreiben (wir nennen es foo) in meiner Unterklasse . foo ist im Datenmodell definiert und Xcode hat automatisch die Felder und @dynamic in den .h- und .m-Dateien erstellt.Benutzerdefinierte Setter-Methoden in Core-Daten

Wenn ich meine Setter wie folgt schreiben:

- (void)setFoo: (NSObject *)inFoo { 
    [super setFoo: inFoo]; 
    [self updateStuff]; 
} 

dann erhalte ich eine Compiler-Warnung auf den Anruf zu super.

Alternativ kann, wenn ich dies tun:

- (void)setFoo: (NSObject *)inFoo { 
    [super setValue: inFoo forKey: inFoo]; 
    [self updateStuff]; 
} 

dann beende ich in einer Endlosschleife auf.

Also, was ist der richtige Ansatz zum Schreiben eines benutzerdefinierten Setter für eine Unterklasse von NSManagedObject?

Antwort

1

Hier ist der Apple Weg zum Überschreiben von NSManagedObject Eigenschaften (ohne KVO zu brechen), in Ihrem.m:

@interface Transaction (DynamicAccessors) 
- (void)managedObjectOriginal_setDate:(NSDate *)date; 
@end 

@implementation Transaction 
@dynamic date; 

- (void)setDate:(NSDate *)date 
{ 
    // invoke the dynamic implementation of setDate (calls the willChange/didChange for you) 
    [self managedObjectOriginal_setDate:(NSString *)date; 

    // your custom code 
} 

managedObjectOriginal_propertyName ist eine eingebaute in Magie Methode Sie müssen nur die Definition für hinzufügen. Wie am Ende dieser Seite zu sehen What's New in Core Data in macOS 10.12, iOS 10.0, tvOS 10.0, and watchOS 3.0

+0

Guter Platz @malhal - Ich war mir dieser Änderung in iOS 10 nicht bewusst. –

100

Nach the documentation, würde es sein:

- (void) setFoo:(NSObject *)inFoo { 
    [self willChangeValueForKey:@"foo"]; 
    [self setPrimitiveValue:inFoo forKey:@"foo"]; 
    [self didChangeValueForKey:@"foo"]; 
} 

Dies ist natürlich, ignoriert die Tatsache, dass NSManagedObjects wollen nur NSNumbers, NSDates, NSDatas und NSStrings als Attribute.

Dies ist jedoch möglicherweise nicht der beste Ansatz. Da Sie wollen, dass etwas passiert, wenn der Wert Ihrer foo Eigenschaft sich ändert, warum beobachten Sie es nicht einfach mit Key Value Observing? In diesem Fall klingt es wie "KVO ist der Weg zu gehen".

+0

Dank Dave. Entschuldigung, das Feld ist eigentlich als 'NSNumber *' definiert, aber ich habe versucht, das Problem zu verallgemeinern. Ich habe versucht, was Sie oben vorgeschlagen, aber ich bekomme eine Compiler-Warnung, dass meine Klasse nicht auf '-setPrimitivePositionX:' reagieren kann. Irgendwelche Ideen? Gute Idee re. KVO. Wo wäre der beste Ort für die Registrierung? In '- (void) watchFromInsert'? Ich würde mich in '- (void) dealloc' abmelden, richtig? –

+0

OK, ich habe einen privaten '@ interface'-Abschnitt in der .m-Datei hinzugefügt und das hat die Warnung behoben, aber die Codes verhalten sich immer noch nicht wie erwartet. Ich muss das debuggen! –

+0

Bei weiteren Untersuchungen wird der Setter korrekt aufgerufen, wenn ich den Wert explizit auf das Objekt setze, aber er wird nicht aufgerufen, wenn ich NSUndoManager zum Zurücksetzen der Änderung verwende. In diesem Fall rate ich KVO ist eine bessere Allround-Ansatz. –

17

Ich denke, es ist ein kleiner Fehler: Verwendung

[self setPrimitiveValue:inFoo forKey:@"foo"]; 

statt

[self setPrimitiveFoo:inFoo]; 

das für mich arbeitet.

+0

Danke Martin. Wie du sagst, KVO ist der Weg zu gehen (ich registriere in '- (void) watchFromFetch' und Aufhebung der Registrierung in' - (void) dealloc' und ich habe dies jetzt implementiert und es funktioniert mit Rückgängig. –

+7

nicht verwenden - (void) dealloc, um die Registrierung aufzuheben, die Beobachtung in - (void) willTurnIntoFault abzubrechen, andernfalls erhalten Sie unnötige Benachrichtigungen, wenn ein Objekt in einen Fehler umgewandelt wird Neue Objekte, die eingefügt werden, erhalten keine - void wakeFromFetch-Nachricht. benutze auch - (void) awakeFromInsert –

+1

@Andrew Ebling, bitte beantworte deine eigene Frage und füge den Quellcode deiner Lösung ein. (Du kannst Variablennamen ändern, aber behalte es gut.) Ich arbeite daran Genau das mache ich, indem ich den Link auf KVC lese, aber die Lösung wäre sehr hilfreich! :) – ma11hew28

19

Hier ist, wie ich KVO auf dem id Attribut einer Photo : NSManagedObject. Wenn sich die ID des Fotos ändert, laden Sie das neue Foto herunter.

#pragma mark NSManagedObject 

- (void)awakeFromInsert { 
    [self observePhotoId]; 
} 

- (void)awakeFromFetch { 
    [self observePhotoId]; 
} 

- (void)observePhotoId { 
    [self addObserver:self forKeyPath:@"id" 
       options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL]; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change 
         context:(void *)context { 
    if ([keyPath isEqualToString:@"id"]) { 
     NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey]; 
     NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];   
     if (![newValue isEqualToString:oldValue]) { 
      [self handleIdChange]; 
     } 
    } 
} 

- (void)willTurnIntoFault { 
    [self removeObserver:self forKeyPath:@"id"]; 
} 

#pragma mark Photo 

- (void)handleIdChange { 
    // Implemented by subclasses, but defined here to hide warnings. 
    // [self download]; // example implementation 
} 
+0

Wenn ein Objekt gelöscht wird, wird der Kontext gespeichert (Objektaktualisierung) Ally deallocated), rückgängig gemacht aufgerufen, die Beobachtung wird fehlen. In 10.6+ können Sie das Beobachten auch in watchFromSnapshotEvents einrichten. Um Abwärtskompatibilität zu gewährleisten, sehen Sie sich https://github.com/mbrugger/CoreDataDependentProperties an. Es löst genau diese Probleme. –

+2

In den Apple-Dokumenten sollten Sie super "watchFromFetch" und "watchFromInsert" aufrufen. – Fervus

+0

Der Test [newValue isEqualToString: oldValue] ist nicht erforderlich, da die Benachrichtigung nur ausgelöst wird, wenn sie nicht identisch sind. –

0

Hier ist, wie Sie es tun 1-n (und ich nehme an n-m) Beziehungen:

Nehmen wir an, die Beziehung Namen "Schüler" in einem Objekt namens "Schule" genannt wird.

Zuerst müssen Sie die primitiven Accessor-Methoden für NSMutableSet definieren. Xcode wird diese nicht automatisch für Sie generieren.

@interface School(PrimitiveAccessors) 
- (NSMutableSet *)primitiveStudents; 
@end 

Als nächstes können Sie Ihre Accessor-Methode definieren. Hier werde ich den Setter übersteuern.

- (void)addStudentsObject:(Student *)student 
{ 
    NSSet *changedObjects = [[NSSet alloc] initWithObjects:&student count:1]; 

    [self willChangeValueForKey:@"students" 
       withSetMutation:NSKeyValueUnionSetMutation 
       usingObjects:changedObjects]; 

    [[self primitiveStudents] addObject:value]; 

    [self didChangeValueForKey:@"students" 
      withSetMutation:NSKeyValueUnionSetMutation 
       usingObjects:changedObjects]; 
} 
Verwandte Themen