NSFetchResultController auf nil prüfen

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • NSFetchResultController auf nil prüfen

    Hallo,

    ich bin gerade dabei raus zu finden, wie ich sauber einen FRC auf ein leeres NSArray prüfen kann. Ich habe in meiner App das Problem, dass ich einen FRC beim App start bekomme, in dem kein Inhalt ist. Bei folgernder Stelle stürzt ist app ab, da das NSArray im FRC leer ist und ich auf einen Objekt zugreife den es natürlich nicht gibt.

    Quellcode

    1. ​[[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];


    Ich prüfen zur Zeit so das ab, was aber nicht wirklich sauber ist, da ich hier einen Integer Wert auf Nil prüfe.

    Quellcode

    1. ​self.fetchedResultsController.sections.count == nil
    Vielen Dank

    Gruß

    Bongartz
  • Ja das stimmt, so habe ich es ja auch versucht, nur dummerweise gibt:

    Quellcode

    1. ​self.fetchedResultsController.sections.count == nil


    nicht 0 zurück, sondern <nil>. Da ich jetzt nicht davon ausgeehe, dass ein <nil> == 0 ist, habe ich es so versucht zu lösen.
    Vielen Dank

    Gruß

    Bongartz
  • Bongartz schrieb:

    Ja das stimmt, so habe ich es ja auch versucht, nur dummerweise gibt:

    Quellcode

    1. ​self.fetchedResultsController.sections.count == nil


    nicht 0 zurück, sondern <nil>. Da ich jetzt nicht davon ausgeehe, dass ein <nil> == 0 ist, habe ich es so versucht zu lösen.
    Doch, es wird 0 geliefert nicht nil.

    In deinem Key-Path können nils vorkommen. Dies bedeutet, dass alle weiteren Rückgabewerte, die auf id typisiert sind, ebenfalls nil sind und alle weiteren Rückgabewerte, die auf int et al. typisiert sind 0 sind. Dies ist ein dokumentiertes Verhalten:

    Note: If you expect a return value from a message sent to nil, the return value will be nil for object return types, 0 for numeric types, and NOfor BOOL types. Returned structures have all members initialized to zero.

    developer.apple.com/library/io…s/WorkingwithObjects.html

    Also, in deinem Beispiel ist etwa das fetchedResultsController nil:

    self.fetchedResultsController.sections.count

    Dann liefert .sections nil, weil es eine Objektreferenz als Rückgabewert einer Message-to-nil ist:

    self.fetchedResultsController.sections.count
    nil.count

    count liefert einen numerischen Wert, der dann garantiert 0 ist:

    nil.count
    0

    Deshalb ist es richtig, auf 0 zu überprüfen.

    Aber, und damit das Allerwichtigste:

    Die Probleme bei der Entwicklung deiner App liegen tatsächlich darin, ob nil mal was anderes als 0 ist? Herzlichen Glückwunsch, deine Probleme hätte ich gerne.
    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"?
  • Dumme Frage: wenn bei diesem Keypath:

    self.fetchResultsController.sections.count

    fetchResultsController bereits nil ist, woher weiß dann Cocoa das "count" ein Integer liefert? Ich hätte vermutet, dass beim ersten "Nil" im Keypath einfach nil zurückgegeben wird. Und das auf dem Stack lässt sich eben auch als Integer 0 interpretieren.

    ciao

    gandhi
  • Michael schrieb:

    Nachricht count an nil. Rückgabe: 0.


    Genau, das ist die Frage. Warum hier 0? Niemand weiß an der Stelle, das count ein Integer zurückliefen würde, da vorher einmal ein Nil zurückkommt. Klar, funktionieren tut es trotzdem, weil "nil" eben so definiert ist, das es auch bei einem Integer ein 0 ergibt.

    Es würde mich wundern, wenn die Evaluierung von Keypaths nach dem ersten "nil" noch weitergeht. Warum auch? Es wird dann "nil" zurückgegeben und gut ist.

    ciao

    gandhi
  • gandhi schrieb:

    Genau, das ist die Frage. Warum hier 0? Niemand weiß an der Stelle, das count ein Integer zurückliefen würde

    Die Objective-C Runtime findet das raus.

    gandhi schrieb:

    Es würde mich wundern, wenn die Evaluierung von Keypaths nach dem ersten "nil" noch weitergeht.

    Das ist ja auch kein Key Path, sondern eine Folge von Methodenaufrufen in Punktnotation geschrieben:

    self.fetchResultsController.sections.count
    ist identisch mit
    [[[self fetchResultsController] sections] count];

    Der Compiler erzeugt jetzt schlicht Code, der die oben genannten Methodenaufrufe durchführt. Der Programmablauf ändert sich nicht abhängig von den Rückgabewerten. Das mag in Swift beim Optional Chaining anders sein, aber wir sprechen hier ja über Objective-C.

    Ein Key Path ist ein String: @"self.fetchResultsController.sections.count"
  • Spannend ist es trotzdem.

    Ich weiß ja, was ich erwarte…

    Quellcode

    1. NSFetchResultsController * controller = [self fetchResutlsController]; // Soll ein Objekt liefern, also vermutlich nil.
    2. NSArray * sections = [controller sections]; // Soll ein Objekt liefern, also vermutlich nil.
    3. NSInteger count = [sections count]; // Soll einen Integer liefern, also vermutlich 0.


    Auch wenn ich den Code entsprechend 'anonymisiere', weiß ich ungefähr, was ich erwarte.

    Quellcode

    1. id controller = [self fetchResutlsController]; // Soll ein Objekt liefern, also vermutlich nil.
    2. id sections = [controller sections]; // Soll ein Objekt liefern, also vermutlich nil.
    3. NSInteger count = [sections count]; // Soll einen Integer liefern, also vermutlich 0.


    Spannend wird bei der Verkettung von Aufrufen aber die Frage, woher dann die Laufzeit solche Dinge wissen kann.

    Quellcode

    1. if ( [[[self fetchResutlsController] sections] count] != nil ) {…}


    Ein kurzer Test hat gezeigt: Compiler und implizite Casts sind die Schlüsselworte.
    Offenbar wird für jedwede Methode im Framework angenommen, dass es Rückgabeparameter gibt und auf diese Rückgabeparameter wird sich bezogen.

    Quellcode

    1. id noArray = nil;
    2. id result = [noArray count];
    3. // Warning: Incompatible integer to pointer conversion initializing '__strong id' with an expression of type 'NSUInteger' (aka 'unsigned long')
    4. // Error: Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'id' is disallowed with ARC

    Diese Info, dass count einen NSUInteger zurück gibt, kann der Compiler nicht aus meinem Code haben. Er trifft also augenscheinlich die Annahme, dass ich ein count von einer Collection verwende.

    Richtig spannend wird das Ganze dann, wenn man den Compiler vollständig umgeht:

    Quellcode

    1. id noArray = nil;
    2. id result = [noArray performSelector:@selector( someInteger )];
    3. if( result == 0 && result == nil && result == Nil && result == NO && result == false ) {
    4. NSLog(@"It's all true!");
    5. NSLog(@"Some nils: %@, %d, %f", nil, nil, nil);
    6. }
    7. }

    Resultat:
    It's all true!
    Some nils: (null), 0, 0.000000


    Es ist also im Prinzip völlig egal worauf man prüft, nil wird implizit auf den gewünschten Datentypen gecastet.
    Es ist der Lesbarkeit zuträglich auf den Datentypen zu prüfen, den man auch verwendet.


    Fun Fact:

    Quellcode

    1. id noArray = nil;
    2. id result = [noArray performSelector:@selector( someInteger )];
    3. if( [result isEqualTo:nil] ) {
    4. NSLog(@"It's all true!");
    5. NSLog(@"Some nils: %@, %d, %f", nil, nil, nil);
    6. }
    7. else {
    8. NSLog(@"%@ (%X) and %@ (%X) differ.", result, result, nil, nil);
    9. }
    10. }
    Alles anzeigen

    (null) (0) and (null) (0) differ.


    Übersetzt heißt die Abfrage schließlich: if( NO ) {…} ;)
    «Applejack» "Don't you use your fancy mathematics to muddle the issue!"

    Iä-86! Iä-64! Awavauatsh fthagn!

    kmr schrieb:

    Ach, Du bist auch so ein leichtgläubiger Zeitgenosse, der alles glaubt, was irgendwelche Typen vor sich hin brabbeln. :-P
  • Ich glaube nicht, dass das die Runtime herausfinden kann. Die sieht schließlich nur den nil.

    Der Compiler hat das sehr wohl aus den Sourcen. Es gibt zwei Alternativen:

    a) Wie im Fall des OP ist nicht auf id typisiert. Dann weiß er jedenfalls, was gemeint sein soll und kann das -count einer Klasse zuordnen.

    b) Du machst es komplizierter für den Compiler, indem du id typisierst. Aber dein Gedanke, dass er irgendeine Annahme mit Collecitons zaubert ist falsch. Er trifft genau eine Annahme: Wenn alle -count, die ich kenne, einen bestimmten Typen liefern, dann nehme ich diesen an. Man kann das daher ziemlich schnell kaputt machen:

    Quellcode

    1. @interface Test : NSObject
    2. @property (nonatomic, getter=isTest) BOOL test;
    3. - (float)count;
    4. @end
    5. @implementation Test
    6. - (float)count
    7. {
    8. return 1.0;
    9. }
    10. @end
    11. int main(int argc, const char * argv[]) {
    12. @autoreleasepool {
    13. id object = [Test new];
    14. NSUInteger c = [object count];
    15. }
    16. return 0;
    17. }
    Alles anzeigen

    Und schon hat man eine Fehlermeldung.

    c) Dass in deinem Beispiel ohnehin nichts kaputt zu machen geht, liegt einfach daran, dass nil und ein ganzzahliges 0 binärgleich sind. Da wird gar nichts gecastet. Das ist reines Glück.
    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"?
  • Mal ein Beispiel, um es kaputt zu bekommen:

    Quellcode

    1. @interface Test : NSObject
    2. - (Test*)method;
    3. @end
    4. @implementation Test
    5. - (Test*)method
    6. {
    7. return nil;
    8. }
    9. @end
    10. typedef struct { double a; double b; } TestStruct;
    11. @interface Test2 : NSObject
    12. - (TestStruct)method;
    13. @end
    14. @implementation Test2
    15. - (TestStruct)method
    16. {
    17. return (TestStruct){ 0.0, 0.0 };
    18. }
    19. @end
    20. int main(int argc, const char * argv[]) {
    21. @autoreleasepool
    22. {
    23. id test = [Test new];
    24. TestStruct testStruct = { 0.1, 0.2 };
    25. testStruct = [(Test2*)test method];
    26. }
    27. return 0;
    28. }
    Alles anzeigen


    Quellcode

    1. (lldb) p testStruct
    2. (TestStruct) $0 = (a = 0, b = NaN)
    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"?