Factory mit NSClassFromString und isKindOfClass

  • Factory mit NSClassFromString und isKindOfClass

    Hallo,

    ich habe ein (Verständnis?)Problem mit NSClassFromString und isKindOfClass:

    Es gibt verschiedene Modellklassen: z.B. LocalVideo, YouTubeVideo, VimeoVideo, etc.. Die sind Unterklassen der Klasse Video. Nun habe ich noch eine VideoPlayer-Klasse. Diese soll eine Factory sein, die je nachdem welche Art von Video reingereicht wird eine entsprechende Instanz eines Players zurückgibt. Beispiel:

    Quellcode

    1. Video *video = [[LocalVideo alloc] init];
    2. id videoPlayer = [VideoPlayer create:localVideo]; //videoPlayer ist dann hier vom Typ LocalVideoPlayer


    In der create Methode von VideoPlayer wird die Instanz folgendermaßen instanziiert:

    Quellcode

    1. + (id)create:(Video*)aVideo {
    2. NSString *videoClassString = NSStringFromClass([aVideo class]);
    3. NSString *videoPlayerClassString = [NSString stringWithFormat:@"%@Player", videoClassString];
    4. Class videoClassPlayer = NSClassFromString(videoPlayerClassString);
    5. id concreteVideoPlayer= [[videoClassPlayer alloc] init];
    6. return concreteVideoPlayer;
    7. }


    Nun teste ich in meinen Tests, ob die Instanz, die ich von create zurückbekomme tatsächlich vom Typ (um beim Beispiel zu bleiben) LocalVideoPlayer ist:

    Quellcode

    1. - (void)testIsLocalVideoPlayer {
    2. Video *localVideo = [[LocalVideo alloc] init];
    3. VideoPlayer *videoPlayer = [VideoPlayer create:localVideo];
    4. STAssertTrue([videoPlayer isKindOfClass:[LocalVideoPlayer class]], @"videoPlayer must be of class LocalVideoPlayer");
    5. }


    isKindOfClass gibt hier immer NO zurück. Laut description Methode des videoPlayer Objekts ist videoPlayer ein LocalVideoPlayer. Ändere ich den Test folgendermaßen

    Quellcode

    1. - (void)testIsLocalVideoPlayer {
    2. Video *localVideo = [[LocalVideo alloc] init];
    3. VideoPlayer *videoPlayer = [VideoPlayer create:localVideo];
    4. STAssertTrue([videoPlayer isKindOfClass:NSClassFromString(@"LocalVideoPlayer")], @"videoPlayer must be of class LocalVideoPlayer");
    5. }


    läuft er durch. Dabei ist es egal ob ich isSubclassOf verwende, wie es in einem anderen Beitrag hier vorgeschlagen wird. isKindOfClass liefert mir immer NO. Was hab ich übersehen?
  • Hm nur kurz angesehen aber

    Quellcode

    1. Video *localVideo=[[LocalVideo alloc] init];


    liefert Dir ja eine Klasse "Video" und die gibst du an create. Also müßte der daraus doch eine "VideoPlayer" Class erzeugen und keine "LocalVidePlayer", oder ?

    Gruß

    Claus
    2 Stunden Try & Error erspart 10 Minuten Handbuchlesen.

    Pre-Kaffee-Posts sind mit Vorsicht zu geniessen :)
  • Thallius schrieb:

    Hm nur kurz angesehen aber

    Quellcode

    1. Video *localVideo=[[LocalVideo alloc] init];


    liefert Dir ja eine Klasse "Video" und die gibst du an create. Also müßte der daraus doch eine "VideoPlayer" Class erzeugen und keine "LocalVidePlayer", oder ?

    Gruß

    Claus

    das ", oder?" wäre richtig.

    Bevor Du den osigge in die falsche Richtung schickst:

    Die Klasse bestimmt sich übers konkrete Objekt - es heißt ja [objekt class] - und nicht den Typ der Variable wo die Referenz gespeichert wird...

    Wo der Fehler liegt kann ich aus dem vorliegenden Code auch nicht erkennen. Evtl. ist die Klassenhierarchie anders definiert als der Anwendungsfall vermuten läßt.
  • osigge schrieb:

    Das was ich nicht verstehe ist, warum

    Quellcode

    1. [videoPlayer isKindOfClass:NSClassFromString(@"LocalVideoPlayer")]
    YES zurückgibt aber

    Quellcode

    1. videoPlayer isKindOfClass:[LocalVideoPlayer class]]
    NO; als würde isKindOfClass auf magische Art und Weise checken wie das Objekt instanziiert wurde... ;)

    Um das zu verstehen ist es notwendig mehr Code anzuschauen. Denn normalerweise geht das.

    Probiere mal:

    Quellcode

    1. Class class=NSClassFromString(@"LocalVideoPlayer");
    2. NSLog(@"%p %@", class, class);
    3. class=[LocalVideoPlayer class];
    4. NSLog(@"%p %@", class, class);
  • osigge schrieb:

    hns schrieb:

    Um das zu verstehen ist es notwendig mehr Code anzuschauen. Denn normalerweise geht das.

    Probiere mal:

    Quellcode
    1
    2
    3
    4
    Class class=NSClassFromString(@"LocalVideoPlayer");
    NSLog(@"%p %@", class, class);
    class=[LocalVideoPlayer class];
    NSLog(@"%p %@", class, class);


    Output ist:

    Quellcode

    1. 0x801c LocalVideoPlayer
    2. 0x67b02b0 LocalVideoPlayer
    Das ist sehr seltsam. Ist Deine eigene LocalVideoPlayer-Klasse von NSObject abgeleitet?
    Probiere mal:

    Quellcode

    1. Class class=NSClassFromString(@"LocalVideoPlayer");
    2. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    3. class=[LocalVideoPlayer class];
    4. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
  • hns schrieb:

    Das ist sehr seltsam. Ist Deine eigene LocalVideoPlayer-Klasse von NSObject abgeleitet?
    Probiere mal:

    Quellcode
    1
    2
    3
    4
    Class class=NSClassFromString(@"LocalVideoPlayer");
    while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    class=[LocalVideoPlayer class];
    while(class) NSLog(@"%p %@", class, class), class=[class superclass];


    Ok, LocalVideoPlayer ist eine subclass von VideoPlayer, was nicht korrekt ist.

    Quellcode

    1. 0x801c LocalVideoPlayer
    2. 0x7f68 VideoPlayer
    3. 0x1d46f4c NSObject
    4. 0x67b02b4 LocalVideoPlayer
    5. 0x67b0160 VideoPlayer
    6. 0x1d46f4c NSObject


    Änderung auf NSObject behebt aber das Problem leider nicht:

    Quellcode

    1. 0x801c LocalVideoPlayer
    2. 0x1d46f4c NSObject
    3. 0x67b02b4 LocalVideoPlayer
    4. 0x1d46f4c NSObject


    Editiert: Komisch ist auch, dass die Adresse von LocalVideoPlayer unterschiedlich ist..

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

  • Hm. Wobei ich eines nicht verstehe. Warum gibts den VideoPlayer auch doppelt obwohl Du den gar nicht mit NSClassFromString() anlegst?

    Es deutet alles auf einen (neuen?) Bug in NSClassFromString sein...

    => Bug Report an Apple.

    Oder Deine Klassenhierarchie hat sich verdoppelt (nicht-druckendes Zeichen?).

    Work-Around: statt den Klassennamen als String zu verarbeiten kannst Du auch der Klasse Video eine Methode verpassen die die Klasse des Players liefert. Und die in allen davon abgeleiteten Unterklassen passend überschreiben:

    Quellcode

    1. @implementation LocalVideo
    2. + (Class) playerClass
    3. {
    4. return [LocalVideoPlayer class];
    5. }

    Noch eine Frage. Verwendest Du evtl. KVO? Das verändert möglicherweise den Klassenpointer, wobei isKindOfClass das aber m.E. berücksichtigen sollte.
  • hns schrieb:


    Hm. Wobei ich eines nicht verstehe. Warum gibts den VideoPlayer auch doppelt obwohl Du den gar nicht mit NSClassFromString() anlegst?

    Es deutet alles auf einen (neuen?) Bug in NSClassFromString sein...

    => Bug Report an Apple.

    Oder Deine Klassenhierarchie hat sich verdoppelt (nicht-druckendes Zeichen?).

    Work-Around: statt den Klassennamen als String zu verarbeiten kannst Du auch der Klasse Video eine Methode verpassen die die Klasse des Players liefert. Und die in allen davon abgeleiteten Unterklassen passend überschreiben:








    Quellcode




    1
    2
    3
    4
    5



    @implementation LocalVideo
    + (Class) playerClass
    {
    return [LocalVideoPlayer class];
    }





    Noch eine Frage. Verwendest Du evtl. KVO? Das verändert möglicherweise den Klassenpointer, wobei isKindOfClass das aber m.E. berücksichtigen sollte.


    Kein KVO. Ich hatte zwischenzeitlich isKindOfClass im player überschrieben... kann aber auch nicht die Lösung sein... Schätze ich verbrat mal einen von meinen "incidents" vom dev program. Mal sehen was die sagen. Vielen Dank für deine Mühen!
  • Amin Negm-Awad schrieb:

    Mal auf Nil getestet?

    Nil wäre bei meinem Code-Vorschlag schnell sichtbar geworden... Er hat ja 2 verschiedene Class-Objekte mit gleichem Namen (zumindest auf der Konsole erscheinen sie gleich).
    Ich habe das mal in einen Code (SDK 10.5 auf OSX 10.6.8 ) gepackt:

    Quellcode

    1. Class class=NSClassFromString(@"AppController");
    2. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    3. class=[AppController class];
    4. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    5. 2012-07-30 12:18:43.021 SyncTest[17555:a0f] 0x71bc AppController
    6. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0xa082c68c NSObject
    7. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0x71bc AppController
    8. 2012-07-30 12:18:43.024 SyncTest[17555:a0f] 0xa082c68c NSObject

    Da kommt das gewünschte Class-Objekt heraus.

    Ein wichtiger Hinweis ist vielleicht aber auch schon drin: Mein AppController hat eine niedrige Adresse, während NSObject eine hohe hat. Das liegt daran, dass der AppController in meinem eigenen Code liegt, während NSObject aus dem Foundation.framework stammt. Die werden vom Kernel an deutlich verschiedene Adressen geladen.

    Bei osigge haben die unterschiedlichen LocalVideoPlayers nochmal einen dritten Adressbereich!

    Kann es sein, dass die Klasse sowohl in der Applikation als auch in einem nachgeladenen Bundle/Framework definiert ist? Also zweimal existiert? Das kann versehentlich passieren, wenn man in einem Projekt mehrere Targets hat und die Sources beiden zuordnet.

    Frage an osigge: verwendest Du nachgeladene Bundles? Oder ist das in einer NIB? Oder hast Du ein eigenes Framework definiert, wo die LocalPlayers drin sind?

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von hns ()

  • hns schrieb:

    Amin Negm-Awad schrieb:

    Mal auf Nil getestet?

    Nil wäre bei meinem Code-Vorschlag schnell sichtbar geworden... Er hat ja 2 verschiedene Class-Objekte mit gleichem Namen (zumindest auf der Konsole erscheinen sie gleich).
    Ich habe das mal in einen Code (SDK 10.5 auf OSX 10.6.8 ) gepackt:

    Quellcode

    1. Class class=NSClassFromString(@"AppController");
    2. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    3. class=[AppController class];
    4. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    5. 2012-07-30 12:18:43.021 SyncTest[17555:a0f] 0x71bc AppController
    6. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0xa082c68c NSObject
    7. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0x71bc AppController
    8. 2012-07-30 12:18:43.024 SyncTest[17555:a0f] 0xa082c68c NSObject

    […]
    Kann es sein, dass die Klasse sowohl in der Applikation als auch in einem nachgeladenen Bundle/Framework definiert ist? Also zweimal existiert? Das kann versehentlich passieren, wenn man in einem Projekt mehrere Targets hat und die Sources beiden zuordnet.

    Frage an osigge: verwendest Du nachgeladene Bundles? Oder ist das in einer NIB? Oder hast Du ein eigenes Framework definiert, wo die LocalPlayers drin sind?

    Genau darauf wollte ich hinaus: Nicht alle Arten der Klassengenerierung laden ein etwaiges Bundle nach. Und dann bekommt man Nil. (Sollte man jedenfalls.) Und das kannst du eigentlich nur dann richtig testen, wenn du deinen eigenen Code untersuchst.
    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"?
  • hns schrieb:

    Amin Negm-Awad schrieb:

    Mal auf Nil getestet?

    Nil wäre bei meinem Code-Vorschlag schnell sichtbar geworden... Er hat ja 2 verschiedene Class-Objekte mit gleichem Namen (zumindest auf der Konsole erscheinen sie gleich).
    Ich habe das mal in einen Code (SDK 10.5 auf OSX 10.6.8 ) gepackt:

    Quellcode

    1. Class class=NSClassFromString(@"AppController");
    2. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    3. class=[AppController class];
    4. while(class) NSLog(@"%p %@", class, class), class=[class superclass];
    5. 2012-07-30 12:18:43.021 SyncTest[17555:a0f] 0x71bc AppController
    6. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0xa082c68c NSObject
    7. 2012-07-30 12:18:43.023 SyncTest[17555:a0f] 0x71bc AppController
    8. 2012-07-30 12:18:43.024 SyncTest[17555:a0f] 0xa082c68c NSObject

    Da kommt das gewünschte Class-Objekt heraus.

    Ein wichtiger Hinweis ist vielleicht aber auch schon drin: Mein AppController hat eine niedrige Adresse, während NSObject eine hohe hat. Das liegt daran, dass der AppController in meinem eigenen Code liegt, während NSObject aus dem Foundation.framework stammt. Die werden vom Kernel an deutlich verschiedene Adressen geladen.

    Bei osigge haben die unterschiedlichen LocalVideoPlayers nochmal einen dritten Adressbereich!

    Kann es sein, dass die Klasse sowohl in der Applikation als auch in einem nachgeladenen Bundle/Framework definiert ist? Also zweimal existiert? Das kann versehentlich passieren, wenn man in einem Projekt mehrere Targets hat und die Sources beiden zuordnet.

    Frage an osigge: verwendest Du nachgeladene Bundles? Oder ist das in einer NIB? Oder hast Du ein eigenes Framework definiert, wo die LocalPlayers drin sind?


    Ich lach mich kaputt... das wars. Im Test-Target war bei "Compile Sources" noch der LocalVideoPlayer.m drin. Dann ist es ja wohl so, dass NSClassFromString die Klasse vom "Standard"-Buildtarget nimmt und im Test dann die unter "Compile Sources". Gut zu wissen. Ergibt nun auch Sinn. Vielen Dank für eure Hilfe!
  • osigge schrieb:

    Ich lach mich kaputt... das wars. Im Test-Target war bei "Compile Sources" noch der LocalVideoPlayer.m drin. Dann ist es ja wohl so, dass NSClassFromString die Klasse vom "Standard"-Buildtarget nimmt und im Test dann die unter "Compile Sources". Gut zu wissen. Ergibt nun auch Sinn. Vielen Dank für eure Hilfe!

    Aber gerne. Irgendwo gibts einen Hinweis dass man Methoden in Kategorien in Bundles zwar mehrfach definieren darf, aber dann nicht vorhersagbar ist welche Version verwendet wird. Nur dass es auch für unbenannte Kategorien oder gar Klassen gilt ist möglicherweise neu... Bisher dachte ich dass der Linker meldet wenn man eine Klasse doppelt definiert. Vielleicht war (ist) das bei gcc so.
  • hns schrieb:

    osigge schrieb:

    Ich lach mich kaputt... das wars. Im Test-Target war bei "Compile Sources" noch der LocalVideoPlayer.m drin. Dann ist es ja wohl so, dass NSClassFromString die Klasse vom "Standard"-Buildtarget nimmt und im Test dann die unter "Compile Sources". Gut zu wissen. Ergibt nun auch Sinn. Vielen Dank für eure Hilfe!

    Aber gerne. Irgendwo gibts einen Hinweis dass man Methoden in Kategorien in Bundles zwar mehrfach definieren darf, aber dann nicht vorhersagbar ist welche Version verwendet wird. Nur dass es auch für unbenannte Kategorien oder gar Klassen gilt ist möglicherweise neu... Bisher dachte ich dass der Linker meldet wenn man eine Klasse doppelt definiert. Vielleicht war (ist) das bei gcc so.

    Der Linker weiß das nicht. Übrigens wäre es auch zulässig, da Bundles alternativ geladen werden können.
    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:

    hns schrieb:

    osigge schrieb:

    Ich lach mich kaputt... das wars. Im Test-Target war bei "Compile Sources" noch der LocalVideoPlayer.m drin. Dann ist es ja wohl so, dass NSClassFromString die Klasse vom "Standard"-Buildtarget nimmt und im Test dann die unter "Compile Sources". Gut zu wissen. Ergibt nun auch Sinn. Vielen Dank für eure Hilfe!

    Aber gerne. Irgendwo gibts einen Hinweis dass man Methoden in Kategorien in Bundles zwar mehrfach definieren darf, aber dann nicht vorhersagbar ist welche Version verwendet wird. Nur dass es auch für unbenannte Kategorien oder gar Klassen gilt ist möglicherweise neu... Bisher dachte ich dass der Linker meldet wenn man eine Klasse doppelt definiert. Vielleicht war (ist) das bei gcc so.

    Der Linker weiß das nicht. Übrigens wäre es auch zulässig, da Bundles alternativ geladen werden können.

    Weißt Du das oder vermutest du das?

    Wie erklärst Du diese Fehlermeldung:

    Quellcode

    1. ld: duplicate symbol .objc_class_name_AppController in /Users/hns/Documents/Projects/ConnectionSync/build/ConnectionSync.build/Development/SyncTest.build/Objects-normal/i386/SyncServer.o and /Users/hns/Documents/Projects/ConnectionSync/build/ConnectionSync.build/Development/SyncTest.build/Objects-normal/i386/AppController.o
    2. collect2: ld returned 1 exit status
    3. Command /Developer/usr/bin/gcc-4.2 failed with exit code 1

    Wie man sieht ist das ein (alter) gcc 4.2.

    Was aber sein kann (ich weiß es nicht, gebe ich gerne zu), ist dass sich der dynamische Linker beim Nachladen von Bundles und Frameworks anders verhält als der ld.
  • Ich weiß das:

    Der Linker erkennt (natürlich), dass du zwei gleichnamige Klassen in demselben Bundle hast. (Genauer eigentlich: Executable-Bundle im Target, denn das erstellt er ja.)
    Der Linker erkennt (natürlich) nicht, dass du zwei gleichnamige Klassen in verschiedenen Bundles hast. (Genauer eigentlich: Executable-Bundle im Target, denn die erstellt er in mehreren Durchläufen.)

    Lädst du dann beide Bundles, "kracht" es, weil das Laufzeitsystem zweimal denselben Klassennamen vorfindet.

    Dieses Verhalten ist wie gesagt richtig. Du kannst etwa zwei Bundles haben, die für verschiedene Zwecke dienen und alternativ geladen werden sollen.

    Die Fehlermeldung oben erkläre ich damit, dass es zwei gleichnamige Klassen in einem Bundle sind.
    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"?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von Amin Negm-Awad ()

  • Amin Negm-Awad schrieb:

    Ich weiß das:

    Der Linker erkennt (natürlich), dass du zwei gleichnamige Klassen in demselben Bundle hast. (Genauer eigentlich: Executable-Bundle im Target, denn das erstellt er ja.)
    Der Linker erkennt (natürlich) nicht, dass du zwei gleichnamige Klassen in verschiedenen Bundles hast. (Genauer eigentlich: Executable-Bundle im Target, denn die erstellt er in mehreren Durchläufen.)

    Lädst du dann beide Bundles, "kracht" es, weil das Laufzeitsystem zweimal denselben Klassennamen vorfindet.

    Dieses Verhalten ist wie gesagt richtig. Du kannst etwa zwei Bundles haben, die für verschiedene Zwecke dienen und alternativ geladen werden sollen.

    Die Fehlermeldung oben erkläre ich damit, dass es zwei gleichnamige Klassen in einem Bundle sind.

    So würde ich es auch beschreiben was en Detail passiert.
    Nur müsste es nicht so sein. D.h. "natürlich" ist das nicht. Sondern "konventionell". Wenn es anders wäre, hätte es das ursprüngliche Problem schneller sichtbar gemacht.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von hns ()

  • Was wäre denn die Alternative? Der Linker beschwert sich ja.

    Bei zwei Bunden hat der Linker aber keine Chance. Das zweite Bundle kann ja sonstwoher stammen. Für ihn sind das zwei Compilerläufe.

    Der Loader könnte natürlich eine Exception werfen, wenn die zweite Klasse eintrudelt. Das ist bloß ja so "entfernt vom Sourcecode", dass du im Prinzip die gesamte Applikation mit @try versehen müsstest. Das liegt einfach sehr, sehr tief.
    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"?