Gibt es stark typisierte Sammlungen in Objective-C?

140

Ich bin neu in der Mac / iPhone-Programmierung und in Objective-C. In C # und Java gibt es "Generics", Sammlungsklassen, deren Mitglieder nur vom deklarierten Typ sein können. Zum Beispiel in C #

Dictionary<int, MyCustomObject>

kann nur Schlüssel enthalten, die Ganzzahlen sind, und Werte vom Typ MyCustomObject. Gibt es einen ähnlichen Mechanismus in Objective-C?

Reich
quelle
Ich fange gerade an, etwas über ObjC selbst zu lernen. Vielleicht können Sie ObjC ++ verwenden, um das schwere Heben zu erledigen?
Toybuilder
Möglicherweise interessieren Sie sich für Antworten auf diese Frage: Gibt es eine Möglichkeit, die Eingabe in NSArray, NSMutableArray usw. zu erzwingen? . Es werden Argumente angeführt, warum dies bei Objective-C / Cocoa nicht üblich ist.
Mouviciel
2
ObjC ++ ist nicht wirklich eine Sprache ... nur eine Möglichkeit, auf ObjCs Fähigkeit zu verweisen, C ++ inline genauso zu handhaben wie C. Sie sollten dies jedoch nur tun, wenn Sie es müssen (z. B. wenn Sie es brauchen) um eine Drittanbieter-Bibliothek zu verwenden, die in C ++ geschrieben wurde).
Marc W
Ziemlich genau ein Duplikat von stackoverflow.com/questions/649483/…
Barry Wark
@ Mark W - "sollte das nicht tun" warum nicht? Ich habe ObjC ++ verwendet und es funktioniert großartig. Ich kann #import <map> und @property std :: map <int, NSString *> myDict ausführen. Ich kann die vollständige Cocoa-API verwenden UND habe stark typisierte Sammlungen. Ich sehe keinen Nachteil.
John Henckel

Antworten:

211

In Xcode 7 hat Apple "Lightweight Generics" in Objective-C eingeführt. In Objective-C generieren sie Compiler-Warnungen, wenn eine Typinkongruenz vorliegt.

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

Und im Swift-Code erzeugen sie einen Compilerfehler:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

Lightweight Generics sind für die Verwendung mit NSArray, NSDictionary und NSSet vorgesehen. Sie können sie jedoch auch Ihren eigenen Klassen hinzufügen:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

Objective-C verhält sich wie zuvor mit Compiler-Warnungen.

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

Swift ignoriert die allgemeinen Informationen jedoch vollständig. (Nicht mehr wahr in Swift 3+.)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

Abgesehen von diesen Foundation-Sammlungsklassen werden Objective-C-Lightweight-Generika von Swift ignoriert. Alle anderen Typen, die Lightweight-Generika verwenden, werden in Swift importiert, als wären sie nicht parametrisiert.

Interaktion mit Objective-C-APIs

Connor
quelle
Da ich Fragen zu Generika und Typen habe, die in Methoden zurückgegeben werden, habe ich meine Frage in einem anderen Thread gestellt, um alles klar zu halten: stackoverflow.com/questions/30828076/…
lvp
2
@ Rizzes. Ja, es wurde gerade eingeführt.
Connor
Eine Einschränkung hierbei ist, dass Swift die Typanmerkungen in Ihrer generischen ObjC-Klasse nicht vollständig ignoriert. Wenn Sie Einschränkungen angeben, zum Beispiel MyClass <Foo: id<Bar>>, wird Ihr Swift Code Werte annehmen , die Art Ihrer Einschränkung sind, die man gibt etwas zu arbeiten. Bei spezialisierten Unterklassen von MyClasswürden jedoch ihre spezialisierten Typen ignoriert (effektiv als das gleiche wie ein generisches angesehen MyClass). Siehe github.com/bgerstle/LightweightGenericsExample
Brian Gerstle
Kompiliert dies also für 10.10, 10.9 und frühere Betriebssysteme?
p0lAris
Es sollte so lange dauern, bis Sie Ihr Bereitstellungsziel festgelegt haben, um sie zu unterstützen
Connor
91

Diese Antwort ist veraltet, bleibt aber für den historischen Wert. Ab Xcode 7 ist Connors Antwort vom 8. Juni 15 genauer.


Nein, in Objective-C gibt es keine Generika, es sei denn, Sie möchten C ++ - Vorlagen in Ihren eigenen benutzerdefinierten Sammlungsklassen verwenden (von denen ich dringend abraten möchte).

Objective-C verfügt über eine dynamische Typisierung als Funktion. Dies bedeutet, dass sich die Laufzeit nicht um den Typ eines Objekts kümmert, da alle Objekte Nachrichten empfangen können. Wenn Sie einer integrierten Sammlung ein Objekt hinzufügen, werden diese nur so behandelt, als wären sie vom Typ id. Aber keine Sorge, senden Sie einfach wie gewohnt Nachrichten an diese Objekte. es wird gut funktionieren (es sei denn natürlich, eines oder mehrere der Objekte in der Sammlung antworten nicht auf die Nachricht, die Sie senden) .

Generika werden in Sprachen wie Java und C # benötigt, da es sich um starke, statisch typisierte Sprachen handelt. Völlig anderes Ballspiel als die dynamische Tippfunktion von Objective-C.

Marc W.
quelle
88
Ich bin nicht einverstanden mit "Mach dir keine Sorgen, sende einfach Nachrichten an diese Objekte". Wenn Sie den falschen Objekttyp in die Sammlung aufnehmen, die nicht auf diese Nachrichten reagieren, führt dies zu Laufzeitfehlern. Durch die Verwendung von Generika in anderen Sprachen wird dieses Problem bei der Überprüfung der Kompilierungszeit vermieden.
Henning77
8
@ henning77 Ja, aber Objective-C ist eine dynamischere Sprache als diese Sprachen. Wenn Sie eine starke Typensicherheit wünschen, verwenden Sie diese Sprachen.
Raffi Khatchadourian
36
Ich bin auch nicht einverstanden mit der Philosophie, sich keine Sorgen zu machen - zum Beispiel, wenn Sie den ersten Gegenstand aus einem NSArray ziehen und ihn in eine NS-Nummer umwandeln, aber dieser Gegenstand wirklich ein NSString war, sind Sie geschraubt ...
jjxtra
13
@ RaffiKhatchadourian - nicht viel Auswahl, wenn Sie eine iOS-App schreiben. Wenn es einfach wäre, eine mit Java zu schreiben und alle Vorteile einer nativen App zu nutzen, glauben Sie mir: Ich würde.
Ericsoco
11
Die größte Beschwerde, die ich darüber habe, betrifft nicht die Überprüfung dynamischer Sprachen im Vergleich zur Kompilierungszeit, sondern die einfache Kommunikation mit Entwicklern. Ich kann mir nicht einfach eine Eigenschaftsdeklaration ansehen und wissen, welche Art von Objekten sie zurückgeben wird, es sei denn, sie ist irgendwo dokumentiert.
Devios1
11

Nein, aber um es klarer zu machen, können Sie es mit dem Objekttyp kommentieren, den Sie speichern möchten. Ich habe dies einige Male gesehen, wenn Sie heutzutage etwas in Java 1.4 schreiben müssen.

NSMutableArray* /*<TypeA>*/ arrayName = ....

oder

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...
Mark Rhodes
quelle
Ich denke, dies ist ein guter Weg, um es dokumentieren zu lassen, falls jemand anderes Ihren Code liest. Auf jeden Fall sollte der Name der Variablen so klar wie möglich sein, um zu wissen, welche Objekte sie enthält.
Htafoya
6

In Objective-C gibt es keine Generika.

Aus den Dokumenten

Arrays sind geordnete Sammlungen von Objekten. Cocoa bietet mehrere Array-Klassen: NSArray, NSMutableArray (eine Unterklasse von NSArray) und NSPointerArray.

Matthew Vines
quelle
Der Link zum Dokument in der Antwort ist tot - "Diese Seite wurde leider nicht gefunden" .
Pang
5

Dies wurde in Xcode 7 veröffentlicht (endlich!)

Beachten Sie, dass es sich bei Objective C-Code nur um eine Überprüfung zur Kompilierungszeit handelt. Es tritt kein Laufzeitfehler auf, wenn nur der falsche Typ in eine Sammlung eingefügt oder einer typisierten Eigenschaft zugewiesen wird.

Erklären:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

Verwenden:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

Sei vorsichtig mit diesen *s.

Kevin
quelle
4

Generische NSArrays können durch Unterklassen NSArrayund Neudefinition aller bereitgestellten Methoden mit restriktiveren Methoden realisiert werden. Beispielsweise,

- (id)objectAtIndex:(NSUInteger)index

müsste neu definiert werden in

@interface NSStringArray : NSArray

wie

- (NSString *)objectAtIndex:(NSUInteger)index

für ein NSArray, das nur NSStrings enthält.

Die erstellte Unterklasse kann als Drop-In-Ersatz verwendet werden und bietet viele nützliche Funktionen: Compiler-Warnungen, Eigenschaftszugriff, bessere Codeerstellung und -vervollständigung in Xcode. All dies sind Funktionen zur Kompilierungszeit. Die eigentliche Implementierung muss nicht neu definiert werden. Die Methoden von NSArray können weiterhin verwendet werden.

Es ist möglich, dies zu automatisieren und auf nur zwei Anweisungen zu reduzieren, wodurch es Sprachen nahe kommt, die Generika unterstützen. Ich habe mit WMGenericCollection eine Automatisierung erstellt , bei der Vorlagen als C-Präprozessor-Makros bereitgestellt werden.

Nach dem Importieren der Header-Datei, die das Makro enthält, können Sie ein generisches NSArray mit zwei Anweisungen erstellen: eine für die Schnittstelle und eine für die Implementierung. Sie müssen nur den Datentyp, den Sie speichern möchten, und die Namen für Ihre Unterklassen angeben. WMGenericCollection stellt solche Vorlagen für NSArray, NSDictionaryund NSSet, sowie deren wandelbar Pendants.

Ein Beispiel: List<int>könnte durch eine benutzerdefinierte Klasse namens aufgerufen werden NumberArray, die mit der folgenden Anweisung erstellt wird:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

Sobald Sie erstellt haben NumberArray, können Sie es überall in Ihrem Projekt verwenden. Es fehlt die Syntax von <int>, aber Sie können Ihr eigenes Namensschema auswählen, um diese als Klassen als Vorlagen zu kennzeichnen.

wm
quelle
Beachten Sie, dass das gleiche in CoreLib existiert: github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105
user1259710
2

Jetzt werden Träume wahr - es gibt Generika in Objective-C seit heute (danke, WWDC). Es ist kein Witz - auf der offiziellen Seite von Swift:

Mit den neuen Syntaxfunktionen können Sie ausdrucksstärkeren Code schreiben und gleichzeitig die Konsistenz in der gesamten Sprache verbessern. Die SDKs haben neue Objective-C-Funktionen wie Generika und Annotationen zur Nullbarkeit verwendet, um Swift-Code noch sauberer und sicherer zu machen. Hier ist nur eine Auswahl von Swift 2.0-Verbesserungen.

Und ein Bild, das dies beweist:Objective-C-Generika

htzfun
quelle
2

Ich will nur hier rein springen. Ich habe hier einen Blog-Beitrag über Generika geschrieben.

Ich möchte dazu beitragen, dass Generika zu jeder Klasse hinzugefügt werden können , nicht nur zu den Sammlungsklassen, wie Apple angibt.

Ich habe dann erfolgreich zu einer Vielzahl von Klassen hinzugefügt, da diese genauso funktionieren wie Apples Sammlungen. dh. Überprüfung der Kompilierungszeit, Vervollständigung des Codes, Ermöglichen des Entfernens von Abgüssen usw.

Genießen.

drekka
quelle
-2

Die von Apple- und GNUStep-Frameworks bereitgestellten Collections-Klassen sind insofern semi-generisch, als sie davon ausgehen, dass sie Objekte erhalten, von denen einige sortierbar sind und andere auf bestimmte Nachrichten reagieren. Für Grundelemente wie Floats, Ints usw. ist die gesamte C-Array-Struktur intakt und kann verwendet werden. Für sie gibt es spezielle Wrapper-Objekte zur Verwendung in den allgemeinen Auflistungsklassen (z. B. NSNumber). Darüber hinaus kann eine Collection-Klasse in Unterklassen unterteilt (oder speziell über Kategorien geändert) werden, um Objekte eines beliebigen Typs zu akzeptieren. Sie müssen jedoch den gesamten Code für die Typverarbeitung selbst schreiben. Nachrichten können an jedes Objekt gesendet werden, sollten jedoch null zurückgeben, wenn dies für das Objekt ungeeignet ist, oder die Nachricht sollte an ein geeignetes Objekt weitergeleitet werden. Echte Typfehler sollten zur Kompilierungszeit und nicht zur Laufzeit abgefangen werden. Zur Laufzeit sollten sie behandelt oder ignoriert werden. Schließlich bietet Objc Funktionen zur Laufzeitreflexion, um schwierige Fälle zu behandeln, und die Nachrichtenantwort, der bestimmte Typ und die Dienste können für ein Objekt überprüft werden, bevor eine Nachricht gesendet oder in eine unangemessene Sammlung gestellt wird. Beachten Sie, dass unterschiedliche Bibliotheken und Frameworks unterschiedliche Konventionen hinsichtlich des Verhaltens ihrer Objekte beim Senden von Nachrichten anwenden, für die sie keine Code-Antworten haben, daher RTFM. Abgesehen von Spielzeugprogrammen und Debugging-Builds sollten die meisten Programme nicht abstürzen müssen, es sei denn, sie vermasseln es wirklich und versuchen, fehlerhafte Daten in den Speicher oder auf die Festplatte zu schreiben, illegale Operationen auszuführen (z. B. durch Null teilen, aber Sie können das auch abfangen) oder darauf zuzugreifen sperrt Systemressourcen. Die Dynamik und Laufzeit von Objective-C ermöglicht es, dass Dinge ordnungsgemäß fehlschlagen und in Ihren Code integriert werden sollten. (TIPP) Wenn Sie Probleme mit der Großzügigkeit Ihrer Funktionen haben, versuchen Sie etwas Spezifität. Schreiben Sie die Funktionen mit bestimmten Typen auf und lassen Sie die Laufzeit zur Laufzeit die entsprechende Elementfunktion auswählen (deshalb werden sie Selektoren genannt!).

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
Chris Reid
quelle