Gibt es eine Möglichkeit, NSMutableArray zu zwingen, nur einen bestimmten Objekttyp zu halten?
Ich habe Klassendefinitionen wie folgt:
@interface Wheel:NSObject
{
int size;
float diameter;
}
@end
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray *wheels;
}
@end
Wie kann ich das Radarray zwingen , Radobjekte nur mit Code zu halten ? (und absolut keine anderen Objekte)
objective-c
cocoa
nsmutablearray
Tuyen Nguyen
quelle
quelle
Antworten:
Update im Jahr 2015
Diese Antwort wurde erstmals Anfang 2011 geschrieben und begann:
Im Jahr 2015 hat Apple dies offenbar mit der Einführung von "Lightweight Generics" in Objective-C geändert. Jetzt können Sie Folgendes deklarieren:
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
Aber alles ist nicht ganz so, wie es scheint. Beachten Sie das "Leichtgewicht" ... Dann beachten Sie, dass der Initialisierungsteil der obigen Deklaration keine generische Notation enthält. Während Apple parametrische Sammlungen eingeführt und dem obigen Array eine Nicht-Zeichenfolge direkt hinzugefügt hat
onlyStrings
, wie in:[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
wird die Warnung wie angegeben unzulässig, die Typensicherheit ist kaum hauttief. Betrachten Sie die Methode:
- (void) push:(id)obj onto:(NSMutableArray *)array { [array addObject:obj]; }
und das Codefragment in einer anderen Methode derselben Klasse:
NSMutableArray<NSString *> *oops = [NSMutableArray new]; [self push:@"asda" onto:oops]; // add a string, fine [self push:@42 onto:oops]; // add a number, no warnings...
Was Apple implementiert hat, ist im Wesentlichen ein Hinweissystem zur Unterstützung der automatischen Interaktion mit Swift, das eine Art typsicherer Generika aufweist. Auf der Objective-C-Seite ist das System zwar "leichtgewichtig", während der Compiler einige zusätzliche Hinweise liefert, und die Typintegrität liegt letztendlich immer noch beim Programmierer - ebenso wie die Objective-C-Methode.
Also was solltest du verwenden? Die neuen Lightweight- / Pseudo-Generika oder eigene Muster für Ihren Code? Es gibt wirklich keine richtige Antwort. Finden Sie heraus, was in Ihrem Szenario Sinn macht, und verwenden Sie es.
Zum Beispiel: Wenn Sie auf eine Interaktion mit Swift abzielen, sollten Sie die leichten Generika verwenden! Wenn jedoch die Typintegrität einer Sammlung in Ihrem Szenario wichtig ist, können Sie die Lightweight-Generika mit Ihrem eigenen Code auf der Objective-C-Seite kombinieren, wodurch die Typintegrität erzwungen wird, die Swift auf seiner Seite hat.
Der Rest der Antwort von 2011
Als weitere Option finden Sie hier eine kurze allgemeine Unterklasse von NSMutableArray, die Sie mit der Art von Objekt initiieren, die Sie in Ihrem monomorphen Array haben möchten. Mit dieser Option können Sie keine statische Typprüfung durchführen (so viel Sie jemals in Obj-C erhalten haben). Sie erhalten Laufzeitausnahmen beim Einfügen des falschen Typs, genauso wie Sie Laufzeitausnahmen für Index außerhalb der Grenzen usw. erhalten.
Dies wird nicht gründlich getestet und setzt voraus, dass die Dokumentation zum Überschreiben von NSMutableArray korrekt ist ...
@interface MonomorphicArray : NSMutableArray { Class elementClass; NSMutableArray *realArray; } - (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems; - (id) initWithClass:(Class)element; @end
Und die Umsetzung:
@implementation MonomorphicArray - (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems { elementClass = element; realArray = [NSMutableArray arrayWithCapacity:numItems]; return self; } - (id) initWithClass:(Class)element { elementClass = element; realArray = [NSMutableArray new]; return self; } // override primitive NSMutableArray methods and enforce monomorphism - (void) insertObject:(id)anObject atIndex:(NSUInteger)index { if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match { [realArray insertObject:anObject atIndex:index]; } else { NSException* myException = [NSException exceptionWithName:@"InvalidAddObject" reason:@"Added object has wrong type" userInfo:nil]; @throw myException; } } - (void) removeObjectAtIndex:(NSUInteger)index { [realArray removeObjectAtIndex:index]; } // override primitive NSArray methods - (NSUInteger) count { return [realArray count]; } - (id) objectAtIndex:(NSUInteger)index { return [realArray objectAtIndex:index]; } // block all the other init's (some could be supported) static id NotSupported() { NSException* myException = [NSException exceptionWithName:@"InvalidInitializer" reason:@"Only initWithClass: and initWithClass:andCapacity: supported" userInfo:nil]; @throw myException; } - (id)initWithArray:(NSArray *)anArray { return NotSupported(); } - (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); } - (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); } - (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); } - (id)initWithObjects:(id)firstObj, ... { return NotSupported(); } - (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); } @end
Benutzen als:
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3]; [monoString addObject:@"A string"]; [monoString addObject:[NSNumber numberWithInt:42]]; // will throw [monoString addObject:@"Another string"];
quelle
NSArray
undNSMutableArray
sind ein Klassencluster und die Art und Weise, wie sie entworfen werden, muss jede Unterklasse einen eigenen Speicher für die Elemente bereitstellen - dies kann eine separate Instanz vonNSArray
/ sein,NSMutableArray
wie hier verwendet. Siehe "Hinweise zur Unterklasse" in derNSArray
Dokumentation.Mit XCode 7 sind Generika jetzt in Objective-C verfügbar!
So können Sie Ihre
NSMutableArray
als deklarieren :NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];
Der Compiler gibt eine Warnung aus, wenn Sie versuchen, ein Nicht-Wheel-Objekt in Ihr Array aufzunehmen.
quelle
*
.[[NSMutableArray<Wheel*> alloc] init...]
?Ich könnte mich irren (ich bin ein Noob), aber ich denke, wenn Sie ein benutzerdefiniertes Protokoll erstellen und sicherstellen, dass die Objekte, die Sie dem Array hinzufügen, demselben Protokoll folgen, dann, wenn Sie das verwendete Array deklarieren
NSArray<Protocol Name>
Dies sollte verhindern, dass Objekte hinzugefügt werden, die dem genannten Protokoll nicht folgen.
quelle
Protocol Name
. Es bezieht sich also auf das Array-Objekt selbst und nicht auf dessen Inhalt. Sie sollten schreibenNSArray<id<Protocol Name>>
, um Ihr ursprüngliches Ziel zu erreichen.Wie ich weiß .. Bevor Sie ein Objekt in Rädern mutableArray hinzugefügt haben, müssen Sie ein Häkchen hinzufügen. Ist das Objekt, das ich hinzufüge, Klasse "Rad". wenn es dann hinzugefügt wird, sonst weise nicht.
Beispiel:
if([id isClassOf:"Wheel"] == YES) { [array addObject:id) }
Etwas wie das. Ich erinnere mich nicht an die genaue Syntax.
quelle
Ich hoffe das wird helfen (und funktionieren ...: P)
Wheel.h Datei:
@protocol Wheel @end @interface Wheel : NSObject @property ... @end
Car.h Datei:
#import "Wheel.h" @interface Car:NSObject { NSString *model; NSString *make; NSMutableArray<Wheel, Optional> *wheels; } @end
Car.m Datei:
#import "Car.h" @implementation Car -(id)init{ if (self=[super init]){ self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init]; } return self; } @end
quelle
Mit Xcode 7 können Sie Arrays, Wörterbücher und sogar Ihre eigenen Klassen als generisch definieren. Die Array-Syntax lautet wie folgt:
NSArray<NSString*>* array = @[@"hello world"];
quelle
Ich glaube nicht, dass es eine Möglichkeit gibt, dies sofort zu tun
NSMutableArray
. Sie könnten dies wahrscheinlich erzwingen, indem Sie alle Konstruktoren und Einfügemethoden unterklassifizieren und überschreiben, aber es lohnt sich wahrscheinlich nicht. Was hoffen Sie damit zu erreichen?quelle
Das ist nicht möglich; Ein NSArray (ob veränderbar oder nicht) enthält einen beliebigen Objekttyp. Was Sie tun können, ist, Ihre eigenen benutzerdefinierten Unterklassen zu erstellen, wie bereits von Jim vorgeschlagen. Wenn Sie alternativ ein Array filtern möchten, um Objekte zu entfernen, die nicht dem gewünschten Typ entsprechen, können Sie Folgendes tun:
- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type { int c = 0; while(c < [array length]) { NSObject *object = [array objectAtIndex:c]; if([object isKindOfClass:type]) c++; else [array removeObjectAtIndex:c]; } } ... [self removeObjectsFromArray:array otherThanOfType:[Car class]];
Oder treffen Sie andere Beurteilungen basierend auf dem Ergebnis von isKindOfClass:, z. B. um ein Array mit einer Mischung aus Autos und Rädern in zwei Arrays zu unterteilen, die jeweils nur eine Art von Objekt enthalten.
quelle
Sie können die nsexception verwenden, wenn Sie das spezifische Objekt nicht haben.
for (int i = 0; i<items.count;i++) { if([[items objectAtIndex:i] isKindOfClass:[Wheel class]]) { // do something..! }else{ [NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items]; // do whatever to handle or raise your exception. } }
quelle
Folgendes habe ich getan, um eine Unterklasse von NSMutableArray zu vermeiden: Verwenden Sie eine Kategorie. Auf diese Weise können Sie die gewünschten Argument- und Rückgabetypen haben. Beachten Sie die Namenskonvention: Ersetzen Sie das Wort "Objekt" in jeder der verwendeten Methoden durch den Namen der Elementklasse. "objectAtIndex" wird zu "WheelAtIndex" und so weiter. Auf diese Weise gibt es keinen Namenskonflikt. Sehr ordentlich.
typedef NSMutableArray WheelList; @interface NSMutableArray (WheelList) - (wheel *) wheelAtIndex: (NSUInteger) index; - (void) addWheel: (wheel *) w; @end @implementation NSMutableArray (WheelList) - (wheel *) wheelAtIndex: (NSUInteger) index { return (wheel *) [self objectAtIndex: index]; } - (void) addWheel: (wheel *) w { [self addObject: w]; } @end @interface Car : NSObject @property WheelList *wheels; @end; @implementation Car @synthesize wheels; - (id) init { if (self = [super init]) { wheels = [[WheelList alloc] initWithCapacity: 4]; } return self; } @end
quelle
addObject:
direkt aufrufen können - was bedeutet, dass ein nachfolgender TypwheelAtIndex:
etwas anderes als ein Rad als Rad wirken kann und Chaos entsteht. Sie können Ihren Ansatz mitMonomorphicArray
der anderen Antwort kombinieren . Erweitern Sie einfachMonomorphicArray
mit einer Klasse mit getippten Gettern und Setzern.Protokoll vielleicht eine gute Idee:
@protocol Person <NSObject> @end @interface Person : NSObject <Person> @end
benutzen:
NSArray<Person>* personArray;
quelle
Incompatible pointer types initializing 'NSArray<Person> *' with an expression of type 'NSArray *'
, selbst wenn Sie Person darin einfügen.Es gibt ein Ein-Header-Dateiprojekt, das dies ermöglicht: Objective-C-Generics
Verwendung :
Kopieren Sie ObjectiveCGenerics.h in Ihr Projekt. Verwenden Sie beim Definieren einer neuen Klasse das GENERICSABLE-Makro.
#import "ObjectiveCGenerics.h" GENERICSABLE(MyClass) @interface MyClass : NSObject<MyClass> @property (nonatomic, strong) NSString* name; @end
Jetzt können Sie Generika mit Arrays und Sets verwenden, wie Sie es normalerweise in Java, C # usw. tun.
Code:
quelle