Merkwürdiges Verhalten beim KVO

  • Merkwürdiges Verhalten beim KVO

    (Hinweis: Eigentlich ein iPhone-Projekt)

    Ich habe gerade ein seltsames Verhalten beim KVO entdeckt, bei dem ich nicht weiß, ob/wie ich es verhindern kann.

    Es gibt zwei Instanzen, die eine "ready"-Property enthalten, auf die ich einen Observer anmelde. Das habe ich deshalb gemacht, weil die beiden Klassen/Instanzen auf externe Hardware zurückgreifen, die ihrerseits eine "ich bin fertig"-Meldung als Delegate-Methoden-Aufruf absetzen. Wenn dieser Aufruf erfolgt, setze ich die "ready"-Property auf YES und der Observer klingelt.

    In der Observer-Methode werden ein paar Werte gesetzt, z.B. das Objekt an ein Array gehangen usw., aber auch in der observierenden Instanz eine Property mit einem setter aktualisiert. Das heißt, dass der Observer an dieser Stelle verlassen wird (in den Mainloop zurückkehrt) und danach eigentlich noch einige Werte gesetzt werden sollen. Nun scheint aber parallel, während ich in der Observer-Methode meine Initialisierungen vorgenommen habe, die zweite Instanz ihre Fertigmeldung abgeliefert zu haben. Folge: Der Observer klingelt wieder und fängt für diese Klasse auch mit der Arbeit an.

    Was nun passiert, ist aber merkwürdig: Ich hätte erwartet, dass der Stack sauber runtergearbeitet wird, also v.a. der erste setter-Aufruf (s.o.) irgendwann beendet wird, in die Observer-Methode zurückkehrt und dort der Rest des ersten "Klingelns" erledigt - tut er aber nicht! Vielmehr scheint es so zu sein, dass der zweite Observer-Impakt den ersten ersatzlos überschrieben hat. Alles, was nach dem Setter-Aufruf zur ersten Instanz käme, wird nicht (nie!) mehr durchlaufen - das kann ich mit BPs nachweisen. Damit funktioniert natürlich der Spaß nicht mehr richtig, denn es fehlen einige Schritte.

    Mach ich was falsch, verstehe ich was falsch, oder ist das ein Feature?!
  • Ich rate mal, dass du dich in einer Endlosrekursion befindest. Deshalb wird der Rest nicht mehr durchgearbeitet. Deinen Bezug zur Runloop verstehe ich nicht. Observation läuft nicht durch die Runloop.
    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"?
  • Amin Negm-Awad schrieb:

    Ich rate mal, dass du dich in einer Endlosrekursion befindest. Deshalb wird der Rest nicht mehr durchgearbeitet.

    Hmm. Das hieße, dass ich in der Observer-Methode die observierte Variable verändern würde, und erneut einen Observer-Aufruf triggere. Denkbar, dagegen spricht allerdings, dass ich wirklich nur zwei Mal, nämlich für jede Klasse einmal, den Observer durchlaufe (durch BPs verifiziert).

    Amin Negm-Awad schrieb:

    Deinen Bezug zur Runloop verstehe ich nicht. Observation läuft nicht durch die Runloop.

    Ja, das war wahrscheinlich sowohl der falsche Begriff, als auch inhaltlich Käse. Ich meinte damit, dass ich mich mit dem setter-Aufruf aus der Observer-Methode und ihrem Kontext herausbewege und wieder zurück in die (keine Ahnung wie das heißt) "Hauptschleife" des Programms zurückkehre - dort wartet dann aber bereits der zweite Trigger.

    Ich kann das Problem lösen, indem ich einen anderen - und wie ich finden auch etwas eleganteren - Ansatz wähle (nicht Observieren, sondern Benachrichtigung über eine Protokollmethode). Gleichwohl ist das wieder so ein Ding, wo ich einfach nicht nachvollziehen kann, warum sich das im Ablauf so verhält und nicht so, wie ich es erwartet habe.
  • Ich habe noch ein, zwei Informationen dazu. Vermutlich mache ich einfach etwas falsch. Inzwischen habe ich einen Verdacht und frage mal ganz blöd:

    Wie kann ich eine property, die als readonly deklariert ist,
    1. KVO-complient machen (vermutlich indem ich einen eigenen setter schreibe) und
    2. die Property KVO-complient in den abgeleiteten Klassen-Instanzen ändern?

    Zu 2: Der Compiler kräht, dass er (in meinem Fall) setReady: nicht kennt, das ist aber der in der Mutter-Klasse deklarierte und implementierte und um KVO-Methoden erweiterte interne Setter.

    Ich habe mal in den "Töchtern" die setReady:-Methode in ihren .m-Files als lokale Kategorie ergänzt und zusätzliche setReadys: implementiert, die [super setReady:...] aufrufen. Nunja, so richtig klappt das nicht, es gibt aber auch keinen Fehler, aber hier stoppt die Chose. Will sagen: Die setReady:-Methode in der abgeleiteten Klassen-Instanz wird zwar betreten, aber nicht mehr verlassen - jedenfalls wird der Code nach dem Aufruf nicht mehr bearbeitet.

    Dieses Phänomen tritt sowohl dann auf, wenn ich es mit dem KVO-Observer-Weg mache, als auch, wenn ich in dem setter eine Protokoll-Methode aufrufe.

    Merchwürdich!
  • Das ist zum Verrücktwerden. So wie es aussieht, fliege ich immer bei KVO in der observer-Methode aus der Bahn, sobald ich ein [self set...]-Aufruf mache. Wobei, wohlgemerkt, dieser Aufruf eine andere Property betrifft, als die, die ich observiere.

    Nun ist das Allermerkwürdigste aber, dass diese ganzen Prozesse eigentlich ewig funktioniert haben - nur seit gestern nicht mehr! Ich glaube, ich starte mal den Rechner und das Testdevice neu und lösche die Build-Ordner. Schon wieder.
  • Hmm, ja. Im Grunde lag es nicht wirklich am KVO, wohl aber vermutlich in der Tat daran, dass ich etwas "überschrieben" habe. Ich verstehe allerdings noch nicht ganz, warum das so ist.

    Ich versuche es kurz: Grundsätzlich ist es so, dass ich von meiner ursprünglichen Klasse zwei weitere Klassen abgeleitet und instanziiert habe(1). Nun war es z. B. so, dass ich in der "Mutter"-Klassen einen Setter programmiert habe, der gleichzeitig Abhängigkeit vom gesetzten Wert einen Dienst startet oder stoppt(2). In den Tochter-Klassen habe ich nun diesen Setter ebenfalls überschrieben - und, da ich weiß, dass ich die Funktion der Mutter benötige, das ganze mit einem [super set...] angereichert. Und dieser Aufruf, der an zwei Stellen eingebaut wurde, hat das Threading irgendwie ausgehebelt. Ich konnte nachvollziehen, dass exakt mit diesem Aufruf der die Abarbeitung beendet wurde. Fehlt der Aufruf, wird sauber durchgearbeitett. Ist er drin - Ende ohne Fehler. Einfach Ende.

    Ich habe mir diese [super viewDidAppear], [super dealloc] usw. als Vorlage dieses Denkmusters genommen. Leider offenbar falsch.

    (1) Man kann sich das so vorstellen, dass die "Mutter"-Klasse die Struktur für Module, die Funktionserweiterungen bringen, mit Schnittstellen-Methoden und Protokoll vorgibt. Selbst kann diese Klasse nix (mit Ausnahme von Fn. (2)), die Funktion wird durch konkrete Klassen implementiert, die von ihr erben.
    (2) Was Käse war, denn der Setter sollte nur setten - daher habe ich das nun durch einen Observer gelöst, der nach der Änderung diese Funktion antickt.
  • fwtag schrieb:

    Threading war auch irgendwie der falsche Begriff, ich weiss. Egal. So wie es aussieht, war der Super-Aufruf im Setter das Problem und das verstehe ich halt nicht so ganz, weil mir eben doch die Bildung in dieser Richtung fehlt ...;)

    Vielleicht ergibt sich eine Erklärung daraus, wie automatisches KVO funktioniert:

    1. Es wird eine Subklasse der Instanz der zu observierenden Klasse zur Laufzeit gebaut, in der die Setter der zu observierenden Eigenschaft überschrieben werden. Also hast du etwa eine Klasse Person mit der Eigentschaft name, so wird bei Observierung von name eine Subklasse gebaut und -setName: überschrieben.

    2. In der Implementierung von -setName: (KVO-Klasse) erfolgt sinngemäß folgender Code:

    Quellcode

    1. - (void)setName:(NSString*)value
    2. {
    3. [super setName:value];
    4. [self informObserversAboutChangeOfProperty:@"name" setToValue:value];
    5. }

    Darin wird dann die Liste der Obserser durchgelaufen und entsprechende observeValueForKeyPath:…-Nachrichten versendet.

    3. Die observierete Instanz erhält nachträglich zur Laufzeit diese Klasse (isa-Swizzling).

    Das fürt dann dazu, dass bei einer Nachricht setName: an die observierte nstanz automatisch der Setter der Subklasse ausgeführt wird. Der ruft zunächst den Standardsetter aus der Basisimplementierung von dir auf und unterrichtet alle Observer über diesen Umstand.
    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"?