2013-06-17 10 views
6

Ich möchte von einer Framework-Klasse erben, die über eine Factory-Methode verfügt. Wie kann ich veranlassen, dass die Factory-Methode ein Objekt meines geerbten Klassentyps zurückgibt? Ich habe this useful article gefunden, die eine ähnliche Situation beschreiben, aber in ihrem Fall haben Sie die Kontrolle über die Oberklasse. Wie könnte ich beispielsweise eine Unterklasse von UIImage schreiben, die imageNamed: ein Objekt meines Unterklasse-Typs zurückgeben würde?Objective-C-Klassenvererbung mit Factory-Methoden

+2

vielleicht können Sie erreichen, was Sie wollen, indem Sie eine Kategorie auf UIImage schreiben? –

+0

Ja, ich denke jetzt über diese Richtung nach. Nur dass ich einige zusätzliche Klassenmitglieder (Eigenschaften) benötige und den Ärger mit objc_setAssociatedObject und Freunden vermeiden wollte. Auch weil ich es nicht elegant finde, einen solchen Workaround für ein ganz anderes Problem zu erstellen. Aber ich fürchte immer mehr, dass ich keine Wahl habe. – MrTJ

+1

werfen Sie einen Blick auf diese Kategorie [NSObject] (https://github.com/pixelflut/PixLib-OpenSource/blob/master/PixLib%20OpenSource/NSObject%2BPixLib.m) Kategorie. Die beiden Methoden 'setRuntimeProperty: name:' ​​und 'runtimeProperty:' helfen dabei, assoziierte Objekte zu setzen und darauf zuzugreifen. –

Antwort

9

Ich möchte von einer Framework-Klasse erben, die eine Factory-Methode hat. Wie kann ich veranlassen, dass die Factory-Methode ein Objekt meines geerbten Klassentyps zurückgibt?

Das ist alles, was Sie sollte zu tun haben:

@interface MONImage : UIImage 
@end 

@implementation MONImage 
@end 

Dann:

MONImage * image = [MONImage imageNamed:name]; 

Wie könnte ich, sagen wir, eine Unterklasse von UIImage schreiben, die imageNamed: würde ein Objekt meines Unterklasse-Typs zurückgeben?

+[UIImage imageNamed:] Die Implementierung schrieb Subclasser aus diesem Ansatz. Folglich müssten Sie diese Methode selbst implementieren.

Hier ist, wie man sollte eine Factory-Methode deklarieren:

+ (instancetype)imageNamed:(NSString *)pName; 

und wie man sollte sie implementieren:

+ (instancetype)imageNamed:(NSString *)pName 
{ 
    MONImage * image = [[self alloc] initWithThisDesignatedInitializer:pName]; 
         ^^^^ NOTE: self, not a concrete class 
    ...set up image... 
    return image; 
} 

aber sie haben es nicht so tun - +[UIImage imageNamed:] schrieb Subklassen aus und gibt eine UIImage zurück, wenn Sie MONImage * img = [MONImage imageNamed:pName]; schreiben. Manchmal geschieht das aus einem guten Grund. Einige Methoden sollten eine "endgültige" Semantik haben. Dies tritt häufig auf, wenn Ihre Methode mehrere Typen wie in einem Klassencluster zurückgibt. Die Sprache drückt keine "endgültigen" Methoden aus - aber eine solche Methode sollte zumindest dokumentiert werden.


So zu diesem UIImage Fall zu kommen, um:

@interface MONImage : UIImage 

+ (instancetype)imageNamed:(NSString *)pName; 

@end 

@implementation MONImage 

+ (instancetype)imageNamed:(NSString *)pName 
{ 
    UIImage * source = [UIImage imageNamed:pName]; 
    CGImageRef cgImage = source.CGImage; 
    if (cgImage) 
     return [[self alloc] initWithCGImage:cgImage]; 
    // try it another way 
    return nil; 
} 

@end 

Beachten Sie, dass UIImage s und CGImage s unveränderlich sind. Dies sollte nicht zu einer tiefen Kopie der Bilddaten führen.

+2

+1 für die Verwendung von instancetype. – Abizern

+2

bietet dies auch das Caching-Verhalten von '[UIImage imageNamed:]'? Wenn nicht, sollten Sie einen Verweis auf das Originalbild in "MONImage" speichern. Aber wirklich nette Lösung. –

+0

@ JonathanCichon würde das Lookup-Verhalten von '+ imageNamed:' beibehalten. Es würde das Bild laden und dann zwischenspeichern. Das genaue Verhalten der Caching-Implementierung ist abstrahiert, so dass meine Schlussfolgerung ist, dass es nicht garantiert ist, in dieser Hinsicht identisch zu sein. Ich nehme an, dass es beim Cachen dasselbe wäre, aber das habe ich nicht bewiesen. gute Frage. – justin

2

Für Ihr Beispiel:

  • Subclass UIImage, sagen wir, MyImage
  • Implementieren Sie die imageNamed: Methode etwas Bestimmtes zu tun, dass Sie getan werden müssen.
  • Anruf, dass Verfahren für diese Klasse: MyImage *newImage = [MyImage imageNamed:imageName];
+2

Das wird nicht wirklich helfen, da Sie 'super' nicht in Ihrer eigenen Implementierung von' imageNamed' nennen können, da dies dazu führt, dass eine Instanz vom Typ 'UIImage' zurückgegeben wird. –

+0

Wer sagt, dass er super anrufen muss? Er kann ein Objekt seiner Unterklasse innerhalb der Methode zuweisen und initialisieren und das Objekt zurückgeben. Und wie wir alle wissen, lautet die Konvention für Unterklassen, den Initialisierer ihrer Oberklasse aufzurufen, der ein Objekt vom Typ id zurückgibt. Wie auch immer, dies ist ein künstliches Beispiel, basierend auf dem, was das OP verlangt hat. Die Details sind dem Benutzer überlassen, aber das Prinzip eines Convenience-Konstruktors, das ein Objekt seiner eigenen Klasse zuweist und initialisiert, ist Sound. – Abizern

+1

ja, aber in diesem Fall ist der 'magische' Apfel in 'imageNamed:' sehr nützlich und ich denke nicht, dass er das selbst implementieren möchte. Aber Sie haben Recht, Ihre Antwort ist nicht "falsch", aber ich denke, in diesem Fall nicht sehr nützlich. –

0

Der Ansatz, dass mein Problem gelöst war eine Kategorie statt Vererbung zu verwenden (Kredite in den Kommentaren meiner Frage zu Jonathan Cichon gehen). Ich habe Associative References verwendet, um zusätzliche Daten in der Kategorie Implementierung zu deklarieren und zu speichern, wie es hier in SO viel diskutiert wird. Ich möchte die Aufmerksamkeit auf die von Jonathan vorgeschlagene NSObject category implementation lenken, die es wirklich einfach und elegant macht, jedem Objekt assoziative Referenzen hinzuzufügen.

+0

Während ich bin froh, dass Sie Ihr Problem gelöst haben - Ihre Frage war über überschreiben Bequemlichkeit Konstruktoren, die Sie dann zu einer Frage über das Hinzufügen von Speicher zu einer Klasse außerhalb einer Klassenerweiterung geändert. Es gibt also wirklich zwei Fragen und Antworten gleichzeitig. – Abizern

+0

Ich musste die ursprüngliche Framework-Klasse um zusätzliche Daten erweitern und trotzdem die ursprünglichen Convenience-Konstruktoren verwenden können. Als ich die ursprüngliche Frage gestellt habe, hatte ich keine Ahnung, wie ich das machen soll und vermutete, dass dies mit Vererbung erreicht werden kann. Sie haben Recht von dem Standpunkt aus, dass ich nicht genau angegeben habe, dass ich der Klasse zusätzliche Daten hinzufügen muss, sondern "Vererbung" geschrieben habe. Auf der anderen Seite löst Jonathans Lösung mein Problem perfekt, auch wenn ich keine Vererbung verwende. – MrTJ

+0

NB: Ich habe die ursprüngliche Frage nicht geändert und den Hinweis von assozi.refs in dieser Antwort nur als Referenz für zukünftige Leser hinzugefügt. – MrTJ