asynchroner Ablauf mit KVC

  • asynchroner Ablauf mit KVC

    Hallo zusammen,

    folgendes Szenario versuche ich möglichst effizient und im Sinne der Objective-C-Programmierung richtig umzusetzen:

    Es existieren zwei Klassen: Controller und Request. Erfolgt im View eine Eingabe empfängt der Controller diese, instanziiert ein Request-Objekt, welches grob gesagt eine NSURLConnection abfackelt. Da diese asynchron mit Delegate-Funktionen läuft, weiß ich nicht genau, wann der Controller die vom Request-Objekt empfangenen Daten verarbeiten kann.

    Ich habe das jetzt mal mit KVC probiert. Der Controller observiert einen BOOL-Wert, der von der Request-Klasse innerhalb der Delegate-Funktion -(void)connectionDidFinishLoading:(NSURLConnection*)connection gesetzt wird. Ein NSLog-Aufruf in -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context des Controllers wird auch ausgeführt. Danach erhalte ich in der Konsole allerdings die Meldung:

    <Controller: 0x12eb40>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
    Key path: requestFinished
    Observed object: <Request: 0x1886f0>
    Change: {
    kind = 1;
    new = 1;
    old = 0;
    }
    Context: 0x0

    Meine Fragen:
    - Ist das überhaupt das richtige Vorgehen?
    - Warum erscheint trotz NSLog-Ausgabe die obige Meldung?

    Vielen Dank im Voraus für jede Hilfe bzw. Anregung.
    Marco
  • Ein super hatte ich in der Tat drin. Entferne ich dies erschien erst folgende Meldung in der Konsole:

    An instance 0x1748e0 of class Request is being deallocated while key value observers are still registered with it. Observation info is being leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
    <NSKeyValueObservationInfo 0x18cc40> (
    <NSKeyValueObservance 0x18cc80: Observer: 0x12eb40, Key path: requestFinished, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x18cc20>
    )

    Das brachte mich darauf, daß ich mein Request-Objekt nicht mit

    +(Request*)requestWithURL:(NSURL*)url {
    return [[[self alloc] initWithURL:url] autorelease];
    }

    allokieren darf, sondern es ohne das autorelease tun muß.

    +(Request*)requestWithURL:(NSURL*)url {
    return [[self alloc] initWithURL:url];
    }

    Habe ich da jetzt ein Memory Leak oder paßt das so?
  • Beim Einrichten deiner Observierung gibst du am besten einen eindeutigen Context an. Da bietet sich die Adresse eines Zeigers an.

    Quellcode

    1. static const NSString *myContext = "test";


    Und beim Einrichten der Observierung übergibst du als Context einfach

    Quellcode

    1. &myContext


    (ja - mit &).

    Bei

    -observeValueForKeyPath:ofObject:change:context:

    überprüfst du dann, ob der übergebene Context gleich myContext ist.

    context == &myContext. Ist das der Fall darf super nicht aufgerufen werden. In allen anderen Fällen musst du super aufrufen.
    Die Objective-Cloud ist fertig wenn sie fertig ist. Beta heißt Beta.

    Objective-C und Cocoa Band 2: Fortgeschrittene
    Cocoa/Objective-C Seminare von [co coa:ding].
  • Meine observeValueForKeyPath-Funktion sieht derzeit folgendermaßen aus

    Quellcode

    1. -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    2. if ([keyPath isEqual:@"requestFinished"]) {
    3. NSLog(@"request finished");
    4. NSLog([[NSString alloc] initWithData:[object getRequestData] encoding:NSASCIIStringEncoding]);
    5. } else {
    6. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    7. }
    8. }


    Ich denke, daß ich mit der Überprüfung des keyPath-Wertes genauso gut weiterkomme oder täusche ich mich da?

    Was viel spannender ist für mich im Moment: Die öffentliche Methode +getRequestData des Request-Objektes soll eigentlich eine private Variable an den Controller übermitteln.

    Quellcode

    1. +(NSData*)getRequestData {
    2. return [self data];
    3. }


    Schon im XCode bekomme ich den Warnhinweis, daß ich eine Instanzvariable referenziere und die Ausführung führt zu dieser Konsolenmeldung:
    *** -[Request getRequestData]: unrecognized selector sent to instance 0x179190

    Am elegantesten wäre es ja eigentlich, wenn ich den Kontext zur Übermittlung von Daten nutzen könnte, so wie ich es in einem Tutorial mal gelesen habe. Wie bekomme ich also die vom Request-Objekt empfangenen Daten zum Controller?
  • Äh, stehe ich jetzt auf dem Schlauch?

    keyPath ist der Pfad auf die observierte Eigenschaft, nicht auf die gebundene. Es mag sein, dass das gerade bei dir funktioniert, wäre aber reiner Zufall.
    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"?
  • Ob du ein Memory-Leak hast doer nicht, kann man ja nur an der Gesamtsource sehen, wenn du – wie du es machst – die Speicherverwaltungsregeln nicht einhältst.

    Was macht denn derjenige, der das Objekt erzeugt? Wird das in einer Eigenschaft gespeichert?
    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"?
  • Original von Amin Negm-Awad
    Wieso mit &?

    Zumindest möglicherweise werden String-Konstanten vom gcc auf ein Objekt gemappt, so dass ohnehin aus dem Gleichen dasselbe wird.


    Was meinst du mit "String-Konstanten vom gcc auf ein Objekt gemappt"?

    Natürlich könnte man auch anhand des KeyPaths differenzieren.
    Die Objective-Cloud ist fertig wenn sie fertig ist. Beta heißt Beta.

    Objective-C und Cocoa Band 2: Fortgeschrittene
    Cocoa/Objective-C Seminare von [co coa:ding].
  • Hallo Amin,

    der Controller instanziiert das Request-Objekt lediglich über den Konstruktor +requestWithURL. Gespeichert wird diese Instanz nicht, da es mehrere parallel arbeitetende geben kann bzw. soll.

    Bezüglich des keyPaths steht in dieser offiziellen PDF unter Registering for Key-Value Observing Listing 2 der folgende Code:

    Quellcode

    1. - (void)observeValueForKeyPath:(NSString *)keyPath
    2. ofObject:(id)object
    3. change:(NSDictionary *)change
    4. context:(void *)context
    5. {
    6. if ([keyPath isEqual:@"openingBalance"]) {
    7. [openingBalanceInspectorField setObjectValue:
    8. [change objectForKey:NSKeyValueChangeNewKey]];
    9. }
    10. // be sure to call the super implementation
    11. // if the superclass implements it
    12. [super observeValueForKeyPath:keyPath
    13. ofObject:object
    14. change:change
    15. context:context];
    16. }
    Alles anzeigen
    Daran habe ich mich angelehnt.
  • Original von Objcler
    Original von Amin Negm-Awad
    Wieso mit &?

    Zumindest möglicherweise werden String-Konstanten vom gcc auf ein Objekt gemappt, so dass ohnehin aus dem Gleichen dasselbe wird.


    Was meinst du mit "String-Konstanten vom gcc auf ein Objekt gemappt"?

    Gegenfrage: Wieso willst du ein & verwenden (Zeiger-Zeiger), anstelle gleich den String-Zeiger zu nehmen?

    Ich hatte mir das so beantwortet, dass du damit verschiedene Stringkosntanten, die an verschiedener Stelle in der Source stehen, unterscheiden willst. Nur funktioniert das nicht, weil der gcc die ohnehin auf eine Stringinstanz setzen kann. (Macht er zuweilen auch.)

    Ich sehe daher keinen Sinn in der zusätzlichen Indirektion.

    Original von Objcler
    Natürlich könnte man auch anhand des KeyPaths differenzieren.

    Äh, ich stehe gerade offenbar wirklich auf dem Schlauch:

    Wenn ich zwei gebundene Eigenschaften habe und die jeweils mittels einer Observierung an ein- und dieselbe Eigenschaft binde, dann erhalte ich doch denselben Key-Path!?!?!?!
    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"?
  • Ich habe gelernt, dass man anhand des Contextes entscheiden soll, ob der Aufruf für einen selbst bestimmt ist oder nicht. Daher muss der Context unique sein. Strings, die dann mit isEqualToString: auf Gleichheit überprüft werden sind alles andere als unique. Daher gebe ich grundsätzlich als Context einfach nur eine Adresse im Speicher an. Würde das jeder machen (mit eigenen Adressen), dann gäbe es 0 Probleme.

    Siehe:
    lists.apple.com/archives/Cocoa-dev/2008/Aug/msg02431.html
    Die Objective-Cloud ist fertig wenn sie fertig ist. Beta heißt Beta.

    Objective-C und Cocoa Band 2: Fortgeschrittene
    Cocoa/Objective-C Seminare von [co coa:ding].
  • Der Code ist – wohl aus Gründen der Einfachheit – extrem unschön. Er geht gleich von mehreren Annahmen aus:

    - Im Account gibt es die Eigenschaft openingBalance. Das ist richtig, weil man ja $irgendwodran binden muss.

    Jetzt kommt es aber:
    Im Inspector wird af den Pfad getestet. Das bedeutet, dass der Inspector (wohl ein View), den Pfad kennen muss, unter dem der Controller, ihn registriert hat! Dies bedeutet, dass bei Überwachung einer anderen Eigenschaft (closingBalance) zwei Klassen geändert werden müssen (Binder und Gebundener) und außerdem unser Inspector sourcecodemäßig auf eine bestimmte Eigenschaft festgelegt ist.

    Genau dieses Problem nehme ich übrigens i Band II dazu, die Notwendigkeit von Bindings zu erläutern.
    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"?
  • Original von Objcler
    Ich habe gelernt, dass man anhand des Contextes entscheiden soll, ob der Aufruf für einen selbst bestimmt ist oder nicht. Daher muss der Context unique sein.

    Das ist richtig.

    Original von Objcler
    Strings, die dann mit isEqualToString: auf Gleichheit überprüft werden sind alles andere als unique.

    1. Das will niemand machen. Du sollst Pointer auf Strings vergleichen. Bisher vergleichst du Pointer auf Pointer auf Strings.

    2. Es ist ein Trugschluss, dass zwei Strings mit gleichem Inhalt in jedem Falle einen eigenen Pointer haben. Mach mal Folgends:

    Quellcode

    1. NSString* test = @"Amin";
    2. NSString* test2 = @"Amin";
    3. NSLog( @"%p %p", test, test2 );

    Egal, wie du vergleichst: Es sind immer dieselben Instanzen. Daher bringt eine weitere Indirektion nichts. Es gibt nur eine Möglichkeit, Uniques zu haben: Nimm einen Unique inhalt:

    Quellcode

    1. NSString* binding = @"com.Software #9811.Accounting #9811.Calculater Class.Result Binding;


    Vergleichen kannst du dann sicher mit einem Pointer-Vergleich.

    Du kannst dich nicht darauf verlassen, dass zwei Strings mit gleichem Inhalt unterschiedliche Adressen haben. Das gilt nur umgekehrt.

    Original von Objcler
    Daher gebe ich grundsätzlich als Context einfach nur eine Adresse im Speicher an. Würde das jeder machen (mit eigenen Adressen), dann gäbe es 0 Probleme.

    Nein, es gäbe Probleme, wenn zwei denselben String verwenden, weil der gcc das erkennen kann und daraus eine Stringinstanz mitein- und derselben Adresse macht. Daher mein obiger Beitrag.

    Wenn du eine Indirektion zusätzlich einbindest, bringt das wenig. Bei einer Optimierung kann nämlich der Comiler erkennen, dass test und test2 doppelte Lottchen sind und test2 wegoptimieren: Aus die Maus! (Mal abgesehen davon, dass derlei Seiteneffekte wie Optimierungseinstellungen nichts in der Source zu suchen haben.)



    Zu mmalc sage ich nichts.
    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"?
  • Wie mache ich es nun also richtig?

    Quellcode

    1. -(void)start:(id)sender {
    2. ...
    3. Request *request = [Request requestWithURL:[NSURL URLWithString:ilcDomain]];
    4. NSString *requestContext = [NSString stringWithString:@"requestContext"];
    5. [request addObserver:self forKeyPath:@"requestFinished" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:requestContext];
    6. ...
    7. }
    8. -(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context {
    9. if ([(NSString*)context isEqual:@"requestContext"]) {
    10. NSLog(@"request finished");
    11. NSLog([[NSString alloc] initWithData:[object getRequestData] encoding:NSASCIIStringEncoding]);
    12. } else {
    13. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    14. }
    15. }
    Alles anzeigen
  • Original von m99
    Wie mache ich es nun also richtig?[code]-(void)start:(id)sender {

    Du defineirst dir gloabel einen Unique-Identifier und übergibst den im Kontext. In deiner -observeValue:… vergleichst du auf den Unique-Identifier.

    Es dürfte in deiner Source aber nicht das Problem sein. Das war nur eine Anmerkung am Rande. Bei dir vermute ich eher ein anderes Problem.
    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"?
  • Original von Objcler
    Dann lässt man einfach die Angabe eines konkreten Strings weg...

    Quellcode

    1. static const NSString *myContext;


    Das sollte doch dann unique sein. Oder

    Und dann darf der Compiler nicht mehr optimieren? Wie sicher ist denn dein "Oder?" Mal abgehesen davon, dass du dann einen Zeiger auf eine uninitialisierte variable durch die Gegen reichst. Noch mehr Bäh geht ja kaum.

    +++

    Uninitialisierte Consts führen doch auch ohnehin zu einer Warning. (Was auch wegen des obigen Bäh richtig ist.)
    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"?
  • Also ich habe jetzt in der Controller.m folgende Konstante eingefügt:

    Quellcode

    1. #define RequestContext "requestContext"
    Diese übergebe ich als Kontext und mit dieser überprüfe ich später die Variable context auch auf Gleichheit. Funktioniert soweit.

    Das andere Problem war die Übergabe von Daten an den Controller. Mit object habe ich ja eine Referenz auf mein Request-Objekt und in der entsprechenden Klasse ist folgende Funktion definiert:

    Quellcode

    1. +(NSData*)getRequestData {
    2. return [self data];
    3. }
    Wie bereits geschrieben, zeigt mir XCode da schon eine Warnung an und die Konsole spuckt folgende Meldung aus:
    *** -[Request getRequestData]: unrecognized selector sent to instance 0x188a10

    Wo liegt mein Denkfehler?
  • Original von m99
    Also ich habe jetzt in der Controller.m folgende Konstante eingefügt:

    Quellcode

    1. #define RequestContext "requestContext"
    Diese übergebe ich als Kontext und mit dieser überprüfe ich später die Variable context auch auf Gleichheit. Funktioniert soweit.


    Äh, funktioniert, ja, … ich glaub', ich brech' ab. ;)

    Wenn du dein eigentliche Problem gelöst hast, überlegst du dir mal, was du da programmiert hast. Hinweis: Wenn ich von einer Konstante spreche, meine ich nie ein PP-Define.

    Wie du eine entsprechende Konstante definierst, siehst du in meinem anderen Beitrag.
    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"?